1.集合框架概述 参考博客:https://www.acwing.com/blog/content/4414/
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
Java集合类库将接口与实现分离。
Java标准库自带的java.util
包提供了集合类。
这两个都是接口,不是类。
Collection的接口继承树:
Map的接口继承树:
List接口:动态数组,存储有序可重复数据;Set接口:集合,存储无序不可重复数据;Queue接口:队列。
Map接口:类似函数,存储key-value的键值对,一个key不能对应多个value。
2.Collection接口方法 推荐一个Java全面教程:https://blog.csdn.net/qq_38490457/article/details/108281646
Collection接口的13种常用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Collection coll = new ArrayList(); coll.add("asd" ); coll.add(123 ); coll.add(new Date()); System.out.println(coll.size()); Collection coll1 = new ArrayList(); coll1.add(12 ); coll1.add("asdf" ); coll.addAll(coll1); System.out.println(coll); coll1.clear(); System.out.println(coll1.isEmpty());
向Collection接口的实现类的对象中添加数据obj时,要求重写equals()
,因为contains方法会调用equals。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 Collection coll = new ArrayList(); coll.add(123 ); coll.add(new String("Tom" )); coll.add(false ); coll.add(new Person("Jerry" ,20 )); boolean contains = coll.contains(123 );System.out.println(contains); System.out.println(coll.contains(new String("Tom" ))); System.out.println(coll.contains(new Person("Jerry" ,20 ))); Collection coll1 = new ArrayList(); coll1.add(123 ); System.out.println(coll.containsAll(coll1)); coll.remove(123 ); coll.remove(new Person("Jerry" ,20 )); System.out.println(coll); Collection coll2 = Arrays.asList(false ,new String("asd" )); coll.removeAll(coll2); System.out.println(coll); coll.add(1 ); coll.add(new String("ad" )); Collection coll3 = Arrays.asList(1 ,new String("ad" )); coll.retainAll(coll3); System.out.println(coll); Collection coll4 = Arrays.asList(new String("asd" ),false ); System.out.println(coll2.equals(coll4)); Collection coll = Arrays.asList(false ,new String("asd" ),123 ); System.out.println(coll.hashCode()); Object[] arr = coll.toArray(); for (int i = 0 ;i < arr.length;i++){ System.out.print(arr[i]+" " ); } System.out.println(); List<String> list = Arrays.asList(new String[]{"aa" ,"bb" ,"cc" }); System.out.println(list); List arr1 = Arrays.asList(new int []{12 ,23 ,34 }); System.out.println(arr1.size()); List arr2 = Arrays.asList(new Integer[]{12 ,23 ,34 }); System.out.println(arr2.size());
还有一个iterator方法放在下个知识点,它的作用是返回迭代器对象,用于集合遍历。
3.使用Iterator接口遍历集合
迭代器的执行原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Collection coll = new ArrayList(); coll.add("asd" ); coll.add(123 ); coll.add(new String("fsdf" )); Iterator iterator = coll.iterator(); System.out.println(iterator.next()); while (iterator.hasNext()){ System.out.println(iterator.next()); } Iterator ite = coll.iterator(); while (ite.hasNext()){ Object obj = ite.next(); if ("fsdf" .equals(obj)){ ite.remove(); } }
4.使用for-each遍历集合 JDK5.0的新特性,增强for循环
可用于迭代访问Collection和数组。
和C++中的for-each用法类似。
foreach适用于循环次数未知,只是进行集合或数组遍历,for则在较复杂的循环 中效率更高。
foreach不能 对数组或集合进行修改 (添加删除操作),如果想要修改就要用for循环 。
1 2 3 4 5 6 7 8 9 10 11 12 13 Collection coll = new ArrayList(); coll.add("asd" ); coll.add(123 ); coll.add(new String("fsdf" )); for (Object obj:coll){ System.out.println(obj); } int [] arr = new int []{1 ,2 ,3 ,4 };for (int i : arr){ System.out.print(i+" " ); }
5.Collection子接口之一:List接口 鉴于 Java 中数组用来存储数据的局限性,我们通常使用 List 替代数组。
List 集合类中元素有序、且可重复 ,集合中的每个元素都有其对应的顺序索引。
List 容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
我们考察List<E>
接口,可以看到几个主要的接口方法:
在末尾添加一个元素:boolean add(E e)
在指定索引添加一个元素:boolean add(int index, E e)
删除指定索引的元素:E remove(int index)
删除某个元素:boolean remove(Object e)
获取指定索引的元素:E get(int index)
获取链表大小(包含元素的个数):int size()
List 接口的实现类常用的有: ArrayList 、 LinkedList 和 Vector 。
三者的异同?
相同点:都用于存储有序、可重复元素 。
不同点:
1 2 3 4 |----Collection接口:单列集合,用来存储一个一个的对象 |----List接口:存储序的、可重复的数据。 -->“动态”数组,替换原的数组 |----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储 |----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储 |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
ArrayList是最常用的,但是线程不安全。
一些源码分析
ArrayList的源码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ArrayList list = new ArrayList(); list.add(123 ); ... list.add(11 ); ArrayList list = new ArrayList(); list.add(123 ); ...
LinkedList的源码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 LinkedList list = new LinkedList(); list.add(123 ); private static class Node <E > { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this .item = element; this .next = next; this .prev = prev; } }
Vector的源码分析:
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来
的数组长度的2倍。
List接口的常见方法:
注意:subList返回子集合,不会对原集合造成影响。
1 2 3 4 5 6 7 8 9 10 最常用的几种方法 增:add(Object obj) 删:remove(int index) / remove(Object obj)这两个方法是重载的 改:set(int index, Object ele) 查:get(int index) 插:add(int index, Object ele) 长度:size() 遍历:① Iterator迭代器方式 ② 增强for 循环 ③ 普通的循环
除了使用ArrayList
和LinkedList
,我们还可以通过List
接口提供的of()
方法,根据给定元素快速创建List
:
1 List<Integer> list = List.of(1 , 2 , 5 );
但是List.of()
方法不接受null
值,如果传入null
,会抛出NullPointerException
异常。
遍历List
和数组类型,我们要遍历一个List
,完全可以用for
循环根据索引配合get(int)
方法遍历。
但这种方式并不推荐,一是代码复杂,二是因为get(int)
方法只有ArrayList
的实现是高效的,换成LinkedList
后,索引越大,访问速度越慢。
所以我们要始终坚持使用迭代器Iterator
来访问List
。 Iterator
本身也是一个对象,但它是由List
的实例调用iterator()
方法的时候创建的。Iterator
对象知道如何遍历一个List
,并且不同的List
类型,返回的Iterator
对象实现也是不同的,但总是具有最高的访问效率。
实际上,只要实现了Iterable
接口的集合类都可以直接用for each
循环来遍历,Java编译器本身并不知道如何遍历集合对象,但它会自动把for each
循环变成Iterator
的调用,原因就在于Iterable
接口定义了一个Iterator<E> iterator()
方法,强迫集合类必须返回一个Iterator
实例。
List和Array转换
把List
变为Array
有三种方法,
第一种是调用toArray()
方法直接返回一个Object[]
数组。(前面有例子)
这种方法会丢失类型信息,所以实际应用很少。
第二种方式是给toArray(T[])
传入一个类型相同的Array
,List
内部自动把元素复制到传入的Array
中:
1 2 3 4 5 6 7 8 9 public class Main { public static void main (String[] args) { List<Integer> list = List.of(12 , 34 , 56 ); Integer[] array = list.toArray(new Integer[3 ]); for (Integer n : array) { System.out.println(n); } } }
注意到这个toArray(T[])
方法的泛型参数<T>
并不是List
接口定义的泛型参数<E>
,所以,我们实际上可以传入其他类型的数组,例如我们传入Number
类型的数组,返回的仍然是Number
类型:
1 2 3 4 5 6 7 8 9 public class Main { public static void main (String[] args) { List<Integer> list = List.of(12 , 34 , 56 ); Number[] array = list.toArray(new Number[3 ]); for (Number n : array) { System.out.println(n); } } }
如果我们传入的数组大小和List
实际的元素个数不一致怎么办?
如果传入的数组不够大,那么List
内部会创建一个新的刚好够大的数组,填充后返回;如果传入的数组比List
元素还要多,那么填充完元素后,剩下的数组元素一律填充null
。
实际上,最常用的是传入一个“恰好”大小的数组:
1 Integer[] array = list.toArray(new Integer[list.size()]);