java Set接口和实现类的应用和特点HashSet、LinkedHashSet、TreeSet
1.集合结构
- Collection接口
- List接口:存储有序的,可重复的元素
- ArrayList(主要的实现类)
- LinkedList(频繁的插入删除操作等)
- Vector(古老的实现类,线程安全的,但效率远低于ArrayList)
- Set接口:存储无序的,不可重复的元素
- HashSet、 LinkedHashSet、TreeSet
- List接口:存储有序的,可重复的元素
- Map接口:存储“键-值”对的数据
- HashMap(主要实现类)、LinkedHashMap、TreeMap、HashTable(子类:Properties)
2.Set接口
- Set接口是Collection的子接口,set接口没有提供额外的方法,其常用的方法都是Collection常用的
- Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败。
- Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals 方法
- demo
public class TestSet {
@Test
public void testHashSet() {
Set set = new HashSet<>();
set.add(123);
set.add(456);
set.add("AA");
set.add("AA");
set.add("BB");
set.add(null);
Person p1 = new Person("GG",23);
Person p2 = new Person("GG",23);
System.out.println(p1.equals(p2));
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
set.add(p1);
set.add(p2);
System.out.println(set.size());
System.out.println(set);
}
}//Peson类在最下方
- 无序性:无序性不等于 随机性 (内存空间存的位置是无序的),真正的无序性,指定是元素在底层存储位置是无序的。
- 不可重复性:当向Set添加相同的元素的时候,后面不能添加进去。
说明:要求添加进去Set中的元素所在的类,一定要重写equals()和hashCode()方法进而保证Set中的不可重复性! - Set中的元素是如何存储的呢?
当向Set中添加对象时,首先调用此对象的所在类的hashCode()方法,计算此对象的hash值,决定了此对象在Set中的存储位置,若此位置之前没有对象,则此对象直接存到这个位置,若此位置已有对象存储,再通过equals()比较两个对象是否相同,如果相同,则后一个对象不能再添加进来。
万一返回false呢?都能存储在一个位置。(不建议这样使用)
要求:hashCode()方法要与equals()方法一致。即一致重写。
3. Set接口的实现类之一:HashSet (主要实现类)
-
HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
-
HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。
-
HashSet 具有以下特点:
①不能保证元素的排列顺序
②HashSet 不是线程安全的
③集合元素可以是 null -
当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值决定该对象在 HashSet 中的存储位置。
-
HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。
-
demo参照Set接口的。
4.hashCode() 方法
- 如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
- 对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。
- 重写 hashCode() 方法的基本原则
①在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值
②当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等
③对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值
5.Set接口实现类之二:LinkedHashSet
- LinkedHashSet 是 HashSet 的子类
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
- LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
- LinkedHashSet 不允许集合元素重复。
- LinkedHashSet:使用了链表维护了一个添加进集合中的顺序,导致当我遍历LinkedHashSet集合元素的时候,是按照添加进去的顺序遍历的。
- demo
@Test
public void testLinkedHashSet() {
Set set = new LinkedHashSet<>();
set.add(123);
set.add(456);
set.add("AA");
set.add("AA");
set.add("BB");
set.add(null);
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());//遍历顺序不变
}
}
- 内存图
6.Set实现类之三:TreeSet
-
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
-
TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。
-
向TreeSet中添加的元素必须是同一个类型的。
-
由于TreeSet较为特殊,另写在其他博客
-
TreeSet的方法:
Comparator comparator()
Object first()
Object last()
Object lower(Object e)
Object higher(Object e)
SortedSet subSet(fromElement, toElement)
SortedSet headSet(toElement)
SortedSet tailSet(fromElement)
7.父类
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
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;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}