目录
Interface Set
Set接口
HashSet
LinkedHashSet
TreeSet
TreeSet存储对学生对象遍历
观察API文档我们发现:
public interface Set
extends Collection
不包含重复元素的集合。 更正式地,集合不包含一对元素
e1
和e2
,使得e1.equals(e2)
,并且最多一个空元素。 正如其名称所暗示的那样,这个接口模拟了数学集抽象。Set接口除了继承自Collection接口的所有构造函数的合同以及add,equals和hashCode方法的合同外 , 还 增加了其他规定。 其他继承方法的声明也包括在这里以方便。 (伴随这些声明的规范已经量身定做Set接口,但它们不包含任何附加的规定。)
构造函数的额外规定并不奇怪,所有构造函数都必须创建一个不包含重复元素的集合(如上所定义)。
注意:如果可变对象用作设置元素,则必须非常小心。 如果对象的值以影响equals比较的方式更改,而对象是集合中的元素, 则不指定集合的行为。 这种禁止的一个特殊情况是,一个集合不允许将其本身作为一个元素。
一些集合实现对它们可能包含的元素有限制。 例如,一些实现禁止空元素,有些实现对元素的类型有限制。 尝试添加不合格元素会引发未经检查的异常,通常为NullPointerException或ClassCastException 。 尝试查询不合格元素的存在可能会引发异常,或者可能只是返回false; 一些实现将展现出前者的行为,一些实现将展现出后者。 更一般来说,尝试对不符合条件的元素的操作,其完成不会导致不合格元素插入到集合中,可能会导致异常,或者可能会成功执行该选项。 此异常在此接口的规范中标记为“可选”。
此接口是成员Java Collections Framework 。
一个不包含重复元素的 collection,就是元素唯一且元素无序(存储和取出不一致)的集合。
存储字符串并遍历,参考代码:
/*
存储字符串并遍历*/
import java.util.HashSet;
public class SetDemo {public static void main(String[] args) {//定义字符串集合HashSet
}
输出结果:
flink
world
java
bigdata
spark
hello
hadoop
查看API文档我们知道:
public class HashSet
extends AbstractSet
implements Set, Cloneable, Serializable 此类实现Set接口,由哈希表(实际为HashMap实例)支持。 对集合的迭代次序不作任何保证; 特别是,它不能保证订单在一段时间内保持不变。 这个类允许null元素。
这个类提供了基本操作(add,remove,contains和size)固定的时间性能,假定哈希函数将分散的桶中正确的元素。 迭代此集合需要与HashSet实例的大小(元素数量)和后台HashMap实例(桶数)的“容量”的总和成比例的时间。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)是非常重要的。
请注意,此实现不同步。 如果多个线程并发访问哈希集,并且至少有一个线程修改该集合,那么它必须在外部进行同步。 这通常通过在自然地封装集合的一些对象上进行同步来实现。 如果没有这样的对象存在,那么该集合应该使用Collections.synchronizedSet方法“包装”。 这最好在创建时完成,以防止对该集合的意外不同步访问:
Set s = Collections.synchronizedSet(new HashSet(...));
该类iterator方法返回的迭代器是故障快速的 :如果集合在迭代器创建之后的任何时间被修改,除了通过迭代器自己的remove方法之外,迭代器会抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。
请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速迭代器尽力投入ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。
通过观察API文档我们得出结论:
int hashCode()
boolean equals(Object obj)
存储自定义对象并遍历,参考代码:
创建Student2对象:
import java.util.Objects;public class Student2 {private String name;private int age;public Student2() {}public Student2(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;}@Overridepublic String toString() {return "Student2{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student2 student2 = (Student2) o;return age == student2.age &&Objects.equals(name, student2.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}
}
创建HashSetDemo测试类:
/*存储自定义对象并遍历*/
import java.util.HashSet;public class HashSetDemo {public static void main(String[] args) {//创建集合对象HashSet
}
输出结果:
Student2{name='郭富城', age=50}
Student2{name='张学友', age=60}
Student2{name='刘德华', age=45}
查看API文档我们知道:
public class LinkedHashSet
extends HashSet
implements Set, Cloneable, Serializable 哈希表和链表实现了Set接口,具有可预测的迭代次序。 这种实现不同于HashSet,它维持于所有条目的运行双向链表。 该链表定义了迭代排序,它是将元素插入集合(插入顺序 ) 的顺序 。 请注意,如果一个元件被重新插入到组插入顺序不受影响 。 (元件e重新插入一组s如果当s.contains(e)将返回true之前立即调用s.add(e)被调用。)
此实现可以让客户从提供的指定,通常杂乱无章的排序HashSet ,而不会导致与其相关的成本增加TreeSet 。 它可以用于生成与原始文件具有相同顺序的集合的副本,而不管原始集的实现:
void foo(Set s) {Set copy = new LinkedHashSet(s);...}
如果模块在输入上进行设置,复制它,并且稍后返回其顺序由该副本确定的结果,则此技术特别有用。 (客户一般都喜欢以相同的顺序返回事情。)
该类提供了所有可选的Set操作,并允许null元素。 像HashSet,它提供了基本操作(add,contains和remove)稳定的性能,假定散列函数散桶中适当的元件。 性能可能略低于HashSet ,由于维护链表的额外费用,但有一个例外:LinkedHashSet的迭代需要与集合的大小成比例的时间,无论其容量如何。 HashSet的迭代可能更昂贵,需要与其容量成比例的时间。
链接哈希集具有影响其性能的两个参数: 初始容量和负载因子 。 它们的定义精确到HashSet 。 但是请注意,该惩罚为初始容量选择非常高的值是该类比HashSet不太严重的,因为迭代次数对于这个类是由容量不受影响。
请注意,此实现不同步。 如果多个线程同时访问链接的散列集,并且至少有一个线程修改该集合,那么它必须在外部进行同步。 这通常通过在自然地封装集合的一些对象上进行同步来实现。 如果没有这样的对象存在,则应该使用Collections.synchronizedSet方法“包装”。 这最好在创建时完成,以防止对该集合的意外不同步访问:
Set s = Collections.synchronizedSet(new LinkedHashSet(...));
该类iterator方法返回的迭代器是故障快速的 :如果在创建迭代器之后的任何时间对该集合进行了修改,除了通过迭代器自己的remove方法之外,迭代器将会抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。
请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速迭代器尽力投入ConcurrentModificationException 。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。
这个班是Java Collections Framework的会员 。
通过观察API文档我们得出结论:
参考代码:
import java.util.LinkedHashSet;
import java.util.TreeSet;public class LinkedHashSetDemo {public static void main(String[] args) {//创建LinkedHashSet集合对象LinkedHashSet
输出结果:
遍历LinkedHashSet集合:
hello
world
java
bigdata
hadoop
spark
flink
=========================
遍历TreeSet集合:
bigdata
flink
hadoop
hello
java
spark
world
通过观察代码和输出结果我们得出结论:
通过输出结果我们发现,结果去重了,而且输出的结果顺序与添加的顺序不一样,这是为什么呢?
我们查看原码:
public interface Set
}public class HashMap
通过查看API文档我们知道:
public class TreeSet
extends AbstractSet
implements NavigableSet, Cloneable, Serializable A NavigableSet实现基于TreeMap 。 的元件使用其有序natural ordering ,或由Comparator集合创建时提供,这取决于所使用的构造方法。
此实现提供了基本的操作(保证的log(n)时间成本
add
,remove
和contains
)。需要注意的是由一组(无论是否提供了明确的比较器)保持的顺序必须与equals一致 ,如果它是要正确实现
Set
接口。 (参见Comparable
或Comparator
为一致的精确定义与equals)。这是因为该Set
接口在来定义equals
的操作,但一个TreeSet
例如使用其执行所有元件比较compareTo
(或compare
)方法,于是两个通过该方法认为相等的元素从集合的角度来看是相等的。 集合的行为是明确定义的,即使其排序与equals不一致; 它只是没有遵守Set
界面的总体合同。请注意,此实现不同步。 如果多个线程并发访问树,并且至少有一个线程修改该集合,则必须在外部进行同步。 这通常通过在自然地封装集合的一些对象上进行同步来实现。 如果没有这样的对象存在,那么该集合应该使用Collections.synchronizedSortedSet方法“包装”。 这最好在创建时完成,以防止对该集合的意外不同步访问:
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));
该类
iterator
方法返回的迭代器是故障快速的 :如果在迭代器创建之后的任何时间对该集合进行了修改,除了通过迭代器自己的remove
方法之外,迭代器将抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。请注意,迭代器的故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速迭代器尽力投入
ConcurrentModificationException
。 因此,编写依赖于此异常的程序的正确性将是错误的:迭代器的故障快速行为应仅用于检测错误。这个班是Java Collections Framework的会员 。
通过观察API文档我们得出结论:
参考代码:
import java.util.TreeSet;public class TreeSetDemo1 {public static void main(String[] args) {//创建集合对象TreeSet
}
输出结果:
2
12
18
20
23
24
66
通过观察代码和运行结果我们得出结论:TreeSet元素唯一,元素顺序可以按照某种规则进行排序:自然排序、比较器排序。
那去重又是为什么呢?我们查看原码:
public abstract class AbstractCollection
} public class TreeMap
}
参考代码1:
创建Student3类:
public class Student3 {private String name;private int age;//创建无参的构造方法public Student3() {}//创建有参的构造方法public Student3(String name, int age) {this.name = name;this.age = age;}//创建getXxx()和setXxx()方法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;}//重写toString方法@Overridepublic StringtoString() {return "Struden3{" +"name='" + name + '\'' +", age=" + age +'}';}}
创建TreeSeDemo2对象:
import java.util.TreeSet;public class TreeSetDemo2 {public static void main(String[] args) {//创建集合对象TreeSet ts &#61; new TreeSet<>();//创建学生集合对象Student3 s1 &#61; new Student3("张三", 24);Student3 s2 &#61; new Student3("李四", 32);Student3 s3 &#61; new Student3("王二", 53);Student3 s4 &#61; new Student3("麻子", 14);Student3 s5 &#61; new Student3("靓仔", 23);Student3 s6 &#61; new Student3("华仔", 67);Student3 s7 &#61; new Student3("老街", 35);//将学生添加到集合中ts.add(s1);ts.add(s2);ts.add(s3);ts.add(s4);ts.add(s5);ts.add(s6);ts.add(s7);//使用增强for循环遍历集合for (Object t : ts) {System.out.println(t);}}
}
输出结果&#xff1a;
输出结果报错了&#xff0c;这是为什么呢&#xff1f;我们查看原码:
public abstract class AbstractCollection
} public class TreeMap
}
通过查看原码我们得知&#xff1a;由于我们这里创建TreeSet对象调用 的是无参构造方法&#xff0c;所以走的是自然排序&#xff0c;而底层原码有一步向下转型&#xff1a;
Comparable super K> k &#61; (Comparable super K>) key;
原因是我们Student3类没有实现Comparable接口&#xff0c;无法向下转型&#xff0c;所以报错了。
那么实现一下Student3的Comparable接口&#xff1a;
我们再运行一下&#xff1a;
return 0;
return 1;
return -1;
通过不同的返回值我们发现&#xff1a;其实这里的返回值是根据我们的TreeSet的规则进行升序或降序的&#xff0c;比如我们想在去重的前提下&#xff0c;按照年龄进行排序&#xff1a;
returen this.age &#61; o.age;
但题目的要求是年龄一样&#xff0c;姓名不一定不一样
通过运行结果我们发现姓名一样&#xff0c;年龄一样&#xff0c;只保留一开始添加到集合中的学生对象。&#xff08;达到了题目的要求&#xff09;
完整代码如下&#xff1a;
Student3&#xff1a;
public class Student3 implements Comparable
// return 0;//升序
// return 1;//降序
// return -1;//年龄去重
// return this.age - o.age;//姓名年龄都去重int i &#61; this.age - o.age;int i2 &#61; i &#61;&#61; 0 ? this.name.compareTo(o.name) : i;return i2;}}
TreeSetDemo2&#xff1a;
import java.util.TreeSet;public class TreeSetDemo2 {public static void main(String[] args) {//创建集合对象TreeSet ts &#61; new TreeSet<>();//创建学生集合对象Student3 s1 &#61; new Student3("张三", 24);Student3 s2 &#61; new Student3("李四", 32);Student3 s3 &#61; new Student3("王二", 53);Student3 s4 &#61; new Student3("麻子", 14);Student3 s5 &#61; new Student3("靓仔", 23);Student3 s55 &#61; new Student3("靓仔", 23);Student3 s555 &#61; new Student3("靓仔", 18);Student3 s6 &#61; new Student3("华仔", 67);Student3 s7 &#61; new Student3("老街", 35);//将学生添加到集合中ts.add(s1);ts.add(s2);ts.add(s3);ts.add(s4);ts.add(s5);ts.add(s6);ts.add(s7);ts.add(s55);//使用增强for循环遍历集合for (Object t : ts) {System.out.println(t);}}
}
输出结果&#xff1a;
Struden3{name&#61;&#39;麻子&#39;, age&#61;14}
Struden3{name&#61;&#39;靓仔&#39;, age&#61;23}
Struden3{name&#61;&#39;张三&#39;, age&#61;24}
Struden3{name&#61;&#39;李四&#39;, age&#61;32}
Struden3{name&#61;&#39;老街&#39;, age&#61;35}
Struden3{name&#61;&#39;王二&#39;, age&#61;53}
Struden3{name&#61;&#39;华仔&#39;, age&#61;67}
&#x1f536;到底啦&#xff01;给靓仔一个关注吧&#xff01;&#x1f536;