第2节 集合(上)
因为已经有数据结构的基础,前面有关数据结构的知识就不单独整理了,直接上Java的集合类的知识。
一、类集(集合类)
1.1 集合概述
集合: 集合是java中提供的一种容器,可以用来存储多个数据。
集合和数组的区别:
-
数组的长度固定,集合的长度可变;
-
数组中存储的是同一个类型的元素,可以存储基本数据类型值,集合存储的都是对象,而且对象的类型可以不一致,在开发中一般当对象多的时候,使用集合来进行存储。
对象数组有哪些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组,但是这样毕竟太复杂了,所以在Java中为了方便用户操作各个数据结构,引入了类集的概念,有时候就可以把类集称为java对数据结构的实现 。
1.2 集合框架
JavaSE提供了满足各种需求的API,在使用这些API前,先了解其继承与接口操作架构,才能了解何时采用哪个类,以及类之间如何彼此合作,从而达到灵活运用。
集合按照其存储结构可以分为两大类,分别是单列集合java.util.Collection和双列集合java.util.Map。
类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。所有的类集操作的接口或类都在java.util包中。
Java类集的结构图:

二、Collection接口
2.1 Collection介绍
Collection接口是在整个Java类集中保存单值 的最大操作父接口(Map是保存双值/键值对的最大操作父接口),里面每次操作的时候都只能保存一个对象的数据,此接口定义在java.util包中。
有两个重要的子接口,分别是java.util.List和java.util.Set。其中,List的特点是元素有序、元素可重复;Set的特点是元素无序,而且不可重复。List接口的主要实现类有 java.util.ArrayList和 java.util.LinkedList , Set接口的主要实现类有 java.util.HashSet和java.util.TreeSet 。
此接口定义如下:
public interface Collection<E> extends Iterable<E>
注意这里使用了泛型,所以后面在使用具体实现类的时候都需要传入具体类型。
常用方法:

其中像add、iterator、size等方法很常用,此接口的全部子类或子接口就将全部继承以上的方法。
但是,在开发中不会直接使用 Collection接口,而比较常使用它的子接口 List和 Set。
三、List接口
3.1 List概述
习惯性地会将实现了List接口的对象称为List集合,在List集合中允许出现重复元素,所有的元素是以一种线性方式存储的,可以通过索引来访问集合中的指定元素。
List接口的特点:
-
它是一个元素存取有序 的集合,例如:存元素的顺序是11、22、33,那么集合中元素的存储就是按照11、22、33的顺序完成的。(与Set相对)
-
它是一个带有索引 的集合,通过索引可以精确地操作集合中的元素(与数组的索引是一个道理)。
-
集合中可以有重复 的元素,通过元素的equals方法,来比较是否为重复的元素。
3.2 常用方法
除了继承了Collection接口的方法外,还有如下常用的扩充方法:

了解了List接口之后,需要找到此接口的实现类,常用的实现类由如下三个:
ArrayList(95%) 、 Vector(4%) 、 LinkedList(1%)
3.3 ArrayList类
ArrayList是 List接口的子类, 此类的定义如下:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
ArrayList集合数据存储的结构是数组结构,元素增删慢,查找快,由于日常开发中使用的最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。
3.3.1 构造方法

这里涉及到一个容易被问的地方,初始容量是一开始就给了容量为10的空间吗?
这要从源码来解释:
ArrayList<String> all = new ArrayList<String>();
从创建ArrayList开始,进入空参数构造方法:
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
来查看DEFAULTCAPACITY_EMPTY_ELEMENTDATA的值:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
是一个空的对象数组,所以使用无参构造方法新创建的ArrayList对象,初始容量是0。
但是一旦add一个数据进入,会发生什么,进入add看一下:


不论是哪种方式的add,当元素存满了的时候,总要调用grow方法,下面来看grow方法:
private Object[] grow(int minCapacity) {return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}private Object[] grow() {return grow(size + 1);
}
无参grow方法会调用有参grow,并且传入当前容量+1作为扩容的最小容量,有参grow内部是调用了Arrays.copyOf函数,将扩容后的数组给了elementData,再看newCapacity(minCapacity):

