【Java 集合】ArrayList、LinkedList、Stack、Queue、Set、Map, 迭代器 Iterable、Iterator,Collections类

Java笔记目录可以点这里:Java 强化笔记(适合有基础的童鞋,不适合小白)

这篇文章只是学习一些集合的使用,更多底层细节可以看这个 【恋上数据结构】数据结构与算法

java.util 包中有个集合框架(Collections Framework),提供了一大堆常用的数据结构

  • ArrayListLinkedListQueueStackHashSetHashMap

数据结构是计算机存储、组织数据的方式:

  • 在实际应用中,根据使场景来选择最合适的数据结构
    在这里插入图片描述

集合框架预览
下图:紫色代表接口、蓝色是抽象类、绿色是实现类

在这里插入图片描述

数组的局限性

在这里插入图片描述
数组的局限性:

  • 无法动态扩容
  • 操作元素的过程不够面向对象

ArrayList 是 Java 中的动态数组

  • 一个可以动态扩容的数组
  • 封装了各种实用的数组操作

ArrayList(常用方法 + 基本使用)

常用方法:

public int size() // 元素数量
public boolean isEmpty() // 是否为空
public boolean contains(Object o) // 是否包含元素o
public int indexOf(Object o) // 从前往后查询元素o的下标
public int lastIndexOf(Object o) // 从后往前查询元素o的下标
public E get(int index) // 获取下标为index的元素
public E set(int index,E element) // 设置下标index的元素为element
public boolean add(E e) // 往最后添加一个元素e
public void add(int index,E element) // 往下标index处添加一个元素e
public E remove(int index) // 移除index处的元素
public boolean remove(Object o) // 移除元素o
public void clear() // 清空

public Object[] toArray()  // 转为数组
public <T>T[] toArray(T[] a) // 转为数组(指定类型)
public void trimToSize() // 缩容, 缩小到当前容量
public void ensureCapacity(int minCapacity) // 扩容

public boolean addAll(Collection<? extends E>c)
public boolean addAll(int index, Collection<? extends E> c)
public boolean removeAll(Collection<?>c)
public boolean retainAll(Collection<?>c)
public void forEach(Consumer<? super E> action)
public void sort(Comparator<? super E> c)

ArrayList 的基本使用:

// 如果不使用泛型, ArrayList中可以放任何东西, 不推荐这样使用(不安全)
ArrayList list = new ArrayList();
list.add(11);
list.add(false);
list.add(null);
list.add(3.14);
list.add(0, "jack");
list.add('8');

// 3
System.out.println(list.indexOf(null)); 
// 6
System.out.println(list.size());
// [jack, 11, false, null, 3.14, 8]
System.out.println(list);

ArrayList — retainAll

list1.retainAll(list2): 会从 list1 中删除掉 list2 中元素以外的所有元素。

List<Integer> list1 = new ArrayList<>();
list1.add(11);
list1.add(22);
list1.add(33);
list1.add(44);

List<Integer> list2 = new ArrayList<>();
list2.add(22);
list2.add(44);
		
// 从 list1 中删除掉 list2 中元素以外的所有元素
list1.retainAll(list2);
// [22, 44]
System.out.println(list1); 

需要注意的是 public boolean retainAll(Collection<?>c) 接收的参数类型是 Collection ,Java 中大部分集合之间都可以互相调用 retainAll

List<Integer> list = new ArrayList<>();
list1.add(11);
list1.add(22);
list1.add(33);
list1.add(44);

Stack<Integer> stack = new Stack<>();
stack.push(11);

// 从 list1 中删除掉 stack 中元素以外的所有元素
list.retainAll(stack);
// [11]
System.out.println(list); 

ArrayList — toArray

Integer[] array0 = (Integer[]) list.toArray(); 这段代码是不可行的,原因是 ArrayList 中不写泛型是可以任意添加元素的,强转 list.toArray() 的返回值极其不安全,会抛出 java.lang.ClassCastException 异常。

List<Integer> list = new ArrayList<>();
list.add(11);
list.add(22);
list.add(33);

// 不可以强转为Intger[], 会抛出异常: java.lang.ClassCastException
// Integer[] array0 = (Integer[]) list.toArray();

// 可以利用 Object[]来接收, 但是更推荐下面的写法
Object[] array1 = list.toArray();
// [Ljava.lang.Object;@15db9742
System.out.println(array1);
// [11, 22, 33]
System.out.println(Arrays.toString(array1));

