目录
什么是List
继上一篇JAVA Arrays类 详细介绍之后
List:列表类型,表示一个有序集合。
Map:映射类型,表示键值对的集合。
Set:集合类型,表示一组不重复的元素。
以上就是接下来几篇的内容,本篇主要是List
List
├── ArrayList
├── LinkedList
└── Vector——Stack
以上是List接口的几个实现类,以下,则是一些不太常用的List可以先略过,Stack应该还算常用,后面会讲
- Stack(栈):继承自
Vector
类,实现了一个后进先出(LIFO)的栈。 - CopyOnWriteArrayList:这是一个线程安全的列表,适用于读操作频繁、写操作较少的场景。
- AbstractList:这是一个抽象类,实现了
List
接口的大部分方法,可以作为其他列表实现的基类。 - ImmutableList:这是Guava库中提供的一个不可变列表的实现。
- AbstractSequentialList:这是一个抽象类,实现了
List
接口,并且以链接结构存储元素,适用于顺序访问元素的场景。
除了这些,还有许多其他的列表实现,有些是第三方库提供的,有些是Java平台的其他部分提供的。每种列表实现都有其自身的特点和适用场景。
List的特点 (不重要)
- 有序性(Ordering):List中的元素按照插入顺序排列,每个元素都有一个与之关联的索引。
- 允许重复(Duplicates):List允许存储重复的元素,即同一个元素可以出现多次。
- 可变性(Mutability):大多数List实现是可变的,即可以动态地增加、删除和修改其中的元素。
- 索引访问(Indexed Access):可以通过索引来访问List中的元素,索引从0开始计数。
- Iterable接口(Iterable Interface):List实现了Iterable接口,因此可以使用增强型for循环和迭代器来遍历其中的元素。
如何创建List
以下是创建List的方法
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import java.util.Vector;
public class Main {
public static void main(String[] args) {
List<Integer> list_1 = new ArrayList<Integer>();
List<Integer> list_2 = new LinkedList<Integer>();
List<Integer> list_3 = new Vector<Integer>();
List<Integer> list_4 = new Stack<Integer>();
}
}
那么你是否已经生出疑问?
List<Integer> list_1 = new ArrayList<Integer>();
ArrayList<Integer> list_5 = new ArrayList<Integer>();
这两句代码哪一种是你常写或者你常见的呢?
我要告诉你的是,这两句代码他们的效果是一样的,但为什么会有两种写法呢?如果你有意向深入,那么接下来的内容可以看看,你也可以选择先跳过
在语义上,这两句代码的效果是相同的,它们都创建了一个 ArrayList
类型的列表对象。
但是,在编程时,通常建议使用接口类型来声明变量,而不是具体的实现类。这样做有几个好处:
- 更好的封装:使用接口类型声明变量,可以将变量类型与实现类分离,从而提高代码的灵活性和可维护性。
- 降低耦合度:接口类型的变量只关心方法的签名,而不关心具体的实现细节,因此可以轻松地切换不同的实现类而不影响其他部分的代码。
- 符合面向接口编程的原则:面向接口编程是一种良好的编程实践,它能够提高代码的可复用性和可测试性。
因此,建议使用 List<Integer> list_1 = new ArrayList<Integer>();
这种方式,以接口类型声明变量,从而获得更好的代码设计和可维护性。
List常用方法
- add(E e):在列表末尾添加指定的元素。
- get(int index):返回列表中指定位置的元素。
- set(int index, E element):将列表中指定位置的元素替换为指定的元素。
- remove(int index):移除列表中指定位置的元素,并返回被移除的元素。
- size():返回列表中的元素数。
- isEmpty():判断列表是否为空。
- contains(Object o):判断列表是否包含指定的元素。
- indexOf(Object o):返回列表中指定元素第一次出现的索引。
- lastIndexOf(Object o):返回列表中指定元素最后一次出现的索引。
- subList(int fromIndex, int toIndex):返回列表中指定范围的部分视图。
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
List<Integer> list_1 = new ArrayList<Integer>();
list_1.add(1);list_1.add(2);list_1.add(3);list_1.add(4);
list_1.set(0, 2);
list_1.get(0);
list_1.remove(0);
list_1.size();
list_1.isEmpty();
list_1.contains(1);
list_1.indexOf(2);
list_1.lastIndexOf(2);
list_1.subList(1, 3);
}
}
当然,这些方法理论上对list的任意实现类都可以使用,但是它们并不包含输出效果,所以上面的代码没有任何输出。
值得一提的是,在上一篇Arrays类当中貌似忘记提了一点,就是list是可以直接使用System.out.println();语句输出的,和数组是不一样的。
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
List<Integer> list_1 = new ArrayList<Integer>();
list_1.add(1);list_1.add(2);list_1.add(3);list_1.add(4);
list_1.set(0, 2);
System.out.println(list_1.get(0));
list_1.remove(0);
System.out.println(list_1.size());
System.out.println(list_1.isEmpty());
System.out.println(list_1.contains(1));
System.out.println(list_1.indexOf(2));
System.out.println(list_1.lastIndexOf(2));
System.out.println(list_1.subList(1, 3));
}
}
我们只要将它改成这样就会得到输出
2
3
false
false
0
0
[3, 4]
区别
要知道既然list分为了这么多不同的实现类,那么,一定是有区别滴!
ArrayList&LinkedList
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// 创建一个ArrayList
ArrayList<String> arrayList = new ArrayList<>();
// 添加元素到ArrayList
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Orange");
// 遍历ArrayList
for (String fruit : arrayList) {
System.out.println(fruit);
}
}
}
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
// 创建一个LinkedList
LinkedList<String> linkedList = new LinkedList<>();
// 添加元素到LinkedList的开头
linkedList.addFirst("Apple");
linkedList.addFirst("Banana");
linkedList.addFirst("Orange");
// 遍历LinkedList
for (String fruit : linkedList) {
System.out.println(fruit);
}
}
}
这两段代码看上去没有任何区别,但是 ArrayList支持随机访问
LinkedList 也支持随机访问,但是与 ArrayList 不同,LinkedList 的随机访问效率比较低,因为它需要从头或尾开始遍历链表,直到找到目标元素为止。这是因为 LinkedList 是通过链表实现的,每个节点只知道自己的前驱和后继节点,而不像 ArrayList 那样可以通过索引直接访问。
虽然 LinkedList 也支持随机访问,但是相对于 ArrayList 来说,它的性能会比较低,尤其是在大量随机访问操作时。因此,如果需要频繁进行随机访问操作,建议使用 ArrayList。
Vector
Vector
是 Java 中的一个传统集合类,它实现了动态数组的数据结构,与 ArrayList
类似。但是,Vector
是线程安全的,它的方法都是同步的,这意味着在多线程环境下,Vector
可以安全地被多个线程同时访问和修改。但是,这种同步机制也带来了性能上的一定损失。
以下是关于 Vector
类的一些重要点:
- 线程安全性:
Vector
的所有方法都是同步的,可以安全地在多个线程之间共享。 - 容量增长:与
ArrayList
类似,Vector
也具有自动扩容的功能,当容量不足时,会自动增加容量。 - 遍历和访问:可以使用类似于
ArrayList
的方式来访问和遍历Vector
中的元素。 - 迭代器:
Vector
提供了迭代器用于遍历元素。 - 不推荐使用:由于其同步机制的性能损失以及更现代的
ArrayList
和LinkedList
类的出现,一般不推荐使用Vector
。相比之下,ArrayList
在非多线程环境下通常具有更好的性能,而LinkedList
则在插入和删除操作频繁的情况下可能更高效。import java.util.Vector; public class Main { public static void main(String[] args) { Vector<Integer> vector = new Vector<>(); // 添加元素 vector.add(1); vector.add(2); vector.add(3); // 获取元素 System.out.println("Element at index 1: " + vector.get(1)); // 遍历元素 System.out.print("Vector elements: "); for (Integer num : vector) { System.out.print(num + " "); } System.out.println(); } }
在实际开发中,除非需要线程安全的集合类,否则一般建议使用
ArrayList
或LinkedList
来替代Vector
。
Stack
Stack
是 Java 中的一个类,它是一种后进先出(LIFO)的数据结构,类似于现实生活中的堆栈,例如书桌上的书堆或者餐厅里的盘子堆。在 Stack
中,最后添加的元素将会被最先移除。
我记得之前看过一个非常详细的例子但是忘记出处了,如果有人知道可以提醒我
就是说栈是否给你一种像栈道一样长长的感觉,那么就很容易联想到桶装的薯片,生产的时候最先装进去的那一片,就是你最后吃到的一片。
以下是关于 Stack
类的一些重要点:
- 继承关系:
Stack
类继承自Vector
类,因此它包含了Vector
类的所有方法,并在此基础上实现了堆栈的特性。 - 后进先出:在
Stack
中,最后添加的元素将会被最先移除,这就是后进先出的特性。 - 常用方法:
Stack
提供了一些常用的方法,例如push()
将元素推入栈顶,pop()
将栈顶元素移除并返回,peek()
获取栈顶元素但不移除等。 - 空栈异常:在尝试对空栈执行
pop()
或peek()
操作时,将会抛出EmptyStackException
异常。 - 应用场景:
Stack
在许多场景中都有应用,例如表达式求值、括号匹配、深度优先搜索等算法中。
import java.util.Stack;
public class Main {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
// 将元素推入栈顶
stack.push(1);
stack.push(2);
stack.push(3);
// 弹出栈顶元素
int topElement = stack.pop();
System.out.println("Top element popped: " + topElement);
// 获取栈顶元素但不移除
int peekElement = stack.peek();
System.out.println("Top element (peek): " + peekElement);
// 遍历栈元素
System.out.print("Stack elements: ");
while (!stack.isEmpty()) {
System.out.print(stack.pop() + " ");
}
System.out.println();
}
}
当然我们上面也提到过,Stack在java中属于是Vector的分支,那么我们也讲了Vector在内存上的损耗。在实际开发中,Stack
类在一些特定场景下非常有用,例如在处理算法中的栈相关问题时。但需要注意的是,由于 Stack
是线程安全的类,使用 Deque
的实现类 ArrayDeque
或 LinkedList
通常在性能上更好。
Deque(Double Ended Queue)在这里提一嘴,它也可以达成与栈一样的先进后出的效果。Deque
接口提供了一系列方法,可以从队列的两端添加、移除和检查元素。这使得 Deque
既可以作为队列使用,也可以作为栈使用,具有很高的灵活性。例如,addFirst()
和 removeFirst()
方法用于操作队列的头部,addLast()
和 removeLast()
方法用于操作队列的尾部。
import java.util.Deque;
import java.util.ArrayDeque;
public class Main {
public static void main(String[] args) {
// 创建一个双端队列
Deque<Integer> deque = new ArrayDeque<>();
// 添加元素到队列尾部
deque.addLast(1);
deque.addLast(2);
deque.addLast(3);
// 遍历队列,输出队列元素(先进先出)
System.out.print("Queue (FIFO): ");
while (!deque.isEmpty()) {
System.out.print(deque.removeFirst() + " ");
}
System.out.println();
// 添加元素到栈顶
deque.addFirst(1);
deque.addFirst(2);
deque.addFirst(3);
// 遍历栈,输出栈元素(后进先出)
System.out.print("Stack (LIFO): ");
while (!deque.isEmpty()) {
System.out.print(deque.removeFirst() + " ");
}
System.out.println();
}
}
与Arrays类的联系
这里算是个伏笔,也就是为什么我在上一篇介绍了Arrays类的相关内容,正是因为Arrays类的一些操作方法对List也是适用的。嘿嘿。
Integer[] array = {1, 2, 3, 4, 5};
List<Integer> list = Arrays.asList(array);
List<Integer> list = new ArrayList<>();
// 添加元素到列表
list.add(1);
list.add(2);
list.add(3);
// 转换为数组
Integer[] array = list.toArray(new Integer[list.size()]);
String[] array = {"apple", "banana", "orange"};
String str = Arrays.toString(array); // 输出:[apple, banana, orange]
List<String> list = Arrays.asList("apple", "banana", "orange");
int index = list.indexOf("banana"); // 返回 1
List<Integer> list = new ArrayList<>();
list.addAll(Arrays.asList(1, 2, 3)); // 添加元素 1, 2, 3
List<Integer> list = new ArrayList<>(Arrays.asList(5, 3, 1, 4, 2));
list.sort(Comparator.reverseOrder()); // 降序排序
List<String> list = Arrays.asList("apple", "banana", "orange");
boolean contains = list.contains("banana"); // 返回 true
以上都是一些例子
小细节
不知道你有没有生出疑问,反正我有
ArrayList<Integer> list = new ArrayList<>();
你不好奇最后面那个小括号是拿来干嘛的吗?老弄我心痒痒
ArrayList 的括号 "()" 内部可以包含一些参数,这些参数通常用于构造函数。在这里,你可以指定初始容量或者从另一个 Collection 对象初始化 ArrayList。
// 指定初始容量为 20 的 ArrayList
ArrayList<Integer> list = new ArrayList<>(20);
ArrayList<Integer> anotherCollection = new ArrayList<>();
// 添加一些元素到 anotherCollection 中
// 从另一个 Collection 对象初始化 ArrayList
ArrayList<Integer> list = new ArrayList<>(anotherCollection);
完结!!!