个人认为有必要对集合做一下总结。希望能够帮助自己的同时,帮助到更多人。本文内容较长,如有错误,还请指出,万分感激。
1.Java 集合类的基本概念
在编程中,常常需要集中存放多个数据。从传统意义上讲,数组是我们的一个很好的选择,前提是我们事先已经明确知道我们将要保存的对象的数量。一旦在数组初始化时指定了这个数组长度,这个数组长度就是不可变的,如果我们需要保存一个可以动态增长的数据(在编译时无法确定具体的数量),java的集合类就是一个很好的设计方案了。
集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。
数组可以存放基本数据类型,存放引用数据类型。但是集合只能存放引用数据类型。但是我们学习过包装类,所以可以自动装箱,把基本数据类型直接转化为包装类存入集合中。
Java容器类类库的用途是”保存对象”,并将其划分为两个不同的概念:Collection 和 Map。Collection和Map的区别在于容器中每个位置保存的元素个数:
1) Collection 每个位置只能保存一个元素(对象)
2) Map 保存的是”键值对”,就像一个小型数据库。我们可以通过”键”找到该键对应的”值”
先把这两个方面的结构图放下面,再详细来说一下这两个方面。
2.Collection 接口
Collection 是一个接口,不能创建对象,只能创建他的实现类。
Collection 中有很多方法,在这儿只看其中典型的方法:
- 增加:add(E e) addAll(Collection<? extends E> c) ;
- 删除:clear() remove(Object o) removeAll(Collection<?> c);
- 查看:contains(Object o) containsAll(Collection<?> c) isEmpty()
- iterator() retainAll(Collection<?> c) size()
2.1 利用 Collection 接口创建 ArrayList 类:
|
|
注意 :例如 col.add(12); 放入的不是 int 类型的12,而是自动装箱,等效于 col.add(new Integer(12));
|
|
这段代码中的addAll() 方法相当将 col2 里面的全部内容放到了 col 里面。
|
|
retainAll() 方法(标@):对 col 取 col 与 col2 的交集。(注意:是只对 col 操作,不涉及 col2 !!!)
2.2 利用 List 接口创建 ArrayList 类:
下面只讲几个比较常用的方法,其他的很好理解,不再赘述,还请谅解。
|
|
add(int index, E element) 方法:相当于在索引为 index 的位置插入元素 element,注意是插入,不是取代!
|
|
li 中虽然有元素3,但是 remove 方法还是把 3 当作索引来看。
|
|
set(int index, E element)方法:将索引为 index 的位置上的内容替换为 element。
System.out.println(li.indexOf(12));//获取第一个元素12对应的索引、下标
indexOf(Object o)方法:得到索引为 o 的位置上的内容。
2.3 List 集合遍历
说明:与索引有关的遍历都是在 List 接口下的。
List 集合遍历有三种方式
第一种:foreach(代码接上面)
|
|
第二种:迭代器
介绍一下迭代器接口:Interface Iterable
迭代器接口,这是Collection类的父接口。实现这个Iterable接口的对象允许使用foreach进行遍历,也就是说,所有的Collection集合对象都具有”foreach可遍历性”。这个Iterable接口只有一个方法: iterator()。它返回一个代表当前集合对象的泛型
|
|
第三种:普通 for 循环
|
|
补充一个知识点:在 ArrayList 类中放入引用数据类型。代码如下(引用数据类型自己可以随便建,不要在意我的代码中的引用数据类型哈):
|
|
3.泛型
简单来说,泛型就是规定你这个集合中只能存入这个类型的数据。
泛型的格式:<> ,那么为啥用<>呢? — 因为没办法。。。(凡是成对的符号都被占用了,我能怎么办,我也很绝望)
代码如下:
ArrayList<String> al=new ArrayList<String>();
加上
3.1 泛型类
|
|
创建泛型对象:
|
|
3.2 泛型方法
|
|
3.3 泛型接口
|
|
3.4 泛型的高级应用
3.4.1 泛型的上限
|
|
3.4.1 泛型的上限
|
|
4. LinkedList
implements List
4.1 iterator()方法,Iterator接口,Iterable接口区别
上文说过,实现Iterable接口的对象允许使用foreach进行遍历。
4.2 ConcurrentModificationException
ConcurrentModificationException 称为并发修改异常。
- 异常产生的原因
迭代器是依赖于集合而存在的,在判断成功后,集合的中新添加了元素,而迭代器却不知道,所以就报错了,这个错叫并发修改异常。
简单描述就是:迭代器遍历元素的时候,通过集合是不能修改元素的。
解决方法
(1)迭代器迭代元素,迭代器修改元素;
(2)集合遍历元素,集合修改元素(普通for)。在这里主要讲一下第一种解决方法:
|
|
5. 子接口 set
5.1 HashSet
5.1.1 在集合中存放 Integer 类型的数据:
|
|
5.1.2 在集合中存放 String 类型的数据:
|
|
5.1.3 在集合中存放引用数据类型的数据:
代码中的引用数据类型自己随便定义,只要正确就行。
|
|
在这段代码中大家可以发现重复的数据仍然放进去了。这是为啥呢,不是说 set 是唯一的吗?
那么就要了解 HashSet 底层原理了:
- 如果看Integer 和 String 的源码(看源码:按住Ctrl,同时鼠标点击Integer 或者 String),就会发现它们都含有 hashCode() 和 equals() 方法,HashSet 会调用这两个,所以不论是放入 Integer 类型,还是放入 String 类型,结果都会是唯一的。但是放入的引用数据类型里却没有这两种方法。
- 解决方法:引用数据类型里重写 hashCode 和 equals 方法。
5.2 LinkedHashSet 类
|
|
5.3 TreeSet
5.3.1 放入Integer类型数据:
|
|
- TreeSet 原理
5.3.2 放入String类型数据:
|
|
5.3.3 放入自定义 引用数据类型:
原因:
实际上,Integer,String的底层全部都实现了Comparable接口。实现了compareTo方法。这个方法返回int类型的数据。
解决:
实现Comparable接口,重写compareTo方法。
方式1:内部比较器:
|
|
方式2:外部比较器:
|
|
那么这哪种比较器好?? —- 外部
因为这样耦合性低,代码扩展性好,你要加比较器,或者删比较器,对其余的代码影响最小。
6. Map 接口
废话不说,直接上图。
- Map 的特点:Map用于保存具有”映射关系”的数据,因此Map集合里保存着两组值,一组值用于保存Map里的key,另外一组值用于保存Map里的value。key和value都可以是任何引用类型的数据。Map的key不允许重复。
- Map的这些实现类和子接口中key集的存储形式和Set集合完全相同(即key不能重复)。
- Map的这些实现类和子接口中value集的存储形式和List非常类似(即value可以重复、根据索引来查找)。
6.1 HashMap 类
|
|
标 1 处,put(K key,V value) 方法:(1)将指定的值与此映射中的指定键关联(可选操作)。如果此映射以前包含一个该键的映射关系,则用指定值替换旧值。(2)以前与 key 关联的值,如果没有针对 key 的映射关系,则返回 null。
标 2 处,entrySet()方法:此映射中包含的映射关系的 set 视图。
6.2 TreeMap
之前的集合都是没有按照从小到大的这种有序进行输出的。
现在我想按照有序输出,用TreeMap。
这里还是主要讲述当 Key 值为引用数据类型时:
|
|
此处比较的是 age 属性,可以看出来,TreeMap 是唯一,无序的(没有按照输入输出的顺序,按照大小顺序来输出的)
总结:
- Collection 接口下的所有实现类,都可以用迭代器进行遍历。
- HashSet 类是唯一,无序,没有按照输入顺序进行输出;TreeSet 类是唯一,有序(按照从小到大的顺序) 无序(没有按照输入顺序进行输出);LinkedHashSet 类是唯一,有序。
- ArrayList 类是不唯一,有序,按照输入顺序进行输出;LinkedList 类是不唯一,有序,按照输入顺序进行输出。
- HashMap 类是按照key值唯一、无序的;LinkedHashMap 类 是唯一,有序,按照输入输出顺序进行输出;TreeMap 类是唯一,有序(按照从小到大)。