集合的体系,Collection的子接口以及实现类之间的关系,Iterator迭代器和Iterable(十九)

423 阅读12分钟

集合

1 容器:用来装数据。

​ 变量也可以理解为容器,用来装一个数据,当再次给他赋值后,原来的值就被覆盖了,数组也是容器,用来装一组数据,长度也是固定。

我们需要更多种的容器:

​ (1)任意大小的容器,即容器能够不断的自动扩容

​ (2)有序的容器,就是把数据放到容器之后会自动排序

​ (3)不允许重复的容器,就是两个一样的元素放到容器中,只会留下一个

​ (4)存储的数据是<key,value>键值对

​ 。。。

Java中提前设计好了一些容器,它们各有特点。

2 容器都有相同的特点

(1)都可以添加对象

(2)删除对象

(3)查找对象

(4)修改对象

(5)遍历/迭代所有对象

....

3 集合的分类

​ 集合主要分为两大系列:Collection和Map,Collection表示一组对象,Map表示一组映射关系或键值对。

1563547137571.png

Collection

1 java.util包中的Collection<E>

​ 代表未知类型,是集合中位置的元素的类型,E是element的首字母。!

2 特点

​ (1)是Collection层次结构中的根接口。

​ (2)Collection表示一组对象,这些对象也被称为collection的元素。

​ (3)一些collection允许有重复的元素,另一些则不允许。一些collection是有序的,而另一些是无序的。

​ (4)jdk中不提供Collection接口的任何直接的实现,它提供更具体的子接口(如 Set 和 List)实现。

3 collection的方法

(1)添加

​ boolean add(E e) :添加一个元素

​ boolean addAll(Collection<? extends E> c):添加多个元素

​ this.当前集合 = this.当前集合 ∪ c集合

(2)查找

​ boolean contains(Object o):是否包含某个元素

​ boolean containsAll<Collection<?> c>:查找c集合中的元素是否在当前集合中包含,类似于判断c集合是否是当前集合的“子集”。

​ int size() : 元素个数

​ boolean isEmpty():当前集合是否是空集合

查找时,是依赖于元素的equals方法比较对象, 默认情况西,继承Object父类,equals方法实现的是比较对象的地址。一般需要重写equals才能实现比较对象之间的属性。

3 删除

​ void clear() :清空所有元素

​ boolean remove(Object o) :删除一个元素

​ boolean removeAll(Collection<?> c):删除c中的所有元素,如果c中的元素在this.集合中没有,那么会自动跳过,不会发生异常。

​ this.集合 = this.集合 - (c集合 ∩ this.集合)

​ boolean retainAll(Collection<?> c):删除两个集合不共有的元素。留下的是共同元素。

​ this.集合 = this.集合 ∩ c集合

4 集合的遍历方式:增强for循环,又称为foreach

for(元素的类型 元素名 : 集合容器对象名){
	
}

5 Collection的迭代

(1)Object[] toArray()

(2)foreach可以遍历(快捷键iter)

​ foreach遍历也支持数组类型。

(3)Iterator<E> iterator():获取遍历当前集合的迭代器对象

​ java.util.Iterator<E>是一个迭代器的接口。

为什么迭代器要有一个接口?

​ 因为集合的类型有很多,底层的实现也不同,例如:有的底层实现是数组,有的底层实现是链表...它们的底层实现的方式不同,那么遍历集合的方式也不同。如果说每一种集合因为自己的底层实现不同,都单独提供一种迭代方法的话,那么会导致程序员学习成本提高。所以它把所有的迭代器构成了统一的迭代器标准,定义了统一的Iterator<E>接口。

迭代器的方法:

① boolean hasNext():判断还有没有下一个元素可以迭代。

②E next():返回下一个元素。

③void remove():在遍历过程中删除元素。

6 集合元素的删除

(1)情况一:非常明确要删除的元素对象

集合对象.remove(元素);

(2)情况二:在遍历元素的过程中,判断元素是否满足xx条件,在进行删除,必须使用Iterator迭代器的remove()方法

注意:

使用foreach遍历集合的过程中,是不嫩那个调用集合的add,remove等影响元素个数的方法。否则容易出现并发修改合计的问题,可能会报java.util.ConcurrentModificationException

List集合

1 java.util.List是java.util.Collection的子接口,注意不要导错包

2 List系列集合特点

(1)线性,依次排列

(2)有序,位置依次排列

(3)可以根据索引/下标来或者对应的元素

(4)允许元素重复

3 List接口的实现类:

比较经典的有 ArrayList,Vector,LinkedList等

4 List的方法,除了Collection父接口的方法之外,还新增了如下方法

(1)添加

​ void add(int index, E element):添加一个元素到[index]位置

​ boolean addAll(int index, Collection<? extends E> c):添加一组元素到[index]位置

(2)查找

​ E get(int index):获取[index]位置的元素