新扩容的容量为原来的1.5倍,如果比给定的最小容量小或相等,判断elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA就是空数组,所以一个新创建的ArrayList必然会进入到这里,返回了DEFAULT_CAPACITY, minCapacity两个中的较大值,而新创建的minCapacity为1,这时来看一下DEFAULT_CAPACITY:

这才是初始容量为10的地方。
综上,使用无参构造方法新创建的ArrayList对象起始的elementData为空数组,只有在加入一个元素的时候才会初始化为10.
3.3.2 具体使用
使用格式:
ArrayList<Integer> data = new ArrayList<>();
例子:
package org.listdemo.arraylistdemo;
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo01 {public static void main(String[] args) {List<String> all = new ArrayList<String>(); // 实例化List对象, 并指定泛型类型all.add("hello "); // 增加内容, 此方法从Collection接口继承而来all.add(0, "LAMP ");// 增加内容, 此方法是List接口单独定义的all.add("world"); // 增加内容, 此方法从Collection接口继承而来System.out.println(all); // 打印all对象调用toString()方法}
}结果如下:
[LAMP , hello , world]
package org.listdemo.arraylistdemo;
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo02 {public static void main(String[] args) {List<String> all = new ArrayList<String>(); // 实例化List对象, 并指定泛型类型all.add("hello "); // 增加内容, 此方法从Collection接口继承而来all.add(0, "LAMP ");// 增加内容, 此方法是List接口单独定义的all.add("world"); // 增加内容, 此方法从Collection接口继承而来all.remove(1); // 根据索引删除内容, 此方法是List接口单独定义的all.remove("world");// 删除指定的对象System.out.print("集合中的内容是: ");for (int x = 0; x < all.size(); x++) { // size()方法从Collection接口继承而来System.out.print(all.get(x) + "、 "); // 此方法是List接口单独定义的}}
}结果如下:
集合中的内容是: LAMP 、
3.4 Vector类
3.4.1 概述与使用
与 ArrayList 一样, Vector 本身也属于 List 接口的子类, 此类的定义如下:
public class Vector<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
它的使用及操作与使用ArrayList本身基本没有什么区别:
package org.listdemo.vectordemo;
import java.util.List;
import java.util.Vector;
public class VectorDemo01 {public static void main(String[] args) {List<String> all = new Vector<String>(); // 实例化List对象, 并指定泛型类型all.add("hello "); // 增加内容, 此方法从Collection接口继承而来all.add(0, "LAMP ");// 增加内容, 此方法是List接口单独定义的all.add("world"); // 增加内容, 此方法从Collection接口继承而来all.remove(1); // 根据索引删除内容, 此方法是List接口单独定义的all.remove("world");// 删除指定的对象System.out.print("集合中的内容是: ");for (int x = 0; x < all.size(); x++) { // size()方法从Collection接口继承而来System.out.print(all.get(x) + "、 "); // 此方法是List接口单独定义的}}
}结果为:
集合中的内容是: LAMP 、
Vector 属于 Java 元老级的操作类, 是最早的提供了动态对象数组的操作类, 在 JDK 1.0 的时候就已经推出了此类的使用,只是后来在 JDK 1.2 之后引入了 Java 类集合框架。但是为了照顾很多已经习惯于使用 Vector 的用户,所以在JDK 1.2 之后将 Vector 类进行了升级了, 让其多实现了一个 List 接口, 这样才将这个类继续保留了下来。
3.4.2 Vector与ArrayList类的区别(重点)

3.5 LinkedList类
3.5.1 概述
LinkedList集合数据存储的结构是链表结构,方便元素添加、删除的集合。
LinkedList是一个双向链表。
3.5.2 使用
除了继承的List中的方法外,还有一些关于队列或栈操作的方法,以及涉及到首尾操作:


例子:
import java.util.LinkedList;
import java.util.Queue;
public class TestDemo {public static void main(String[] args) {Queue<String> queue = new LinkedList<String>();queue.add("A");queue.add("B");queue.add("C");int len=queue.size();//把queue的大小先取出来, 否则每循环一次, 移除一个元素, 就少一个元素, 那么queue.size()在变小, 就不能循环queue.size()次了。for (int x = 0; x <len; x++) {System.out.println(queue.poll());}System.out.println(queue);}
}结果如下:
A
B
C
[]
四、Iterator迭代器(接口)
4.1 Iterator概述
在程序开发中,经常需要遍历集合中的所有元素,针对这种需求,JDK专门提供了一个接口java.util.Iterator。也是Java集合中的一员,但是它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。
此接口定义如下:
public interface Iterator<E>
要想使用此接口, 则必须使用 Collection 接口, 在 Collection接口中规定了一个 iterator()方法, 可以用于为 Iterator 接口进行实例化操作,其实要到ArrayList类这一级别(因为是接口的实现类)中才对Iterator接口进行了实现。
4.2 方法
此接口定义了以下三个方法:

通过Collection接口为其进行实例化之后,一定要记住,Iterator中的操作指针是在第一条元素之上, 当调用next()方法的时候, 获取当前指针指向的值并向下移动, 使用 hasNext()可以检查序列中是否还有元素。

看个例子:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo01 {public static void main(String[] args) {Collection<String> all = new ArrayList<String>();all.add("A");all.add("B");all.add("C");all.add("D");all.add("E");Iterator<String> iter = all.iterator();while (iter.hasNext()) {// 判断是否有下一个元素String str = iter.next(); // 取出当前元素System.out.print(str + "、 ");}}
}结果如下:
A、 B、 C、 D、 E、
Iterator接口本身可以完成输出的功能, 但是此接口只能进行由前向后的单向输出。 如果要想进行双向输出, 则必须使用其子接口 —— ListIterator。
4.3 ListIterator(理解)
ListIterator是可以进行双向输出的迭代接口,是 Iterator的子接口, 此接口中定义了以下的操作方法:

看一下它的输出:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorDemo01 {public static void main(String[] args) {List<String> all = new ArrayList<String>();all.add("A");all.add("B");all.add("C");all.add("D");all.add("E");ListIterator<String> iter = all.listIterator();System.out.print("从前向后输出: ");while (iter.hasNext()) {System.out.print(iter.next() + "、 ");} System.out.print("\n从后向前输出: ");while (iter.hasPrevious()) {System.out.print(iter.previous() + "、 ");}}
}结果如下:
从前向后输出: A、 B、 C、 D、 E、
从后向前输出: E、 D、 C、 B、 A、
但是, 此处有一点需要注意的是, 如果要想进行由后向前的输出, 则首先必须先进行由前向后的输出。
而且它的add方法也是在指向的位置插入,不是在末尾插入,总之,此接口一般使用较少,理解为主。
五、增强for循环
forEach:增强for循环,最早出现在C#中,用于迭代数组 或 集合(只能是Collection下的集合)
语法: for(数据类型 变量名:集合或数组的名称) {}
作用: 代替传统for循环的复杂写法,进行了简化。
看一段代码即可,注意只能用于数组或Collection下的集合 :
package com.kaikeba.coreclasslibrary.set;
import java.util.ArrayList;
import java.util.Collection;public class foreach {public static void main(String[] args) {int[] arr = {6,5,4,2,1};//传统for循环遍历/*for(int i=0; i<arr.length; i++) {System.out.println(arr[i]);}*///forEach循环遍历for(int a:arr) {System.out.println(a);}System.out.println("------------------------");ArrayList<String> data = new ArrayList<>();data.add("锄禾日当午");data.add("汗滴禾下土");data.add("谁知盘中餐");data.add("粒粒皆辛苦");for(String s:data) {System.out.println(s);}}
}结果如下:
6
5
4
2
1
------------------------
锄禾日当午
汗滴禾下土
谁知盘中餐
粒粒皆辛苦