Java基础23-集合类2(Set接口,Iterator迭代器)
一、Set接口简介
根据API,Set接口是一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2)
的元素对 e1
和 e2
,并且最多包含一个 null 元素。正如其名称所暗示的,此接口模仿了数学上的 set 抽象。
二、Set接口特性
1.不允许重复(HashCode/equals方法)
2.不记录添加顺序(没有添加顺序,但不代表元素排列没有顺序)
三、实现类
(1).HashSet
此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。
1.常用方法
public static void main(String[] args) { Set<String> set=new HashSet<>(); set.add("o"); set.add("0"); set.add("-1"); set.add("s"); //以下的有序排列是因为它们的hashcode是有序的,是根据hashcode来进行排序的 set.add("a"); set.add("b"); set.add("c"); set.add("d"); set.add("e"); //相同的元素hashcode相同,后边这个会将前边的覆盖掉 set.add("0"); System.out.println(set);//[0, a, b, s, c, -1, d, e, o] Set<String> set1=new HashSet<>(); set1.add("6"); set1.add("8"); //将set1里边的值放到set中 set.addAll(set1); //set.clear(); for (String ele : set) { System.out.println(ele);//0 a b s c -1 d e 6 8 o } System.out.println("--------------"); Iterator<String> it = set.iterator(); while(it.hasNext()) { System.out.println(it.next()); } System.out.println(set.remove("-1"));//true set.removeAll(set1); System.out.println(set);//[0, a, b, s, c, d, e, o] }
HshSet是set接口最常用的实现类,底层采用了哈希表(散列/hash)算法
其底层其实也是一个数组,存在的意义是提供查询速度,插入速度以比较快,但是适用于少量数据的插入操作
2.在HshSet中如何判断两个对象是否相同的问题
1).两个对象的equals比较相等,返回true则说明相同对象
2).两个对象的hashcode方法返回值相等
对象的hashcode值决定了在哈希表中的存储位置
3. 当往hashset集合中添加新的对象时,先判断该对象中的hashcode
- 如果不等:直接把新的对象存储到hashcode指定的位置
- 如果相等:再继续判断新对象和集合对象中的equals做比较
若hashcode相同,equals为false:非常麻烦,存储在之前对象同槽位的链表上(操作比较麻烦)
3.对象的hashcode和equals方法的重要性:
每一个存储到hash表中的对象,都得提供hashcode和equals方法,用来判断是否是同一个对象
存储在哈希表中的对象,都应该覆盖equals方法和hashcode方法并且保证equals相等的时候,hashcode也应该相等
(2).LinkedHashSet
此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。注意,插入顺序不 受在 set 中重新插入的 元素的影响。(如果在 s.contains(e) 返回 true 后立即调用 s.add(e),则元素 e 会被重新插入到 set s 中。)
Set<String> set2=new LinkedHashSet<>(); set2.add("o"); set2.add("0"); set2.add("-1"); set2.add("s"); set2.add("a"); set2.add("b"); set2.add("c"); set2.add("d"); set2.add("e"); //链表算法(保证添加顺序,但无索引) System.out.println(set2);//[o, 0, -1, s, a, b, c, d, e]
(3)TreeSet
基于 TreeMap
的 NavigableSet
实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator
进行排序,具体取决于使用的构造方法。
主要是用于排序,但实际开发中主要是在数据库中进行排序等操作。
TreeSet也不能存放重复对象,但是TreeSet会自动排序,如果存放的对象不能排序则会报错,所以存放的对象必须指定排序规则。排序规则包括自然排序和自定义排序。
①自然排序:TreeSet要添加哪个对象就在哪个对象类上面实现java.lang.Comparable接口,并且重写comparaTo()方法,返回0则表示是同一个对象,否则为不同对象。
②自定义排序:建立一个第三方类并实现java.util.Comparator接口。并重写方法。定义集合形式为TreeSet ts = new TreeSet(new 第三方类());
//TreeSet集合底层才有红黑树算法,会对存储的元素默认使用自然排序(从小到大);
//注意:必须保证TreeSet集合中的元素对象是相同的数据类型,否则报错
//在TreeSet的自然排序中,认为如果两个对象作比较的compareTo方法返回的是0,则认为是同一个对象
TreeSet<String> set3=new TreeSet(); set3.add("A"); set3.add("V"); set3.add("6"); set3.add("1"); set3.add("g"); System.out.println(set3);//[1, 6, A, V, g],这是根据ASCII码进行排序
①自然排序
实现Comparable接口
重写hashcode和equals方法
重写compareTo方法
TreeSet<Person> set4=new TreeSet(); set4.add(new Person("路飞",18)); set4.add(new Person("索隆",26)); set4.add(new Person("山治",23)); set4.add(new Person("娜美",22)); System.out.println(set4); //[Person [name=路飞, age=18], Person [name=娜美, age=22], Person [name=山治, age=23], Person [name=索隆, age=26]]
三、Iterator迭代器
对 collection 进行迭代的迭代器。迭代器取代了 Java Collections Framework 中的 Enumeration。迭代器与枚举有两点不同:
- 迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的 collection 移除元素。
- 方法名称得到了改进。
它 提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。 从定义可见,迭代器模式是为容器而生。很明显,对容器对象的访问必然涉及到遍历算法。
public class IteratorDemo { /*把集合中的元素一个一个的遍历出来。 * 迭代器对象 * * */ public static void main(String[] args) { List list=new ArrayList(); list.add("a"); list.add("b"); list.add("c"); //方式1:for循环 //方式2:for-each 增强for循环 //for(元素类型 ele:数组名/Iterable实现类对象) for (Object object : list) { System.out.println(object); } //方式3:使用while循环操作迭代器Iterator Iterator it=list.iterator(); while(it.hasNext()) { System.out.println(it.next()); } //方式3:for循环操作迭代器 for(Iterator it1=list.iterator();it.hasNext();) { System.out.println(it.next()); } //深入分析foreach和迭代器 //1.foreach操作数组:底层依然是采用for循环+索引来获取数组元素 int[] arr= {1,2,3}; for (int i : arr) { System.out.println(i); } //2.foreach可以操作iterable的实例:底层其实采用的iterator迭代器 for (Object object : list) { System.out.println(object); if("c".equals(object)) { list.remove(object); //并发修改异常 java.util.ConcurrentModificationException //当使用迭代器时,当前线程A中,会单独创建一个新的线程B //A线程负责继续迭代,B线程负责去删除 //B线程每次都会去检查和A线程中的元素是否个数相同,如果不是则报错,并发修改异常 //如何解决呢? //不要使用集合对象的删除方法 //在collection接口中存在删除指定元素的方法boolean remove //该方法只能从集合中删除元素,不能把迭代器中指定的元素进行删除 //正确 -使用iterator中的remove方法 } } while(it.hasNext()) { Object ele=it.next(); if("c".equals(ele)) { //list.remove(ele);//错误,不能使用集合对象的remove it.remove();//正确,保证了两个线程的同步 } System.out.println(ele); } //结论:直接使用foreach迭代数组和集合元素即可,简单。 //当需要遍迭代集合元素,边删除指定的元素时,此时只能使用迭代器 } }