​ int indexOf(Object o):从左往右找获取元素的下标

​ int lastIndexOf(Object o):从右往左找获取元素的下标

​ List subList(int fromIndex, int toIndex):截取当前集合[fromIndex, toIndex)范围的元素

(3)删除

​ E remove(int index) :删除[index]位置的元素

(4)修改

​ E set(int index, E element):把[index]位置的元素直接替换为element

5 List集合的遍历

​ 除了Collection的遍历方式(toArray, foreach, Iterator)它也支持外,还支持ListIterator ​

(1)ListIterator<E> listIterator()

(2)ListIterator<E> listIterator(int index)

ListIterator<E>是Iterator<E>接口的子接口。它比Iterator多了一些方法:

​ (1)int nextIndex()

​ (2)boolean hasPrevious()

​ (3)E previous()

​ (4)int previousIndex()

​ (5) void set(E e)

​ (6)void add(E e)

Set接口

1.jav.util.Set<E>接口是java.util.collection接口的子接口

2 特点

(1)不包含重复元素

(2)null也是有效元素之一,但是只能有一个null。

3 Set本身没有扩展Collection接口的方法

4 Set的实现类

HashSet(无序的,不保证元素的添加顺序);

TreeSet(有序的,是按照元素的大小顺序排列,需要集合中的对象对应的类必须实现内/外比较器之一);

LinkedHashSet(有序的,可以保证元素的添加顺序);

这是因为LinkedHashSet中比HashSet多维护了一个”链表“,它可以记录元素的添加顺序。

如果既要求数据不重复,有要求保证添加的顺序,那么可以考虑使用LinkedHashSet

凡是和对象大小有关的一定和java.lang.Comparable或者java.util.Comparator两个接口之一有关

Set的集合都是不允许元素重复的,那么它们都是依赖什么来判断元素是否重复的呢?

​ HashSet和LinkedHashSet是依赖元素的hashCode和equals方法来判断元素是否重复的。

​ TreeSet是依赖于java.lang.Comparable接口的compareTo方法或者是java.util.Comparator接口的CompareTo方法来判断的。

返回值为0的两个对象,就认为它们是重复对象。

Iterator接口

​ JDK1.5之后,让Collection接口继承了一个java.lang.Iterable接口。实现Iterable这个接口就允许对象成为 "foreach" 语句的目标。

问题:为什么Collection系列的集合,可以使用foreach循环进行遍历?

​ 是因为Collection集合继承了Iterable。

问题:java.lang.Iterable接口有什么?

​ 抽象方法:Iterator<T> iterator()

问题3:foreach是如何帮助我们进行集合的遍历的?

​ foreach循环还是用的集合的Iterator迭代器。

map接口

1 java.util.Map<K,V>,它不是Collection<E>的子接口,它和Collection是同一级别的。

2 特点:

(1)Map系列的集合是用来存储键值对<key,vakue>

(2)其中的key是不能重复的

(3)其中的value是允许重复的

(4)正常情况下,任意类型的对象都可以作为key和value,但是一般key类型选择String和Integer比较多,value类型,如果表示一个对象,就普通类型就可以,如果表示多个对象,可以使用Collection集合或者数组。

3 Map接口的API

(1)添加

V put(K key, V value) :添加一对键值对

void putAll(Map<? extends K,? extends V> m) :添加多对键值对

(2)查询

boolean containsKey(Object key) :是否包含某个key

boolean containsValue(Object value) :是否包含某个value

V get(Object key) :根据key获取value

boolean isEmpty() :集合是否为空

int size() :获取键值对的个数

(3)修改

修改value:再次put,就是修改

修改key:只能先移除原来的(key,value),然后修改完key之后重新添加(新key,value)

(4) 删除

V remove(Object key) :根据key,删除一对(key,value)

4、Map接口的常见实现类:

HashMap,Hashtable,TreeMap,LinkedHashMap,Properties等

5、遍历

因为Map接口并没有继承Iterable接口,所以不支持foreach遍历。

(1) Set<K> keySet():返回map中所有的key

(2)Collection<V> values() :返回map中所有的value

(3)Set<Map.Entry<K,V>> entrySet()

思考:keySet返回值类型是Set,而values返回值类型是Collection。

​ 因为所有的key是不能重复的,所以可以用Set集合表示。所有的value是可能重复的,肯定不是Set,具体是Collection哪个子类型,没有规定,用统一的父接口Collection。

6 Map接口的常用实现类和他们之间的关系

(1)分类

​ Map接口的常用实现类:HashMap、Hashtable、TreeMap、LinkedHashMap和Properties。其中HashMap是 Map 接口使用频率最高的实现类。

(2)相同点和不同点

​ 共同点:它们的key都不能重复。都能存储(key,value)键值对,键值对的类型是Map.Entry类型

