Java基础教程——List(列表)

集合概述

Java中的集合,指一系列存储数据的接口和类,可以解决复杂的数据存储问题.
导包:import java.util.*;

简化的集合框架图如下:

List·列表

ArrayList

List是一个接口:

public interface List<E> extends Collection<E>{...}

ArrayList是最常用的一种List的子类(当然也实现了其他接口,也继承了父类)。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{...}

ArrayList用法类似于数组,且其容量可按需要动态调整,亦被称为动态数组。

数组最大的痛点是大小固定(可以改变,但是很麻烦)
ArrayList底层是用数组实现的,所以名字里带了个数组(Array)。

示例:泰国旅行团
本示例展示了在List中使用泛型的必要性。
设定:泰国旅行团,约定只收Girl,有一个Boy混入,编译没问题,接机(输出)时按Girl接收,会出错。

import java.util.ArrayList;
import java.util.List;
public class TestArrayList {
	public static void main(String[] args) {
		// 开团
		List _泰国五日游 = new ArrayList();
		Girl _g1 = new Girl();
		Girl _g2 = new Girl();
		Girl _g3 = new Girl();
		_泰国五日游.add(_g1);
		_泰国五日游.add(_g2);
		_泰国五日游.add(_g3);
		// 混入
		Boy _b = new Boy();
		_泰国五日游.add(_b);
		System.out.println("...");
		// 接机
		for (int i = 0; i < _泰国五日游.size(); i++) {
			Girl g = (Girl) _泰国五日游.get(i);
		}
	}
}
class Boy {
}
class Girl {
}

代码没错,运行出错(对象本是Boy类型,偏要转成Girl类型---类型转换异常)

Exception in thread "main"

java.lang.ClassCastException

ArrayList<E>使用泛型

JDK 1.5之后,引入了泛型,可指定列表内元素的类型。类型不符合的元素不允许加入数组,这样就能再编译阶段发现错误,避免运行时出错的尴尬。

		// 开团
		List<Girl> _泰国五日游 = new ArrayList<Girl>();
		……
		// 混入
		Boy _b = new Boy();
		//提示代码有错误:     _泰国五日游.add(_b);

遍历

import java.util.*;
public class ListTraversal {
	public static void main(String[] args) {
		m010Traversal();
		m020线程安全版();
	}
	private static void m010Traversal() {
		System.out.println("=====遍历");
		List<String> lst = new ArrayList<String>();
		lst.add("孙行者");
		lst.add("猪八戒");
		lst.add("沙悟净");
		// (1)
		for (int i = 0; i < lst.size(); i++) {
			System.out.println("for遍历集合:" + lst.get(i));
		}
		// (2)
		for (String s : lst) {
			System.out.println("foreach遍历集合:" + s);
		}
		// (3)Iterator,迭代器。用于遍历(迭代访问)集合中的元素
		Iterator<String> it = lst.iterator();
		while (it.hasNext()) {
			System.out.println("Iterator遍历:" + it.next());
		}
		// (4)Java 8:调用forEach()方法遍历集合
		lst.forEach(s -> System.out.println("Lambda表达式遍历集合:" + s));
	}
	// API文档上说ArrayList不是同步的,即多线程环境下不安全
    // Collections.synchronizedList(...)将其转为线程安全的列表
	private static void m020线程安全版() {
		System.out.println("=====线程安全版");
		List<String> lst = new ArrayList<String>();
		lst.add("孙行者");
		lst.add("猪八戒");
		lst.add("沙悟净");
		// 解决线程安全问题
		List<String> synList = Collections.synchronizedList(lst);
		for (String s : synList) {
			System.out.println("foreach遍历集合:" + s);
		}
	}
}

迭代器原理gif:https://www.cnblogs.com/tigerlion/p/10706386.html

更多方法

Collection相关方法

这些方法属于Collection类,可以被子类继承,因此通用性较强,不仅List能用,Set也能用。

返回类型 方法名称 描述
boolean add(Object o) 添加元素
int size() 获取元素个数
boolean contains(Object o) 判断是否存在指定元素
boolean remove(Object o) 删除元素
void clear() 清空
boolean isEmpty() 判空
Object[] toArray() 集合转数组
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class TestCollection {
	public static void main(String[] args) {
		Collection<String> list = new ArrayList<>();
		for (int i = 0; i < 100; i++) {
			list.add("s" + i);
		}
		System.out.println("size():" + list.size());
		// 查找
		boolean contains = list.contains("s1");
		System.out.println("contains(Object o):" + contains);
		boolean empty = list.isEmpty();
		System.out.println("isEmpty():" + empty);
		// 集合转数组
		Object[] array = list.toArray();
		System.out.println("toArray():" + Arrays.toString(array));
		// 删除
		list.remove("s1");
		System.out.println("remove(Object o):" + list);
		list.clear();
		System.out.println("clear():" + list);
	}
}

List相关方法

List的派生类对象可以使用,Set不可用。
都是和索引相关的方法:

