Java 进阶篇
序号 | 内容 | 链接地址 |
1 | Java核心Api | |
2 | Java集合框架接口 | |
3 | List实现类 | |
4 | Set实现类 | |
5 | Map实现类 | |
6 | 常见接口 |
文章目录
- Java 进阶篇
- Set集合的常用实现类 TreeSet和HashSet
- HashSet
- TreeSet
- 关于重复元素的说明(重点)
- 小结:
Set集合的常用实现类 TreeSet和HashSet
HashSet
- HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
- HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
特点:
- 不能保证元素的排列顺序
- HashSet 不是线程安全的
- 集合元素可以是 null
- HashSet集合判断两个元素相等的标准:
- 两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。
- 对于存放在Set容器中的对象,**对应的类一定要重写equals()**和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
向HashSet中添加元素的过程:
- 当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)
- 如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。
- 如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
重写 hashCode() 方法的基本原则
- 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
- 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode()
方法的返回值也应相等。
- 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
重写 equals() 方法的基本原则
以自定义的Customer类为例,何时需要重写equals()?
- 当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是
要改写hashCode(),根据一个类的equals方法(改写后),两个截然不
同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法,
它们仅仅是两个对象。
- 因此,违反了“相等的对象必须具有相等的散列码”。
- 结论:复写equals方法的时候一般都需要同时复写hashCode方法,hashCode的对象的属性也应该参与到equals()中进行计算。
示例
例:观察HashSet
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo01 {
public static void main(String[] args) {
Set<String> all = new HashSet<String>(); // 实例化Set接口对象
all.add("A");
all.add("B");
all.add("C");
all.add("D");
all.add("E");
System.out.println(all);
}
}
使用HashSet 实例化的Set 接口实例,本身属于无序的存放。
在Collection 接口中定义了将集合变为对象数组,可以用来进行输出。
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo02 {
public static void main(String[] args) {
Set<String> all = new HashSet<String>(); // 实例化Set接口对象
all.add("A");
all.add("B");
all.add("C");
all.add("D");
all.add("E");
Object obj[] = all.toArray(); // 将集合变为对象数组
for (int x = 0; x < obj.length; x++) {
System.out.print(obj[x] + "、");
}
}
}
但是,以上的操作不好,因为在操作的时候已经指定了操作的泛型类型,那么现在最好的做法是由泛型所指定的类型变为指定的数组。
<T> T[] toArray(T[] a)
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo03 {
public static void main(String[] args) {
Set<String> all = new HashSet<String>(); // 实例化Set接口对象
all.add("A");
all.add("B");
all.add("C");
all.add("D");
all.add("E");
String[] str = all.toArray(new String[] {});// 变为指定的泛型类型数组
for (int x = 0; x < str.length; x++) {
System.out.print(str[x] + "、");
}
}
}
下面再进一步验证Set 接口中是不能有重复的内容的。
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo04 {
public static void main(String[] args) {
Set<String> all = new HashSet<String>(); // 实例化Set接口对象
all.add("A");
all.add("A"); // 重复元素
all.add("A"); // 重复元素
all.add("A"); // 重复元素
all.add("A"); // 重复元素
all.add("B");
all.add("C");
all.add("D");
all.add("E");
System.out.println(all);
}
}
以上字符串“A”设置了很多次,因为Set 接口中是不能有任何的重复元素的,所以其最终结果只能有一个“A”。
TreeSet
特点
- TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
- TreeSet底层使用红黑树结构存储数据
- 有序,查询速度比List快再具体就不说了,可以参看这篇博文对红黑树的讲解写得不错。
- TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。
- 向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。
- 新增的方法如下: (了解)
• Comparator comparator()
• Object first()
• Object last()
• Object lower(Object e)
• Object higher(Object e)
• SortedSet subSet(fromElement, toElement)
• SortedSet headSet(toElement)
• SortedSet tailSet(fromElement)
示例
下面通过代码来观察其是如何进行排序的。
import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo01 {
public static void main(String[] args) {
Set<String> all = new TreeSet<String>(); // 实例化Set接口对象\
all.add("D");
all.add("X");
all.add("A");
System.out.println(all);
}
}
虽然在增加元素的时候属于无序的操作,但是增加之后却可以为用户进行排序功能的实现。
3.4.3 排序的说明(重点)
对于自定义类型Person
如果要进行排序的话,则必须在Person 类中实现Comparable 接口。
public class Person implements Comparable<Person> {
private String name;
private int age;
public int compareTo(Person per) {
if (this.age > per.age) {
return 1;
} else if (this.age < per.age) {
return -1;
} else {
return 0;
}
}
public Person() {}
public Person(String name, int age) { this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
}
import java.util.Set;
import java.util.TreeSet;
public class TreeSetPersonDemo01 {
public static void main(String[] args) {
Set<Person> all = new TreeSet<Person>();
all.add(new Person("张三", 10));
all.add(new Person("李四", 10));
all.add(new Person("王五", 11));
all.add(new Person("赵六", 12));
all.add(new Person("孙七", 13));
System.out.println(all);
}
}
程序的执行结果如下:
[姓名:张三,年龄:10, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:孙七,年龄:13]
从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则此时必须修改Person 类,如果假设年龄相等的话,按字符串进行排序。
public int compareTo(Person per) {
if (this.age > per.age) {
return 1;
} else if (this.age < per.age) {
return -1;
} else {
return this.name.compareTo(per.name);
}
}
此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。
关于重复元素的说明(重点)
之前使用Comparable 完成的对于重复元素的判断,那么Set 接口定义的时候本身就是不允许重复元素的,那么证明如果现在真的是有重复元素的话,使用HashSet 也同样可以进行区分。
import java.util.HashSet;
import java.util.Set;
public class HashSetPersonDemo01 {
public static void main(String[] args) {
Set<Person> all = new HashSet<Person>();
all.add(new Person("张三", 10));
all.add(new Person("李四", 10));
all.add(new Person("李四", 10));
all.add(new Person("王五", 11));
all.add(new Person("赵六", 12));
all.add(new Person("孙七", 13));
System.out.println(all);
}
}
此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过Comparable接口间接完成的(返回值为0时告诉了程序是相同元素)。
如果要想判断两个对象是否相等,则必须使用Object 类中的equals()方法。
从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:
- 第一种判断两个对象的编码是否一致,这个方法需要通过hashCode()完成,即:每个对象有唯一的编码
- 还需要进一步验证对象中的每个属性是否相等,需要通过equals()完成。
public boolean equals(Object obj) {
if (this == obj) {
return true;
} if (!(obj instanceof Person)) {
return false;
}
Person per = (Person) obj;
if (per.name.equals(this.name) && per.age == this.age) {
return true;
} else {
return false;
}
}
public int hashCode() {
return this.name.hashCode() * this.age;
}
发现,此时已经不存在重复元素了,所以如果要想去掉重复元素需要依靠hashCode()和equals()方法共同完成。
小结:
关于TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现
Comparable 接口,则不能实现TreeSet 的排序,会报类型转换(转向Comparable 接口)错误。
换句话说要添加到TreeSet 集合中的对象的类型必须实现了Comparable 接口。
不过TreeSet 的集合因为借用了Comparable 接口,同时可以去除重复值,而HashSet 虽然是
Set 接口子类,但是对于没有复写Object 的equals 和hashCode 方法的对象,加入了HashSet
集合中也是不能去掉重复值的。