​ 不同点: HashMap:哈希表。允许key和value是null。线程不安全的。

​ Hashtable:哈希表,比HashMap还要古老。不允许key和value是null。线程安全的。

​ LinkedHashMap:它是HashMap的子类,比HashMap多维护了key的顺序,添加顺序。而HashMap的put顺序与遍历顺序可能会不一致。

​ TreeMap:它是可以按照key的大小顺序排列的。要求元素实现java.lang.Comparable<T>接口,或者我们指定java.util.Comparator<T>

​ Properties:它是Hashtable的子类,它虽然是Map,但是从命名上看出来,弱化了它是Map的特性,强调了它是“属性”类型。

例如:系统属性 文件编码=UTF-8

​ JDK版本=1.8

​ 在使用Properties时,也尽量不要用put,get等方法,而是用Object setProperty(String key, String value),String getProperty(String key) 。另外,Properties它已经不是泛型类,要求key和value都是String。

​ 补充:java.text.Collator可以执行区分语言环境的 String 比较。使用此类可为自然语言文本构建搜索和排序例程。

问题:java.lang.Comparable<T>接口和java.util.Comparator<T>接口的选择问题?

​ A:只要涉及到对象的比较大小,排序问题,首先(优先)考虑java.lang.Comparable<T>接口。 如果选择java.lang.Comparable<T>接口,那么要搞清楚要比较/排序的对象的类型是什么。

​ 例如:如果是两个String字符串比较大小,那么就要求String实现java.lang.Comparable<T>接口。 例如:如果是两个Circle比较大小,那么就要求Circle实现java.lang.Comparable<T>接口。

​ B:如果要比较大小的对象类型没有实现java.lang.Comparable<T>接口,而又无法让它实现(例如:它是第三方写好的类),那么就改用java.util.Comparator<T>接口来辅助,但是声明一个类,或者用匿名内部类来实现java.util.Comparator<T>接口。

​ C:如果要比较大小的对象类型也实现java.lang.Comparable<T>接口,但是compareTo方法中的比较大小的规则,和我新的需求不一致。

​ 例如:Student类中实现java.lang.Comparable<T>接口,compareTo方法是按照学号排序的,但是我新的需求是想要按照成绩排序。此时不能直接修改compareTo方法,因为它可能在别的地方用了。此时就可以选择java.util.Comparator<T>接口来辅助

Map与Set的关系

​ Set的实现类:HashSet,LinkedHashSet,TreeSet ​

​ Map接口的常用实现类:HashMap、Hashtable、TreeMap、LinkedHashMap和Properties。

讨论:Set与Map的关系?

共同点:Set不允许元素重复, Map不允许key重复。

底层:就从Map中剥离出一些新的类型Set。

​ Set其实内部就是Map,只是把Map中key提取出来了。添加到Set中的元素是作为底层Map的key,而value值是 private static final Object PRESENT = new Object(); 每一个Set类型中的所有(key,value)都是同一个Object类型常量对象。

List接口的实现类

1 List接口的实现类:ArrayList,Vector,LinkedList,Stack等

2 List实现类之间的相同点和不同点

​ 它们共同点:都允许元素重复。

​ 不同点:

ArrayList:动态数组,线程不安全的。

​ 内部数组的初始化长度为10,从JDK1.7之后改为0。

​ 不够了扩容,默认扩容为1.5倍。

​ 仅支持foreach和Iterator迭代

Vector:动态数组,最古老的动态数组,线程安全的。

​ 内部数组的初始化长度为10.

​ 不够了扩容,默认扩容为2倍。

​ 支持foreach和Iterator迭代,还有一种已经不用的Enumeration迭代器。

Stack:它是Vector的子类,表示出来是栈的数据结构。(先进后出)

它是通过扩展几个特有的方法,来体现栈结构。

E peek() :获取/访问栈顶元素,但是不移走。

E pop() :拿走栈顶元素。

E push(E item) :把新元素压入栈,成为栈顶元素。

LinkedList:双向链表。它不仅实现了List接口,还实现Queue和Deque接口。

​ Queue:队列 先进先出

​ Deque:双端队列

​ LinkedList几乎可以实现所有线性数据结构。例如:可以作为链表、栈、队列、双端队列等 ​ LinkedList增加了很多方法,来适应程序员的各种需求。

3 LinkedList和动态数组的区别?

动态数组:一开始需要创建一个数组,不够了再扩容。

​ 扩容:要么扩太多,浪费空间,要么频繁扩容性能低下。

​ 优势:如果按照下标操作元素的话,效率极高。

​ 问题:在非末尾插入和删除元素需要移动其他元素。

LinkedList:一开始不用创建任何对象,来一个加一个就可以。

​ 不用扩容,不涉及空间浪费问题。

​ 优势:在插入和删除时不涉及移动元素,只要断开插入位置的前后元素,然后重新连接上新元素。