返回类型 方法名称 描述
void add(int index, E element) 指定位置添加元素
int indexOf(Object o) 获取指定元素的索引
E set(int index, E element) 替换指定位置的元素,返回更新前的元素
E get(int index) 获取指定索引的元素
E remove(int index) 删除指定索引的元素
import java.util.ArrayList;
import java.util.List;
public class TestList {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		for (int i = 0; i < 10; i++) {
			list.add("s" + i);
		}
		list.add(3, "舍卫国");
		int indexOf = list.indexOf("舍卫国");
		System.out.println("List.indexOf(Object o):" + indexOf);
		String set = list.set(0, "舍卫国赵长者");// 返回更新前的元素
		System.out.println("List.set(int index, E element):" + set);
		String get = list.get(0);
		System.out.println("List.get(int index):" + get);
		String remove = list.remove(3);// 返回被删除的元素
		System.out.println("List.remove(int index):" + remove + list);
	}
}

源码浅析:

ArrayList底层是通过数组实现,查询快、增删慢。API文档上说ArrayList不是同步的,即多线程环境下不安全,但是效率高。

ArrayList每次扩容至1.5倍。

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // >>1:右移动1位=除以2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

Vector

和ArrayList用法一致。

元素超过它的初始大小 线程安全 效率
ArrayList *150% ×
Vector *200%

Vector是一个比较老的类,在JDK 1.0即已出现,不推荐使用(蓝桥杯的练习题中出现过Vector,在那道题中只要知道它的用法和ArrayList一样就行)。

虽然Vector是线程安全的,但是在线程安全方面也不推荐使用。推荐方案如下:

List<String> synList = Collections.synchronizedList(lst);

LinkedList

ArrayList使用数组实现,查询快,增删慢;
LinkedList使用链表实现,查询慢,增删快,适用于经常插入、删除大量数据的场合,适合采用迭代器Iterator遍历。
如果仅仅是在列表末尾插入数据,LinkedList的效率低于ArrayList,因为LinkedList调用add时需要创建对象,而ArrayList只是在容量不够时才扩容。

LinkedList实现了List和Deque(双端队列)接口。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

特色方法(此时不能使用多态):

特色方法 解释
addFirst() 头部添加
addLast() 尾部添加
removeFirst() 头部删除
removeLast() 尾部删除
push() 入栈,等效于addFirst()
pop() 出栈,等效于removeFirst()
offer() 入队列,等效于addLast()
poll() 出队列,等效于removeFirst()
import java.util.LinkedList;
public class TestLinkedList {
	public static void main(String[] args) {
		LinkedList<String> link = new LinkedList<String>();
		// addFirst:头部添加数据
		link.addFirst("A");
		link.addFirst("B");
		link.addFirst("C");
		link.removeFirst();
		System.out.println(link);
		// addLast:尾部添加数据
		link.addLast("A");
		link.addLast("B");
		link.addLast("C");
		link.removeLast();
		System.out.println(link);
		link.clear();// 清空
		// push:将元素推入栈,等效于addFirst()
		link.push("A");
		link.push("B");
		link.push("C");
		// pop:出栈,调用的是removeFirst()
		link.pop();
		System.out.println("栈" + link);
		link.clear();// 清空
		// 将指定元素添加到此列表的末尾(最后一个元素)。
		// offer:入队列:调用的是add方法,add又调用linkLast,和addLast一样
		link.offer("A");
		link.offer("B");
		link.offer("C");
		// poll:出队列:调用的是removeFirst()
		link.poll();
		System.out.println("队列" + link);
	}
}

[B, A]
[B, A, A, B]
栈[B, A]
队列[B, C]


*ArrayDeque·栈和队列

  • 栈:先进后出
  • 队列:先进先出

Deque(双端队列),是Queue的子接口,其实现类ArrayDeque和ArrayList的实现机制相似,使用Object[]数组存储集合元素,当容量不足时,可以重新分配数组。

ArrayDeque可以当做栈和队列使用。

import java.util.*;
public class TestArrayDeque {
	public static void main(String[] args) {
		m030栈();
		m040队列();
	}
	static void m030栈() {
		System.out.println("=====栈");
		// push,pop(poll也可以)
		Deque<String> stack = new ArrayDeque<String>();
		stack.push("A");
		System.out.println(stack);// [A]
		stack.push("B");
		System.out.println(stack);// [B, A]
		stack.push("C");
		System.out.println(stack);// [C, B, A]
		System.out.println("peek()访问第一个元素:" + stack.peek());// C
		System.out.println("pop()弹出:" + stack.pop());// C
		System.out.println(stack);// [B, A]
	}
	static void m040队列() {
		System.out.println("=====队列");
		// offrt,poll(pop也可以)
		Deque<String> queue = new ArrayDeque<String>();
		queue.offer("A");// [A]
		System.out.println(queue);
		queue.offer("B");// [A, B]
		System.out.println(queue);
		queue.offer("C");// [A, B, C]
		System.out.println(queue);
		System.out.println("peek()访问第一个元素:" + queue.peek());// A
		System.out.println("poll()弹出:" + queue.poll());// A
		System.out.println(queue);// [B, C]
	}
}

运行结果:

=====栈
[A]
[B, A]
[C, B, A]
peek()访问第一个元素:C
pop()弹出:C
[B, A]
=====队列
[A]
[A, B]
[A, B, C]
peek()访问第一个元素:A
poll()弹出:A
[B, C]
posted @ 2019-07-13 02:00  虎老狮  阅读(32592)  评论(1编辑  收藏  举报