第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
------------------------
锄禾日当午
汗滴禾下土
谁知盘中餐
粒粒皆辛苦