Set不保存重复的元素。Set中最常被使用的是测试归属性,你可以很容易的询问某个对象是否在某个Set中。Set具有与Collection完全一样的接口,因此没有任何额外的功能。实际上Set就是Collection,只是行为不同。
实现了Set接口的主要有HashSet、TreeSet、LinkedHashSet这几个共同点就是每个相同的项只保存一份。他们也有不同点,区别如下:
一.HashSet
主要用来去重
|–HashSet:底层数据结构是哈希表。是线程不安全的。不同步。
HashSet是如何保证元素唯一性的呢?
是通过元素的两个方法,hashCode和equals来完成。
如果元素的HashCode值相同,才会判断equals是否为true。
如果元素的hashcode值不同,不会调用equals。
HashSet使用的是相当复杂的方式来存储元素的,使用HashSet能够最快的获取集合中的元素,效率非常高(以空间换时间)。会根据hashcode和equals来庞端是否是同一个对象,如果hashcode一样,并且equals返回true,则是同一个对象,不能重复存放。
所以一般都是用来去重的
public class Demo01 {
public static void main(String[] args) {
fun1();
fun2();
}
public static void fun1() {
// 创建一个HashSet集合 保存两个a b c d
// 并打印
HashSet<String> hashSet = new HashSet<>();
hashSet.add("a");
hashSet.add("a");
hashSet.add("b");
hashSet.add("b");
hashSet.add("d");
hashSet.add("d");
hashSet.add("c");
hashSet.add("c");
// 使用增强for循环遍历
for (String string : hashSet) {
System.out.println(string);
}
}
public static void fun2() {
// 需求 创建一个HashSet 保存 5个人 并遍历
HashSet<Person> hashSet = new HashSet<>();
hashSet.add(new Person("q", 12));
hashSet.add(new Person("q", 12));
hashSet.add(new Person("e", 15));
hashSet.add(new Person("e", 15));
hashSet.add(new Person("v", 11));
Iterator<Person> iterator = hashSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
结果
a
b
c
d
看上述的例子 可看到fun1的方法 可以成功去除重复元素 fun2就不可以了
因为:
在java 中字符串对象是一种特殊的对象.String 类是一个不可变的类..也就说,String 对象
一旦创建就不允许修改。String 类有一个对应的 String 池,也就是 String pool.每一个内容相同的字符串对象都对应于一个 pool 里的对象. 所以在hashSet中存储两个内容相同的String,实际上他们都是对应于一个pool里的对象。所以说不是hashset可以识别string的唯一性,而是内容相同的String他们其实就是一个对象。在不允许重复的set中自然是不允许同一个对象存在两次的。
而把对象装进去呢 是因为每次都是new一次 创建一个新对象 哈希值就会不一样 就不会调用equals方法去比较 直接存进去了 这个时候需要在Student类中去重写hashCode和equals方法
二.TreeSet
主要用来排序
TreeSet也不能存放重复对象,但是TreeSet会自动排序,如果存放的对象不能排序则会报错,所以存放的对象必须指定排序规则。排序规则包括自然排序和客户排序。
①自然排序:TreeSet要添加哪个对象就在哪个对象类上面实现java.lang.Comparable接口,并且重写comparaTo()方法,返回0则表示是同一个对象,否则为不同对象。
②自定义排序:建立一个第三方类并实现java.util.Comparator接口。并重写方法。定义集合形式为TreeSet ts = new TreeSet(new 第三方类());
/**
* 创建一个TreeSet集合 添加几个数 查看效果
*/
public static void fun1() {
int[] array = {1,5,78,3,32,5,7,7};
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(1);
treeSet.add(3);
treeSet.add(2);
treeSet.add(3);
treeSet.add(4);
System.out.println(treeSet);
}
/**
* 创建一个TreeSet 添加4个人
*/
public static void fun2() {
// 创建一个TreeSet 添加4个人
TreeSet<Person> treeSet = new TreeSet<>();
treeSet.add(new Person("周五", 12));
treeSet.add(new Person("李四", 12));
treeSet.add(new Person("小三", 11));
treeSet.add(new Person("小三", 16));
System.out.println(treeSet);
}
}
结果
[1, 2, 3, 4]
fun1成功打印出来 因为都是常量 fun2就不行 他是个对象 想要排序 要实现compareble接口 重写compareTo的方法
TreeSet的第一种排序方式是实现Comparable接口覆写compareTo方法,此排序是自然顺序排序。
TreeSet的第二种排序方式是实现Comparator接口覆写compare方法,此排序是在集合一初始化就拥有的,所以要讲此接口的实例对象传入到TreeSet的构造函数中。
3.TreeSet总结
1). TreeSet的特点
(1). 可以对元素进行排序
有两种排序方式。
(2). TreeSet保证元素的唯一性依据
在实现的Comparable的compareTo或者Comparator的compare方法中,如果这两个方法的返回值为0,那么TreeSet就认为这两个元素一样。按照Set的唯一性规则,在一次重复的元素不能被添加到TreeSet这个集合中。
2). TreeSet的两种排序方式
(1). 让元素本身具有比较性
元素本身要实现Comparable接口并实现里面的compareTo方法以保证元素本身具有比较性
(2). 让容器自身具有比较性
当元素本身不具有比较性或者具备的比较性不是所需要的,就在TreeSet建立实例的时候,传入Comparator接口的实现子类的实例。这个Comparator子类必须实现compare方法。
(3). 元素的可存储性
[1]. 自定义的类,如果重写了hashCode和equals两个方法,可以存入HashSet容器
[2]. 自定义的类,如果实现了Comparable的compareTo()方法,可以存入TreeSet容器。
【总结】如果自定义的类既重写了hashCode和equals,又实现了compareTo,那么这个类的元素既可以存入HashSet容器,也可以存入TreeSet容器。
三.比较器
当元素自身不具备比较性,或者具备的比较性不是所需要的。
这时需要让容器自身具备比较性。
定义了比较器,将比较器对象作为参数传递给TreeSet集合的构造函数。
// 创建一个比较器的类 实现比较器接口中的方法
public class ComparatorImpl implements Comparator<String>{
// 主要按字符串长度比较
// 次要按字符比
// 比较器相当于TreeSet
@Override
public int compare(String o1, String o2) {
int length = o1.length() - o2.length();
int num = length == 0 ? o1.compareTo(o2) : length;
// 如果字符串长度和字符都相同 也需要把字符串存进去
// 所以返回1 或 -1(不返0就行)
return num == 0 ? 1 : num;
}
}
public static void fun3() {
// 比较器
// 集合中保存字符串 按字符串长度排序
// 初始化TreeSet的同时把比较器穿进去 传入TreeSet中去
TreeSet<String> treeSet = new TreeSet<>(new ComparatorImpl());
treeSet.add("aaa");
treeSet.add("bb");
treeSet.add("cccc");
treeSet.add("z");
treeSet.add("bb");
for (String string : treeSet) {
System.out.println(string);
}
System.out.println(treeSet);
}
java中treeset使用Comparator进行比较的三种方法
1.让元素具备比较性。
比如我们比较两个人。我们定义一个person类,并且实现Comparable接口
例:
public class Person implements Comparable{
private int age;
private String name;
public Person(){}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public int compareTo(Person o) {
int number = this.age - o.getAge();
// 如果年龄相同去看姓名
// 如果年龄不同直接返回年龄的差
return number ==0 ? this.name.compareTo(o.getName()) :number;
}
2.第二种是写个类实现Comparator接口
例:
class myComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
int num = p1.getName().compareTo(p2.getName());
// 0的话是两个相同,进行下一个属性比较
if (num == 0){
return new Integer(p1.getAge()).compareTo(new Integer(p2.getAge()));
}
return num;
}
}
然后在new Set的时候放进去。如
TreeSet ts = new TreeSet(new myComparator());
3.第三种写内名内部类方法如:
TreeSet ts = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
int num = p1.getName().compareTo(p2.getName());
if (num == 0){
return new Integer(p1.getAge()).compareTo(new Integer(p2.getAge()));
}
return num;
}
});
四.LinkedHashSet
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("a");
linkedHashSet.add("a");
linkedHashSet.add("c");
linkedHashSet.add("c");
linkedHashSet.add("b");
linkedHashSet.add("b");
linkedHashSet.add("d");
linkedHashSet.add("d");
System.out.println(linkedHashSet);
}
结果
[a, c, b, d]
通过结果会发现 LinkedHashSet是有序 去重复的
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。