// 比起用 Object[]接收,更推荐这种写法
Integer[] array2 = list.toArray(new Integer[0]);
// [Ljava.lang.Integer;@6d06d69c
System.out.println(array2);
// [11, 22, 33]
System.out.println(Arrays.toString(array2));

具体为什么不能转,请对比下面一系列情况:

Object obj1 = 11;
// 可以正常转换
Integer obj2 = (Integer) obj1;
System.out.println(obj2); // 11
Object obj1 = new Object();
// java.lang.ClassCastException
Integer obj2 = (Integer) obj1;
System.out.println(obj2);
Object[] array1 = {11, 22, 33};
// 上一行代码的本质就是下面一行
// Object[] array1 = new Object[] {11, 22, 33};
// java.lang.ClassCastException
Integer[] array2 = (Integer[]) array1; 
System.out.println(array1);

ArrayList 的遍历(5种)

经典的 for 循环遍历,可以获取遍历元素的下标:

// 经典
int size = list.size();
for (int i = 0; i < size; i++) {
	System.out.println(list.get(i));
}

利用迭代器进行遍历:

// 迭代器
Iterator<Object> it = list.iterator();
while(it.hasNext()) {
	Object obj = it.next();
	System.out.println(obj);
}

实现了 Iterable 接口的对象,都可以使用 for-each 格式遍历元素:比如 ListSet 等;
Iterable 在使用 for-each 格式遍历元素时,本质是使用了 Iterator 对象(迭代器)。

// for-each 格式
for (Object obj : list) {
	System.out.println(obj);
}

集合的 forEach 利用匿名内部类遍历:

list.forEach(new Consumer<Object>() {
	@Override
	public void accept(Object t) {
		System.out.println();
	}
});

利用了 lambda 表达式的 forEach

list.forEach((obj) -> {
	System.out.println(obj);
});

最精简的遍历输出,了解一下即可:

list.forEach(System.out::println);

ArrayList 的扩容原理

具体的在 恋上数据结构(第一季) 已经讲得很详细了,我已烂熟于心…再不济点进源码看看就行。
在这里插入图片描述

自定义迭代器 Iterable、Iterator

前面说过,实现了 Iterable 接口的对象,都可以使用迭代器for-each 格式(本质还是迭代器)遍历元素。

如何让自定义对象实现实现可迭代的功能呢?

import java.util.Iterator;

public class ClassRoom implements Iterable<String> {
	private String[] students;
	public ClassRoom(String... students) { // 可变参数的本质是数组 
		this.students = students; // 所以可变参数可以用数组来接收
	}
	@Override
	public Iterator<String> iterator() {
		return new ClassRoomIterator();
	}
	
	private class ClassRoomIterator implements Iterator<String> {
		private int index; // 游标
		@Override
		public boolean hasNext() {
			return index < students.length;
		}

		@Override
		public String next() {
			return students[index++];
		}
	}
	
}

我们来测试一下它的遍历:

ClassRoom room = new ClassRoom("Jack", "Rose", "Jerry");
// for-each格式遍历
for (String string : room) {
	System.out.println(string);
}
// 迭代器遍历
Iterator<String> it = room.iterator();
while(it.hasNext()) {
	System.out.println(it.next());
}

集合遍历的注意点(遍历集合的同时删除元素)

首先我们先往集合里加点东西:

List<Integer> list = new ArrayList<>();
list.add(11);
list.add(22);
list.add(33);
list.add(44);

然后我们通过遍历的方式,挨个删除所有的元素

可能有人会这么写,这样是错误的:抛出异常 java.lang.IndexOutOfBoundsException

int size = list.size();
for (int i = 0; i < size; i++) {
	// java.lang.IndexOutOfBoundsException
	list.remove(i);
}

或者会这么写,这样写也是错误的:会少删除元素

for(int i =0; i < list.size(); i++) {
	list.remove(i); 
}
System.out.println(list); // [22, 44]

下面两种写法也是错误的:抛出异常 java.util.ConcurrentModificationException

// java.util.ConcurrentModificationException
list.forEach((e) -> {
	list.remove(e);
}); 
// java.util.ConcurrentModificationException
for (Integer e : list) {
	list.remove(e);
}

