前言:在Java之中提供了一套非常完善的动态对象数组的实现机制:Java类集
下面详细介绍Java的集合框架
一、Collection接口
Collection接口是单值集合操作的最大父接口,每一次只向集合之中保存一个对象。
此类定义如下:
public interface Collection<E> extends Iterable <E>
Collection是属于Iterable接口的子接口,Iterable是在JDK1.5之后提供的一个可迭代的标记性接口,而在Collection接口下面又分为若干个子接口:List(允许重复)、Set(不允许重复)、SortedSet(不允许重复且排序)、Queue(队列,先进先出)。
Collection作为最大的单值父接口基本上在现代的开发之中已经很少使用,最早的Collection被大量的直接应用在了EJB之中,但是后来由于设计的更加严谨性,所以在现代的开发之中都大量的使用了Collection子接口。
在Collection接口中定义有如下的一些方法。
boolean add(E e)
确保此 collection 包含指定的元素(可选操作)。 (向集合中追加数据)
boolean addAll(Collection<? extends E> c)
将指定 collection 中的所有元素都添加到此 collection 中(可选操作)。 (向集合中追加一组数据)
void clear()
移除此 collection 中的所有元素(可选操作)。 (清空集合)
boolean contains(Object o)
如果此 collection 包含指定的元素,则返回 true。 (数据查询,需要equals()方法支持)
Iterator<E> iterator()
返回在此 collection 的元素上进行迭代的迭代器。 (获取Iterator接口实例)
boolean remove(Object o)
从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。 (数据删除,需要equals()方法支持)
(如果相同元素有多个,则只会移除从头开始遇到的第一个指定元素)
int size()
返回此 collection 中的元素数。 (集合中数据保存个数)
如果此 collection 包含的元素大于 Integer.MAX_VALUE,则返回 Integer.MAX_VALUE。大概是21亿。准确值为 2147483647
Object[] toArray()
返回包含此 collection 中所有元素的数组。 (将集合以对象数组形式返回)
在以上所给出的方法里面最为重要的两个方法:add()、iterator()。
最需要注意的就是**contains()与remove()**因为都需要对象比较的支持。
二、List接口
Collection接口有许多的子接口,但是这些接口里面使用最多的就是List接口。
此接口定义如下:
public interface List <E> extends Collection <E>
在List子接口里面对Collection的接口进行了大量的扩充操作。
void add(int index, E element)
在列表的指定位置插入指定元素(可选操作)。 (在指定的索引位置上添加元素)
E get(int index)
返回列表中指定位置的元素。 (获取指定索引位置上的数据)
int indexOf(Object o)
返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。 (查找指定对象的索引位置)
ListIterator<E> listIterator()
返回此列表元素的列表迭代器(按适当顺序)。 (获取ListIterator接口实例)
E set(int index, E element)
用指定元素替换列表中指定位置的元素(可选操作)。 (修改指定索引位置的数据)
public static <E> List <E> of(E... elements) (通过指定内容创建一个List集合)
default void sort(Comparator<? super E> c) (实现List集合排序)
重要方法为get() set()
在JDK1.9之后,List接口做了改进,追加了一个of()方法,可以实现内容的添加
例:创建List集合
public class Demo01 {
public static void main(String[] args) {
List <String> all = List.of("Hello","you","guys","Hello");
System.out.println(all);
}
}
程序执行结果:[Hello, you, guys, Hello]
通过此时的操作可以发现在List集合中所保存的数据可以存放有重复的数据内容但是需要记住的是,利用of()方法所创建的List集合只是一个只读的集合,不能够对数据进行修改处理,一旦使用了remove()、add()、set()等方法进行集合修改的话会直接抛出异常:
java.lang.UnsupportedOperationException(不支持的操作异常),此方法属于未实现的操作。
public class Demo01 {
public static void main(String[] args) {
List <String> all = List.of("Hello","you","guys","Hello");
// all.add("aaa"); //不可用
// System.out.println(all.contains("Hello")); //可用
// all.remove("Hello"); //不可用
// all.set(0,"abc"); //不可用
System.out.println(all);
}
}
正常的设计来讲,如果要想使用List接口,往往都需要利用它的子类来进行接口对象的实例化处理,在List中有三个常用子类: ArrayList、LinkedList、Vector,这三个类的继承结构如下。
ArrayList子类 (使用率最高)
观察此类继承结构
public class ArrayList <E> extends AbstractList <E> implements List<E>, RandomAccess, Cloneable, Serializable
此时ArrayList类继承了AbstractList,而AbstractList类又是List接口的的子类,同时ArrayList又重复实现了一个List接口,这样设计的目的只是对结构的继承关系做出明确的标记。
由于所有的子类的对象都可以利用向上转型的关系为父接口进行实例化处理操作,所以对于ArrayList类而言,只关心它的构造方法,以及具体的功能实现形式,而所有的使用上都以List接口的方法为主。
ArrayList类的构造方法如下:
ArrayList()
构造一个初始容量为 10 的空列表。(使用默认容量)
ArrayList(int initialCapacity)
构造一个具有指定初始容量的空列表。(设置一个初始化容量)
例:通过ArrayList类实例化List接口
public class Demo01 {
public static void main(String[] args) {
List <String> all = new ArrayList<>();
System.out.printf("【集合未保存数据前的状态】集合长度:%s、是否为空:%s\n",all.size(),all.isEmpty());
all.add("Hello");
all.add("you");
all.add("guys");
all.remove("guys");
System.out.printf("【集合未保存数据前的状态】集合长度:%s、是否为空:%s\n",all.size(),all.isEmpty());
System.out.println(all);
}
}
程序执行结果:
【集合未保存数据前的状态】集合长度:0、是否为空:true
【集合未保存数据前的状态】集合长度:2、是否为空:false
[Hello, you]
实际上ArrayList类的使用并不复杂,但是复杂的因素在于,需要清楚ArrayList类的实现原理,那么就必须观察源代码。
1、无参构造方法:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
通过分析可以针对于ArrayList得出如下的结论:
·ArrayList本身是基于数组的形式实现的集合操作,数组的最大优势在于根据索引访问的时候时间复杂度为"O(1)";
2、有参构造:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) { //容量是否大于0,如果大于直接开辟
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) { //容量如果为0,则使用默认的空元素数组
this.elementData = EMPTY_ELEMENTDATA;
} else { //如果容量小于0,肯定无法进行数组的开辟,那么就抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
3、数据增加
public boolean add(E e) {
modCount++; //计数的操作 ,线程同步保护
add(e, elementData, size);
return true;
}
private void add(E e, Object[] elementData, int s) { //s为当前个数
if (s == elementData.length) //数组已经存储满了
elementData = grow(); //grow()进行数组空间的增长,会产生垃圾
elementData[s] = e;
size = s + 1; //个数的统计
}
private Object[] grow() { //返回一个新的对象数组
return grow(size + 1); //
}
private Object[] grow(int minCapacity) { //实现了数组的增长
return elementData = Arrays.copyOf(elementData, //数组拷贝
newCapacity(minCapacity));
}
private int newCapacity(int minCapacity) { // overflow-conscious code
int oldCapacity = elementData.length; //获取已经存在的数组长度
//集合长度扩充()——旧长度+新长度 不是成倍扩容 扩大%50 原长度右移一位等于原长度除以2
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
·当ArrayList中保存的容量不足时,每一次扩充“50%”;
所以在每一次使用ArrayList的时候一定要考虑好长度的存储问题。
如果你保存的数据的长度在10或以内使用无参构造即可。
自定义类对象存储
在之前实现的全部都是String类对象的存储,但是在类集里面经常可以存放自定义类的对象,但是如果要想让所有的自定义类对象的集合可以正常操作,一定要在类中覆写equals()方法。
例:实现自定义类型的存储
import java.util.ArrayList;
import java.util.List;
class Girl {
private String name;
private int age;
public Girl(String name, int age) {
this.name = name;
this.age = age;
}
public Girl() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "姓名:"+this.name+"、年龄:"+this.age+"\n";
}
@Override
public boolean equals(Object obj) { // 重写equals方法
if(this==obj){
return true;
}
if(obj==null){
return false;
}
if(!(obj instanceof Girl)){
return false;
}
Girl girl = (Girl)obj;
return this.name.equals(girl.name)&&this.age==girl.age;
}
}
public class Demo01 {
public static void main(String[] args) {
List <Girl> list = new ArrayList<>(); //默认容量为10
list.add(new Girl("田小七",25));
list.add(new Girl("梁静茹",30));
list.add(new Girl("金沙",28));
System.out.println(list.contains(new Girl("金沙",28)));
list.remove(new Girl("金沙",28));
System.out.println(list);
}
}
程序执行结果:
true
[姓名:田小七、年龄:25
, 姓名:梁静茹、年龄:30
]
在以后进行项目开发的过程里面,List集合经常会用于进行数据的存储,对于需要动态查询或者删除的自定义类对象就一定要提供有equals()方法并进行覆写。
但是如果此时只是进行存储而不进行contains()和remove()操作的时候,就不需要equals()方法了。
LinkedList子类
LinkedList实际上通过名称就可以发现其是基于链表的方案实现的集合存储,首先来观察此类的继承结构:
public class LinkedList<E> extends AbstractSequentialList <E> implements List<E>, Deque<E>, Cloneable, Serializable
deque 是“double ended queue(双端队列)”的缩写
在LinkedList类里面提供有节点处理类,这个类的定义如下:
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;
}
}
既然LinkedList中的内容全部基于链表的形式存储,所以无参构造实际上什么都不会去做,每一次在进行新数据增加的时候只需要创建节点,并设计好节点之间的关系即可。
例:通过LinkedList实现内容存储
public class Demo01 {
public static void main(String[] args) {
List<String> list = new LinkedList<>();
list.add("Hello");
list.add("you");
list.add("guys");
System.out.println(list);
}
}
程序执行结果:
[Hello, you, guys]
任何情况只要是链表操作就都会存在有“O(n)”性能问题。
Vector子类
继承结构:
public class Vector<E> extends AbstractList <E> implements List<E>, RandomAccess, Cloneable, Serializable
继承图:
Vector类是最早的实现集合处理的操作类,其是在JDK1.0的时候提供的概念,表示的“向量”,但是到了JDK1.2之后出现了集合框架 ,集合的操作有了更加明确的规范化,所以让Vector多实现了一个List接口,这样才得以保存,保留Vector的目的更多情况是出于“情怀”,因为太多的系统类和用户编写代码之中使用到了Vector。
除了一个类名称不同之外,实际上整体的定义和继承结构和ArrayList是非常相似的。
Vector和ArrayList虽然继承结构上相同,但是两者最大的区别在于,Vector类中的方法采用的是synchronized同步处理机制,而ArrayList并未采用同步。
例:Vector使用
public class Demo01 {
public static void main(String[] args) {
List<String> list = new Vector<>();
list.add("Hello");
list.add("you");
list.add("guys");
System.out.println(list);
}
}
程序执行结果:
[Hello, you, guys]
由于一切的子类都向List接口进行实例化处理,所以使用那一个子类最终的效果都是相同的,只不过每一个子类有自己实现的操作机制,这一点是不同的。
在开发中如果使用肯定是List和ArrayList搭配最为常见,但是如果问到原理的时候要能够将3个子类的实现原理详细说明。
面试题:请解释ArrayList、LinkedList区别?
·ArrayList是基于数组实现的集合类,而LinkedList是基于链表实现的集合类;。
·ArrayList 类在根据索引查询的时候时间复杂度为“O(1)”,而LinkedList时间复杂度为“O(n)“
面试题:请解释ArrayList与Vector区别?
·ArrayList是在JDK1.2提出集合框架的时候定义的,其内部的方法未使用同步处理,属于非线程安全的操作。
·Vector是在JDK1.0的时候提供的类,在JDK1.2之后增加到集合框架之中,所有方法使用synchronize同步属于线程安全的操作;
·ArrayList和Vector一样都是基于数组实现的动态存储。