1. 泛型Generics
1.1 泛型的概述
泛型是JDK5.0以后增加的,他可以帮助我们建立类型安全的集合。在使用了泛型的集合中,不必进行强制类型转换。JDK提供了支持泛型的编译器,将运行时的类型检查提前到了编译时执行,使代码可读性和安全性更高。
-
不使用泛型时,会在程序运行时期检查元素的类型是否为String,强转不当会出现类型转换异常
-
使用泛型时,在编译时期就会进行类型检查,如果不符合集合泛型,那么就添加不进去,编译报错
说明: 集合添加了泛型后,可以在编译时期约束元素的类型,对编程有指导作用。
1.1.1 泛型擦除补偿
java分两个部分,编译和运行,泛型的出现提高了编译的安全性,正因为编译时检查完了,运行时才没事。因此泛型本质上是编译时期的技术,是给编译器用的。
程序在运行时,会将泛型擦除掉(擦除之后的类型就是Object类型),这个称为泛型擦除。
为什么要擦除呢?为了兼容运行的类加载器!运行的时候,要靠虚拟机启动类装载器,专门读取类文件解析类文件,并把类文件加载进内存的程序。JDK1.4版本,JDK1.5版本都用的这个加载器。程序只在编译的时候,将类型进行检查。如果检查完是安全的,没有错误的,运行就可以把这个泛型去掉了,因为类型就统一了。我们还想要以前的类加载器进行加载解析,并进内存。
有一个问题,取出来的时候不加强转了,你还知道取出来的是什么吗?编译器只做检查,检查完,最终这个类型被擦掉,所以这个里面和以前一样还是object,(这里应该是指,add方法中加入的元素变成了object类型了),那就意味着它们提升了。同时,下面还不能做强转,取出来就能用,这是为什么呢?
这里又做了另外一种动作:在运行时,通过获取元素的实际类型进行强制,叫做补偿(不必手动实现强制转换)。编译结束后,要是完全不告诉类装载器不合适,用了泛型补偿机制告诉类加载器,都检测完了,就编写一个补偿程序。在类装载器已有的基础上,来了以后,我根据你指定的好的元素类型,真正运行的时候就要碰到这个元素了,根据你的元素获取其类型,再对你进行一次转换就行了。
1.2 泛型的使用
泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中,将数据类型作为参数进行传递。
1.2.1 含有泛型的类
定义格式:修饰符 class 类名<代表泛型的变量> { }
使用格式:创建对象时,确定泛型的类型
注意:静态方法不能使用类上定义的泛型。
1.2.2 含有泛型的方法
定义格式:修饰符 <代表泛型的变量> 返回值类型 方法名(形参列表){}
使用格式:调用方法时,确定泛型的类型。
注意:泛型对象只能使用Object类中的方法,不能使用其特有的方法。
1.2.3 含有泛型的接口
定义格式:修饰符 interface 接口名<代表泛型的变量> { }
使用格式:
1、 定义类时确定泛型的类型。
2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型。
1.2.4 泛型通配符
泛型是在限定数据类型,当在集合或者其他地方使用到泛型后,那么这时一旦明确泛型的数据类型,那么在使用的时候只能给其传递和数据类型匹配的类型,否则就会报错。
有的时候,我们在定义方法时,根本无法确定具体集合中的元素类型是什么。为了解决这个“无法确定具体集合中的元素类型”问题,java 中,为我们提供了泛型的通配符。
通配符的几种形式:
1、无限定通配符,<?>。
2、上限通配符,<? extends Number>。表示参数类型只能是Number及其子类。
3、下限通配符,<? supper Number>。表示参数类型只能是Number及其父类。
2 队列(Queue)
1.1 队列的简介
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,队列是一种操作受限制的线性表。进行插入操作(入口)的端称为队尾,进行删除操作(出口)的端称为队头。
队列的插入操作只能在队尾操作,队列的删除操作只能在队头操作,因此队列是一种先进先出(First In First Out)的线性表,简称FIFO表。
LinkedList类实现了Queue接口,在Queue接口中提供的方法可以实现队列的相关操作。
Queue接口扩展了Collection接口,常见的方法如下所示。
方法名 | 描述 |
---|---|
boolean add(E e); | 这两个方法都是在尾部添加元素add()会在长度不够时抛出异常; offer()则不会,只返回false |
boolean offer(E e); | |
E element(); | 这两个方法都是查看头部元素 ,返回头部元素,但不改变队列element()会在没元素时抛出异常; peek()则返回null; |
E peek(); | |
E remove(); | 这两个方法都是删除头部元素,返回头部元素,并且从队列中删除remove()会在没元素时抛出异常; poll()返回null; |
E poll(); | |
boolean isEmpty(); | 判断队列是否为空,如果为空则返回true;否则返回false。 |
【示例】使用LinkedList实现队列操作
import java.util.LinkedList; import java.util.Queue; /* @author mashushu @date 2023/7/17 @description 队列的介绍 队列在JDK中的形式是:Queue接口,也就是说实现了Queue接口的类都具有队列的功能 队列的体系结构: LinkedList -实现--> Deque -继承--> Queue -继承--> Collection Queue接口的常用方法: boolean add(E e); 在队列尾部添加元素, add()会在长度不够时抛出异常 boolean offer(E e); 在队列尾部添加元素, offer()则不会,只返回false E element(); 查看头部元素 ,返回头部元素,但不改变队列 , element()会在没元素时抛出异常; E peek(); 查看头部元素 ,返回头部元素,但不改变队列 , peek()则返回null; E remove(); 删除头部元素,返回头部元素,并且从队列中删除,remove()会在没元素时抛出异常; E poll(); 删除头部元素,返回头部元素,并且从队列中删除,poll()返回null; boolean isEmpty(); 判断队列是否为空,如果为空则返回true;否则返回false。 */ public class QueueDemo1 { public static void main(String[] args) { Queue<Integer> queue = new LinkedList<>(); queue.add(2); queue.add(5); queue.add(8); queue.add(1); queue.add(4); System.out.println("queue = " + queue); // list = [2, 5, 8, 1, 4] // 遍历队列并删除队列元素 while(!queue.isEmpty()){ Integer remove = queue.remove(); System.out.println("remove = " + remove); } System.out.println("queue = " + queue); } }
1.2 队列的实现
1.2.1 数组模拟队列实现
【示例】顺序队列的实现
public class ArrayQueue<T> { /** * 模拟队列的数组 */ private Object[] elementData; /** * 保存队首的指针 */ private int front; /** * 保存队尾的指针 */ private int rear; /** * 无参构造方法 */ public ArrayQueue() { // 设置数组的默认空间长度为10 this(10); } /** * 有参构造方法 * @param capcaity 设置数组的空间长度 */ public ArrayQueue(int capcaity) { // 处理capcaity参数不合法的情况 if (capcaity < 0) { throw new IllegalArgumentException("参数不合法异常,capcaity:" + capcaity); } // 创建指定空间长度的数组 this.elementData = new Object[capcaity]; } /** * 获取队列中元素的个数 * @return 返回队列中元素的个数 */ public int getSize() { return rear - front; } /** * 入队列操作 * @param element */ public void add(T element) { // 判断队列是否已满 if (isFull()) { throw new RuntimeException("队列已满,无法执行入队列操作"); } // 执行入队操作 elementData[rear++] = element; } /** * 删除队列的首元素 * @return 返回被删除的元素值 */ public T remove() { // 判断队列是否为空 if (isEmpty()) { throw new RuntimeException("队列为空,无法执删除队列操作"); } // 获取队列首元素 T element = (T)elementData[front]; // 把front索引位置元素设置为默认值,并更新front索引值 elementData[front++] = null; // 返回被删除的队首元素 return element; } /** * 获得队列的首元素 * @return 返回队列的首元素 */ public T element() { // 判断队列是否为空 if (isEmpty()) { throw new RuntimeException("队列为空,无法执获取队列操作"); } // 获取并返回队列的首元素 return (T)elementData[front]; } /** * 判断队列是否已满 * @return 队列已满,则返回true;队列未满,则返回false。 */ private boolean isFull() { return rear == elementData.length; } /** * 判断队列是否为空 * @return 队列为空,则返回true;队列不为空,则返回false。 */ public boolean isEmpty() { return front == rear; } }
【示例】循环队列的实现
public class ArrayQueue<T> { /** * 模拟队列的数组 */ private Object[] elementData; /** * 保存队首的指针 */ private int front; /** * 保存队尾的指针 */ private int rear; /** * 无参构造方法 */ public ArrayQueue() { // 设置数组的默认空间长度为10 this(10); } /** * 有参构造方法 * @param capcaity 设置数组的空间长度 */ public ArrayQueue(int capcaity) { // 处理capcaity参数不合法的情况 if (capcaity < 0) { throw new IllegalArgumentException("参数不合法异常,capcaity:" + capcaity); } // 创建指定空间长度的数组 this.elementData = new Object[capcaity]; } /** * 获取队列中元素的个数 * @return 返回队列中元素的个数 */ public int getSize() { if (rear >= front) { return rear - front; } else { return (rear + elementData.length) - front; } } /** * 入栈操作 * @param element */ public void add(T element) { // 判断队列是否已满 if (isFull()) { throw new RuntimeException("队列已满,无法执行入队列操作"); } // 执行入队操作 elementData[rear] = element; // 更新rear的值 rear = (rear + 1) % elementData.length; } /** * 删除队列的首元素 * @return 返回被删除的元素值 */ public T remove() { // 判断队列是否为空 if (isEmpty()) { throw new RuntimeException("队列为空,无法执删除队列操作"); } // 获取队列首元素 T element = (T)elementData[front]; // 把front索引位置元素设置为默认值 elementData[front] = null; // 更新front索引值 front = (front + 1) % elementData.length; // 返回被删除的队首元素 return element; } /** * 获得队列的首元素 * @return 返回队列的首元素 */ public T element() { // 判断队列是否为空 if (isEmpty()) { throw new RuntimeException("队列为空,无法执获取队列操作"); } // 获取并返回队列的首元素 return (T)elementData[front]; } /** * 判断队列是否已满 * @return 队列已满,则返回true;队列未满,则返回false。 */ private boolean isFull() { return front == (rear + 1) % elementData.length; } /** * 判断队列是否为空 * @return 队列为空,则返回true;队列不为空,则返回false。 */ public boolean isEmpty() { return front == rear; } }
1.2.2 链表模拟队列实现
public class LinkedListQueue<T> { /** * 保存队列首节点 */ private QueueNode<T> front; /** * 保存队列尾结点 */ private QueueNode<T> rear; /** * 保存实际存放元素的个数 */ private int size; /** * 获取队列实际存储元素的个数 */ private int getSize() { return size; } /** * 入队列操作 * @param element */ public void add(T element) { // 把需要入队的元素封装成节点对象 // 处理链表为空的情况 if (rear == null) { // 把元素封装成对象,并且只为队尾 rear = new QueueNode<>(element, null); // 更新front的值 front = rear; } // 处理链表非空的情况 else { // 把元素封装成对象,并添加到队尾 rear.next = new QueueNode<>(element, null); // 更新rear的值 rear = rear.next; } // 更新size的值 size++; } /** * 删除队列的首元素 * @return 返回被删除的元素值 */ public T remove() { // 判断队列是否为空 if (isEmpty()) { throw new RuntimeException("队列为空,无法执删除队列操作"); } // 获取队列的首元素值 T element = front.data; // 更新front的指向,让front指向其下一个节点 front = front.next; // 更新size的值 size--; // 返回出队的元素 return element; } /** * 获得队列的首元素 * @return 返回队列的首元素 */ public T element() { // 判断队列是否为空 if (isEmpty()) { throw new RuntimeException("队列为空,无法执获取队列操作"); } // 返回队列首元素 return front.data; } /** * 判断队列是否为空 * @return 如果队列为空,则返回true;如果队列非空,则返回false。 */ public boolean isEmpty() { return size == 0; } /** * 单链表节点类 */ private static class QueueNode<T> { /** * 节点中存放的数据 */ private T data; /** * 指向下一个节点 */ private QueueNode<T> next; /** * 构造方法 */ public QueueNode(T data, QueueNode<T> next) { this.data = data; this.next = next; } } }
3. 栈(Stack)
3.1 栈的简介
栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作,栈的重要特点为:后进先出或先进后出。
栈的一个最重要的特征就是栈的插入和删除只能在栈顶进行,所以每次删除的元素都是最后进栈的元素,故栈也被称为后进先出(Last In First Out)的线性表,简称LIFO表。
Java中有一个类Stack,用于表示栈,但这个类已经过时了。Java中没有单独的栈接口,栈相关方法包括在了表示双端队列的接口Deque中,主要有四个方法:
方法名 | 描述 |
---|---|
void push(E e); | 入栈,在头部添加元素,栈的空间可能是有限的,如果栈满了,会抛出异常。 |
E pop(); | 出栈,返回头部元素,并且从栈中删除,如果栈为空,会抛出异常。 |
E peek(); | 查看栈头部元素,不修改栈,如果栈为空,返回null。 |
boolean isEmpty(); | 判断栈是否为空,如果为空则返回true;否则返回false。 |
【示例】使用LinkedList实现栈操作
/* @author mashushu @date 2023/7/19 @description 栈结构 void push(E e);入栈,在头部添加元素,栈的空间可能是有限的,如果栈满了,会抛出异常。 E pop();出栈,返回头部元素,并且从栈中删除,如果栈为空,会抛出异常。 E peek();查看栈头部元素,不修改栈,如果栈为空,返回null。 boolean isEmpty();判断栈是否为空,如果为空则返回true;否则返回false。 */ public class DequeDemo1 { public static void main(String[] args) { Deque<Integer> deque = new LinkedList<>(); deque.push(11); deque.push(22); deque.push(33); deque.push(44); deque.push(55); System.out.println(deque);//[55, 44, 33, 22, 11] while (!deque.isEmpty()){ System.out.println(deque.pop()); } System.out.println(deque); } }
3.2 栈的实现方式
每个栈都有一个栈顶指针,它初始值为-1,且总是指向最后一个入栈的元素,栈有两种处理方式,即进栈(push)和出栈(pop),因为在进栈只需要移动一个变量存储空间,所以它的时间复杂度为O(1),但是对于出栈分两种情况,栈未满时,时间复杂度也为O(1),但是当栈满时,需要重新分配内存,并移动栈内所有数据,所以此时的时间复杂度为O(n)。以下举例栈结构的两种实现方式,顺序表存储和链表存储。
3.2.1 数组模拟栈实现
要求:需要限制栈的最大容量。 /** * 数组模拟栈(限制最大容量) */ public class ArrayStack<T> { /** * 存放数据的容器 */ private Object[] elementData; /** * 指向栈顶的指针 */ private int top = -1; // 默认值为-1 /** * 保存栈的最大容量 */ private int maxSize; /** * 构造方法 * @param maxSize 设置栈的最大容量 */ public ArrayStack(int maxSize) { this.elementData = new Object[maxSize]; this.maxSize = maxSize; } /** * 入栈操作 * @param value */ public void push(T element) { // 1.判断栈是否已满 if(isFull()) { throw new RuntimeException("栈已满,无法执行入栈操作"); } // 2.添加数据入栈 elementData[++top] = element; } /** * 出栈操作 */ public T pop() { // 1.判断栈是否为空 if(isEmpty()) { throw new RuntimeException("栈为空,无执行出栈操作"); } // 2.获取需要出栈的元素 T value = (T)elementData[top]; // 3.清空栈顶存放的数据 elementData[top] = null; // 4.修改栈顶指针的指向 top--; // 6.返回需要取出的栈顶元素 return value; } /** * 检查栈是否为空 * @return */ public boolean isEmpty() { return top == -1; } /** * 判断栈是否已满 * @return */ private boolean isFull() { return maxSize - 1 == top; } }
3.2.2 链表模拟栈实现
要求:无需限制链表的最大容量。 /** * 链表模拟栈(不限制最大容量) */ public class LinkedListStack<T> { /** * 保存单链表首节点 */ private StackNode<T> topNode; /** * 保存实际添加元素的个数 */ private int size; /** * 入栈操作(链表的头部开始插入) * @param element 需要入栈的数据 */ public void push(T element) { // 1.把element元素封装成节点对象 StackNode<E> node = new StackNode<>(element); // 2.把node节点添加进入链表(把node插入到topNode的前面) // 把node节点的next指向topNode节点 node.next = topNode; // 更新topNode指向node节点 topNode = node; // 3.更新size的值,也就是执行size++操作 size++; } /** * 出栈操作(删除单链表首节点) * @return 返回栈顶的数据 */ public T pop() { // 判断栈是否为空 if(isEmpty()) { throw new RuntimeException("栈为空,无执行出栈操作"); } // 定义tempNode变量,来零时保存headNode节点 StackNode<T> tempNode = topNode; // 更新topNode的值,将topNode.next 改为:topNode topNode = topNode.next; // 完成出栈操作后,size递减 size--; // 返回栈顶的数据 return tempNode.data; } /** * 获取栈顶元素(不会删除栈顶元素) * @return */ public T peek() { // 判断栈是否为空 if (isEmpty()) { return null; } // 返回栈顶数据 return topNode.data; } /** * 判断栈是否为空 * @return 如果栈为空,则返回true;如果栈非空,则返回false。 */ public boolean isEmpty() { return size == 0; } /** * 栈节点类 */ private static class StackNode<T> { /** * 节点中存放的数据 */ private T data; /** * 指向下一个节点 */ private StackNode<T> next; /** * 构造方法 * @param data */ public StackNode(T data, StackNode<T> next) { this.data = data; this.next = next; } } }
4. Iterator迭代器
4.1 Iterator类详解
java中提供了很多个集合,它们在存储元素时,采用的存储方式不同。我们要取出这些集合中的元素,可通过一种通用的获取方式来完成。这样使得对容器的遍历操作与其具体的底层实现相隔离,达到解耦的效果。
Collection 集合元素的通用获取方式:在取元素之前先要判断集合中有没有元素,如果有元素则把元素取出,然后继续再判断下一个元素,如果还有就再取出,直到把集合中的所有元素全部取出为止。这种取出方式专业术语称为迭代。
Collection 集合中把这种取元素的方式描述在 Iterator 接口中,Iterator 接口的常用方法如下:
方法名 | 描述 |
---|---|
boolean hasNext(); | 判断集合中是否有下一个元素可以迭代,如果有,则返回 true。 |
Object next(); | 返回迭代的下一个元素,并把指针向后移动一位。 |
void remove(); | 将迭代器当前返回的元素删除(可选操作)。 |
对于Iterator而言,因为本身是一个接口,所以要想实例化则必须依靠Collection接口完成,也就是使用Collection接口的Iterator<E> iterator()方法。
【示例】使用迭代器遍历ArrayList元素
/* @author mashushu @date 2023/7/19 @description 使用迭代器遍历ArrayList集合 */ public class IteratorDemo1 { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("aa"); list.add("bb"); list.add("cc"); list.add("dd"); list.add("ee"); // 获取迭代器对象 Iterator<String> iterator = list.iterator(); // 使用while循环迭代 while (iterator.hasNext()){ System.out.println(iterator.next()); } System.out.println("--------------------------------"); for( Iterator<String> iterator2 = list.iterator();iterator2.hasNext();){ System.out.println(iterator2.next()); } } }
注意:在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的 next()方法,将会抛出java.util.NoSuchElementException没有集合元素的异常。
如果在迭代过程中,使用了集合的方法对元素进行操作。导致迭代器并不知道集合中的变化,容易引发数据的不确定性,从而会抛出并发修改异常(java.util.ConcurrentModificationException)。
-
Iterator和for-each的区别
(1) for-each不但能遍历集合,还能遍历数组,但是Iterator只能遍历集合。
(2) 使用for-each遍历集合时不能删除元素,使用Iterator遍历集合时,还可以使用Iterator的remove方法删除元素。
4.2 Iterator的实现原理
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
Java中的Iterator功能比较简单,并且只能单向移动(从前往后):
-
使用iterator()方法要求容器返回一个Iterator,iterator()方法是java.lang.Iterable接口的抽象方法。
-
使用hasNext()检查序列中是否还有元素。
-
使用next()获得序列中的下一个元素。
【示例】模拟ArrayList的Iterator实现
// 集合类实现Iterable接口 class MyArrayList implements Iterable { // 存放数据的数组 private Object[] elementData = {"a", "b", "c", "d", "e"}; // 数组的长度 private int size = elementData.length; // 获取元素方法 public Object get(int index) { // 断索引是否越界,请字节处理,此处是阉割版。。。 return elementData[index]; } // 获取迭代器 @Override public Iterator iterator() { return new MyIterator(0); } // 匿名内部类,实现容器的迭代器功能 private class MyIterator implements Iterator { // 游标,表示下一个元素的索引位置 private int cursor; // 构造方法 public MyIterator() { this(0); } public MyIterator(int cursor) { this.cursor = cursor; } // 判断迭代器是否有下一个元素 @Override public boolean hasNext() { return cursor != size; } // 获取下一个元素 @Override public Object next() { // 获取集合中当前游标对应的元素 Object value = get(cursor); // 游标累加,保存下一个元素的索引位置 cursor++; // 返回元素值 return value; } } }
4.3 ListIterator类详解
ListIterator 是 Iterator 的子接口,但它比 Iterator 更加的强大,提供了更多在迭代过程中使用的方法,ListIterator 接口常用方法:
方法名 | 描述 |
---|---|
boolean hasNext(); | 以正向遍历列表时,判断迭代器后面是否还有元素。 |
Object next(); | 返回列表中ListIterator指向位置后面的元素。 |
boolean hasPrevious(); | 以逆向遍历列表,判断迭代器前面是否还有元素。 |
Object previous(); | 返回列表中ListIterator指向位置前面的元素。 |
void remove(); | 从列表中删除next()或previous()返回的最后一个元素。 |
void set(Object e); | 从列表中将next()或previous()返回的元素更改为指定元素。 |
void add(Object e); | 将指定的元素插入列表,插入位置为迭代器当前位置之前。 |
ListIterator接口获取方式:
public ListIterator<E> listIterator();返回列表中的列表迭代器,游标位置默认为0。
public ListIterator<E> listIterator(final int index);从列表中的指定位置开始,返回列表中的元素(游标位置为index)的列表迭代器。
【示例】 ListIterator迭代ArrayList元素
import java.util.ArrayList; import java.util.ListIterator; /* @author mashushu @date 2023/7/19 @description 使用ListIterator 迭代 List集合中的元素 */ public class IteratorDemo4 { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); list.add(11); list.add(22); list.add(33); list.add(44); list.add(55); // 创建listIterator迭代器对象 ListIterator<Integer> listIterator1 = list.listIterator(); // 迭代遍历: 从前往后 while (listIterator1.hasNext()) { System.out.println(listIterator1.next()); } System.out.println("----------------------------------------"); // 创建listIterator迭代器对象 list.size() : 指定迭代器在集合的最大索引处 // 注意:传入集合的长度,最大取到集合的长度-1 ListIterator<Integer> listIterator2 = list.listIterator(list.size()); // 迭代遍历: 从后往前 while (listIterator2.hasPrevious()) { System.out.println(listIterator2.previous()); } } }
通过 ListIterator 迭代器来操作元素,ListIterator接口允许程序员按照任一方向遍历列表,并且允许迭代期间操作集合中的元素。
【示例】迭代过程中对ArrayList元素操作
import java.util.ArrayList; import java.util.ListIterator; /* @author mashushu @date 2023/7/19 @description 使用ListIterator 在迭代过程中修改元素 */ public class IteratorDemo5 { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); list.add(11); list.add(22); list.add(33); list.add(44); list.add(55); // 创建listIterator迭代器对象 ListIterator<Integer> listIterator1 = list.listIterator(); // 迭代遍历: 从前往后 while (listIterator1.hasNext()) { listIterator1.add(66);// 每次添加的元素会存入迭代器指针的左边 System.out.println(listIterator1.next()); listIterator1.set(77); // 将next() 或者 previous() 方法返回的值更改为: 77 } System.out.println("list = " + list); } }
Iterator和ListIterator的区别
-
Iterator应用于Set和List集合及其子类型,但是ListIterator只能用于List及其子类型。
-
Iterator和ListIterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。
-
Iterator和ListIterator都有remove()方法,但是ListIterator还有add()方法和set()方法。