我们会发现,遍历的集合的方法特别多,但是如果要在遍历的同时删除元素,以上都是不可行的。
使用迭代器forEach 遍历集合元素时,若使用了集合自带的方法修改集合的长度(比如 addremove 等方法),会抛出 java.util.ConcurrentModificationException 异常。

如果希望在遍历元素的同时删除元素

  • 请使用 Iterator 进行遍历
  • 然后使用 Iteratorremove 方法删除元素
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
	it.next();
	it.remove();
}
// 删除成功
System.out.println(list); // []
System.out.println(list.size()); // 0

ListIterator(遍历同时添加元素)

ListIterator 继承自 Iterator,在 Iterator 的基础上增加了一些功能

boolean hasNext();  // 是否有下一个元素
E next(); // 获取下一个元素
boolean hasPrevious(); // 是否有前一个元素 
E previous(); // 获取前一个元素
int nextIndex();  
int previousIndex(); 
void remove();
void set(E e); 
void add(Ee);

基本使用:

List<Integer> list = new ArrayList<>();
list.add(11);
list.add(22);
list.add(33);

ListIterator<Integer> it = list.listIterator();
// 从前往后遍历
while (it.hasNext()) { 
	System.out.println(it.next());
} // 11 22 33

// 从后往前遍历
while (it.hasPrevious()) {
	System.out.println(it.previous());
} // 33 22 11

遍历同时添加元素

ListIterator<Integer> it = list.listIterator();
while (it.hasNext()) {
	it.set(it.next() + 55);
}
System.out.println(list); // [66, 77, 88]

LinkedList

LinkedList 是一个双向链表

  • 实现了 List 接口
  • API 跟 ArrayList 类似

在这里插入图片描述

LinkedList vs ArrayList

在这里插入图片描述

Stack

在这里插入图片描述
Stack 常用方法:
在这里插入图片描述
Stack 使用:

Stack<Integer> stack = new Stack<>();
stack.push(11);
stack.push(22);
stack.push(33);

System.out.println(stack.peek()); // 33

System.out.println(stack.search(11)); // 3

while(!stack.isEmpty()) {
	System.out.println(stack.pop());
} // 33 22 11

Queue

在这里插入图片描述
Queue 常用方法:
在这里插入图片描述
Queue 使用:

Queue<Integer> queue = new LinkedList<>();
queue.add(11);
queue.add(22);
queue.add(33);

System.out.println(queue.element()); // 11

while (!queue.isEmpty()) {
	System.out.println(queue.remove());
} // 11 22 33

Set

  • 只要是 Set 都可以用作去重

HashSet

HashSet 简单使用:

Set<String>set = new HashSet<>();
set.add("Jack");
set.add("Rose");
set.add("Kate");
set.add("Jack");
set.add("Hello");

System.out.println(set.size()); // 4

// 元素的存储是无序的
System.out.println(set);  // [Kate, Hello, Rose, Jack]

set.remove("B");
System.out.println(set); // [Kate, Hello, Rose, Jack]

HashSet 的遍历:

Set<String> set = new HashSet<>();
set.add("Jack");
set.add("Rose");
set.add("Kate");

// 元素的遍历是无序的

// 增强for循环遍历
for (String str : set) {
	System.out.println(str);
} // Kate Rose jack

// 迭代器遍历
Iterator<String> it = set.iterator();
while (it.hasNext()) {
	System.out.println(it.next());
} // Kate Rose jack

// forEach遍历
set.forEach((str) -> {
	System.out.println(str);
}); // Kate Rose jack

LinkedHashSet

  • LinkedHashSetHashSet 的基础上(可去重),记录了元素的添加顺序

LinkedHashSet 简单使用:

Set<String> set = new LinkedHashSet<>();
set.add("Jack");
set.add("Rose");
set.add("Kate");
set.add("Jack");
set.add("Hello");

System.out.println(set.size()); // 4

// 元素的存储是有序的(添加顺序)
System.out.println(set);  // [Jack, Rose, Kate, Hello]

// 元素的遍历是有序的(添加顺序)
for (String str : set) {
	System.out.println(str); // Jack Rose Kate
}

set.remove("B");
System.out.println(set); // [Jack, Rose, Kate, Hello]

TreeSet

  • TreeSet 要求元素必须具备可比较性,默认从小到大的顺序遍历元素(可通过比较器修改)
