简介:
List是Collection的子接口,其最大的特点是允许保存重复的元素数据。
定义:
public interface List<E> extends Collection<E>
需要清楚的是List子接口对于Collection的接口进行了方法的扩充。
- 获取指定索引的数据:E get(int index);
- 修改指定索引对象数据:E set(int index, E element);
- 返回ListIterator接口对象:ListIterator<E> listIterator();
从JDK1.9开始List接口中追加有一些static方法,方便用户的使用:
import java.util.List;
public class MAIN {
public static void main(String[] args) {
List<String> all = List.of("| WD |","| WD |","| WD |","| WD |","| WD |"); // 返回一个列表
System.out.println(all);
Object[] array = all.toArray();
for (Object o : array) {
System.out.println(o);
}
}
}
输出结果:
上面代码使用的方法不是List的传统用法,是在新版本之后添加的新功能。
ArrayList类:
ArrayList类是List接口中使用最多的子类,但是在使用这个子类的时候也是有前提要求的,对此展开理解;
定义:
public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
构造方法:
通过类名可以发现,ArrayList应该封装的是一个数组。
无参构造:public ArrayList()
|
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
|
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
|
|
有参构造:public ArrayList(int initialCapacity) |
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) { // 初始长度存在则创建对象数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) { // 没有初始长度
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
|
private static final Object[] EMPTY_ELEMENTDATA = {};
|
|
transient Object[] elementData; // 不可序列化对象数组
|
|
通过有参构造方法可以发现,在ArrayList里面所包含的就是一个对象数组。如果现在进行数据的追加可以发现ArrayList集合里面保存的对象数组的长度不够的时候,会进行新的数组开辟并将旧数组的内容拷贝到新数组之中,而后数组的开辟操作时下面是计算新数组长度的方法 | |
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity, // 新的数组大小
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
public static int newLength(int oldLength, int minGrowth, int prefGrowth) { // 计算新数组长度
int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
return prefLength;
} else {
return hugeLength(oldLength, minGrowth);
}
}
|
如果实例化ArrayList类对象的时候没有传递初始化长度,则默认情况下会使用一个空数组,但是在进行数据增加的时候发现创建的数组容量不够了,则会判断当前容量与默认容量的大小,使用较大容量的数值开辟新的数组:
JDK1.9之后 | ArrayList默认的构造只会使用默认的空数组,使用的时候才会开辟数组,默认的开辟长度为10; |
JDK1.9之前 |
ArrayList默认的构造会默认开辟长度为10的数组。 |
当ArrayList之中保存的容量不足的时候会采用成倍的方式进行增长,原始长度为10,下次增长后就是20,一次类推。
在使用ArrayList的时候,一定要估计数据量会有多少,如果超过了10个,最好是使用有参构造进行创建,以免产生大量的垃圾数组空间。
import java.util.ArrayList;
import java.util.List;
public class MAIN {
public static void main(String[] args) {
List<String> list = new ArrayList<>(); // 实例化List父接口
list.add("www");
list.add("www");
list.add(".baidu.");
list.add("com");
System.out.println(list);
}
}
输出结果:
从上面的代码和结果可以发现List存储的特征:
- 保存的顺序就是其存储的顺序
- List集合中可以保存重复的数据
在JDK1.8之后为了方便输出处理,Iterable接口之中定义有forEach()方法:
输出支持(不是标准输出):
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
使用forEach()方法:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class MAIN {
public static void main(String[] args) {
List<String> list = new ArrayList<>(); // 实例化List父接口
list.add("www");
list.add("www");
list.add(".baidu.");
list.add("com");
list.forEach((str)->{
System.out.println(str + "/");
});
}
}
输出结果:
自定义类对象保存:
import java.util.ArrayList;
import java.util.List;
class Person{
private String name ;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class MAIN {
List<Person> list = new ArrayList<>(); // 实例化List父接口
list.add(new Person("张三",12));
list.add(new Person("李四",15));
list.add(new Person("王五",16));
System.out.println(list.contains(new Person("王五",16)));
System.out.println(list.remove(new Person("王五",16)));
System.out.println(list);
}
}
输出结果:
我们可以发现,此时无法删除或者查找到“王五”这个对象,这里就需要提醒一点,在使用remove()或contains()方法之前,一定要确保对象类之中重写了equals()方法;
当我们重写了equals()方法后输出结果:
此时就能发现“王五”这个对象可以被查找并删除了。
List子类:LinkedList类
LinkedList操作实现:
import java.util.LinkedList;
import java.util.List;
public class MAIN {
public static void main(String[] args) {
List<String> list = new LinkedList<>(); // 实例化List父接口
list.add("www");
list.add("www");
list.add(".baidu.");
list.add("com");
list.remove("www"); // 删除元素
System.out.println("集合是否为空:" + list.isEmpty() + " \t集合元素个数 : " + list.size()); // 判断为空和元素个数
list.forEach(System.out::print);
}
}
输出结果:
此时程序的功能和ArrayList类是一样的,但是内部的实现机制是完全不同的;
在LinkedList类的构造方法里面并没有像ArrayList类那样提供有初始化大小的方法而只是提供有无参的构造方法:
/**
* Constructs an empty list.
*/
public LinkedList() {
}
下面观察add()方法的实现:
add()方法 |
public boolean add(E e) {
linkLast(e);
return true;
}
在自定义链表的时候做了一个判断:传入的数据为null则不进行保存; 但是在LinkedList里面并没有这样的处理,而是所有的数据都可以保存; 而后此方法调用了LinkedLast()方法(在最后一个节点追加): void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
可以发现LinkedList里面保存的数据都是利用Node节点进行的封装处理,同时为了提高程序的执行性能,每一次都会保存上一个追加的节点(最后一个节点),这样就可以在增加数据的时候避免递归处理,在增加数据的时候要进行数据保存个数的增加。 |
通过上述分析可以发现LinkedList封装就是一个链表的实现。
面试题:
请问ArrayList 和LinkedList 有什么区别?
- ArrayList 是数组实现的集合操作,而LinkedList 是链表实现的集合操作
- 在使用List集合中的get()方法根据索引获取数据时,ArrayList 的时间复杂度为“ O(1) ”,而LinkedList 的时间复杂度为“ O(n) ”(n为集合的长度)
- ArrayList在使用的时候默认的初始化长度为10,如果不足则会采用2倍的形式进行容量的扩充,如果保存大数据量的时候可能会造成垃圾的产生以及性能的下降,但是这个时候可以通过LinkedList 进行保存。
Vector【JDK1.0】:
到了JDK1.2的时候,因为用户以及习惯于Vector,并且许多的系统类也是基于Vector实现的,所以类集框架将其保存了下来,并且让其多实现了一个List接口;
定义:
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
继承结构与ArrayList 相同;
构造方法源代码:
public Vector()
|
public Vector() {
this(10);
}
|
如果Vector使用的是无参的构造方法,则一定会默认开辟一个10个长度的数组,而后其余的操作与ArrayList相同 |
public Vector(int initialCapacity)
|
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
|
|
public Vector(int initialCapacity, int capacityIncrement)
|
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
|
|
public synchronized void addElement(E obj)
|
public synchronized void addElement(E obj) {
modCount++;
add(obj, elementData, elementCount);
}
|
可以发现Vector类之中的操作方法采用的都是synchronized 同步处理,而ArrayList并没有进行同步处理,所以Vector类之中的方法在多线程访问的时候属于线程安全的,但是性能不如ArrayList高 |
Vector的使用:
import java.util.List;
import java.util.Vector;
public class MAIN {
public static void main(String[] args) {
List<String> list = new Vector<>(); // 实例化List父接口
list.add("www");
list.add(".baidu.");
list.add("com");
System.out.println("集合是否为空:" + list.isEmpty() + " \t集合元素个数 : " + list.size()); // 判断为空和元素个数
list.forEach(System.out::print);
}
}
输出结果:
未完待续、、、
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)