// 字符串继承了Comparable, 具有可比较性, 默认按字母顺序从小到大
Set<String> set =new TreeSet<>();
set.add("Jack");
set.add("Rose");
set.add("Jim");
set.add("Kate");
set.add("Rose");
set.add("Larry");

System.out.println(set); // [Jack, Jim, Kate, Larry, Rose]

for (String str : set) {
	System.out.println(str); // Jack Jim Kate Larry Rose
}

通过比较器自定义比较方式:

// 默认比较方式是从小到大
// 通过比较器修改为从大到小
Set<Integer> set = new TreeSet<>((i1, i2) -> i2 -i1);
set.add(33);
set.add(11);
set.add(55);
set.add(22);
set.add(44);

System.out.println(set); // [55, 44, 33, 22, 11]

for (Integer i : set) {
	System.out.println(i); // 55 44 33 22 11
}

Map

HashMap

  • HashMap 存储的是键值对(key-value),Map 译为 映射,有些编程语言中叫做 字典

HashMap 简单使用:

HashMap<String, Integer> map = new HashMap<>();
map.put("Jack", 11);
map.put("Rose", 22);
map.put("Jim", 22);
map.put("Jack", 33); // 重复
map.put("Kate", 11);

System.out.println(map.size()); // 4

System.out.println(map.get("Jack")); // 33

System.out.println(map); // {Kate=11, Rose=22, Jack=33, Jim=22}

map.remove("Rose");
System.out.println(map); // {Kate=11, Jack=33, Jim=22}

HashMap — 遍历

HashMap<String, Integer> map = new HashMap<>();
map.put("Jack", 11);
map.put("Rose", 22);
map.put("Jim", 33);

最简洁的遍历方法:通过 forEach 遍历

// forEach 遍历, 推荐
map.forEach((key, value) -> {
	System.out.println(key + "=" + value);
}); // Rose=22 Jack=11 Jim=33

通过键值对 entrySet 遍历:

// entrySet 是一组键值对, 通过键值对来遍历, 推荐
Set<Entry<String, Integer>> entries = map.entrySet();
for (Entry<String, Integer> entry : entries) {
	System.out.println(entry.getKey() + "=" + entry.getValue());
} // Rose=22 Jack=11 Jim=33

先取出 value 的集合,再遍历:无法通过 value 获取 key,只能遍历 value

// 只能遍历 value
Collection<Integer> values = map.values(); // value的集合
for (Integer value : values) {
	System.out.println(value);
} // 22 11 33

取出 key 的集合,再遍历:可以通过 key 获取 value,但是效率低下

Set<String> keys = map.keySet(); // key的集合
for (String key : keys) {
	// 先遍历key, 又通过key去找到value (整体效率较低)
	System.out.println(key + "=" + map.get(key));
} // Rose=22 Jack=11 Jim=33

LinkedHashMap

  • LinkedHashMapHashMap 的基础上,记录了元素的添加顺序
Map<String, Integer> map = new LinkedHashMap<>();
map.put("Jack", 11);
map.put("Rose", 22);
map.put("Jim", 33);
map.put("Kate", 44);

System.out.println(map); // {Jack=11, Rose=22, Jim=33, Kate=44}

map.forEach((k, v) -> {
	System.out.println(k + "=" + v);
}); // Jack=11 Rose=22 Jim=33 Kate=44

map.remove("Rose");
System.out.println(map); // {Jack=11, Jim=33, Kate=44}

TreeMap

  • TreeMap 要求 key 必须具备可比较性,默认从小到大的顺序遍历 key(可通过比较器修改)
Map<String, Integer> map = new TreeMap<>();
map.put("Jack", 11);
map.put("Rose", 22);
map.put("Jim", 33);
map.put("Kate", 44);
map.put("Larry", 55);

System.out.println(map); // {Jack=11, Jim=33, Kate=44, Larry=55, Rose=22}

map.forEach((k, v) -> {
	System.out.println(k + "=" + v);
}); // Jack=11 Jim=33 Kate=44 Larry=55 Rose=22

List vs Set vs Map

在这里插入图片描述

java.util.Collections

  • java.util.Collections 是一个常用的集合工具类,提供了很多实用的静态方法

在这里插入图片描述

发布了191 篇原创文章 · 获赞 71 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43734095/article/details/105523122