持有对象(3):迭代器、LinkedList、Stack

一、迭代器

    任何容器类,都必须有某种方式可以插入元素并将它们再次取回。毕竟,持有事物是容器最基本的工作。对于List,add()是插入元素的方法之一,而get()是取出元素的方法之一。

    如果从更高层的角度思考,会发现这里有个缺点:要使用容器,必须对容器的确切类型编程。初看起来没什么不好,但是考虑下面的情况:如果原本是对着List编码的,但是后来发现如果能够把相同的代码应用于Set,将会显得非常方便,此时应该怎么做?或者打算从头开始编写通用的代码,它们只是使用容器,不知道或者说不关心容器的类型,那么如何才能不重写代码就可以应用于不同类型的容器?

    迭代器(也是一种设计模式)的概念可以用于达成此目的。迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。此外,迭代器通常被称为轻量级的对象:创建它的代价小。因此,经常可以见到对迭代器有些奇怪的限制;例如,java的Iterator只能单向移动,这个Iterator只能用来:

  1. 使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
  2. 使用next()获得序列中的下一个元素。
  3. 使用hasNext()检查序列中是否还有元素。
  4. 使用remove()将迭代器新近返回的元素删除。

    下面的例子演示了迭代器的基本使用方式。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class SimpleIteration {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("qwer");
		list.add("asfd");
		list.add("zxcv");
		list.add("1234");
		Iterator<String> iterator = list.iterator();
		while (iterator.hasNext()) {
			System.out.print(iterator.next() + ", ");
		}
		System.out.println();
		for (String string : list) {
			System.out.print(string + ", ");
		}
		System.out.println();
		iterator = list.iterator();
		for (int i = 0; i < 2; i++) {
			iterator.next();
			iterator.remove();
		}
		System.out.println(list);
	}
}

    有了Iterator就不必为容器中元素的数量操心了,那是由hasNext()和next()关心的事情。如果你只是向前遍历List,并不打算修改List对象本身,那么你可以看到foreach语法会显得更加简洁。Iterator还可以移除由next()产生的最后一个元素,这意味着在调用remove()之前必须先调用next()。

    现在考虑创建一个display()方法,它不必知晓容器的确切类型:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.TreeSet;

public class CrossContainerIteration {
	public static void display(Iterator<String> i) {
		while (i.hasNext()) {
			System.out.print(i.next() + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		String[] str = { "qwer", "asdf", "zxcv", "1234" };
		ArrayList<String> arrayList = new ArrayList<>(Arrays.asList(str));
		LinkedList<String> linkedList = new LinkedList<>(Arrays.asList(str));
		HashSet<String> hashSet = new HashSet<>(Arrays.asList(str));
		TreeSet<String> treeSet = new TreeSet<>(Arrays.asList(str));
		display(arrayList.iterator());
		display(linkedList.iterator());
		display(hashSet.iterator());
		display(treeSet.iterator());
	}
}

    请注意,display()方法不包含任何有关它所遍历的序列的类型信息,而这也展示了Iterator的真正威力:能够将遍历序列的操作与序列底层的结构分离。正由于此,我们有时会说:迭代器统一了对容器的访问方式。

二、ListIterator

    ListIterator是一个更加强大的Iterator的子类型,它只能用于各种List类的访问。尽管Iterator只能向前移动,但是ListIterator可以双向移动。它还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素。你可以通过调用listIterator()方法产生一个指向List开始处的ListIterator,并且开可以通过调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。下面的示例演示了所有这些能力:

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class ListIteration {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("qwer");
		list.add("asdf");
		list.add("zxcv");
		list.add("1234");
		ListIterator<String> it = list.listIterator();
		while (it.hasNext())
			System.out.println(it.next() + ", " + it.nextIndex() + ", " + it.previousIndex());
		while (it.hasPrevious())
			System.out.print(it.previous() + " ");
		System.out.println();
		System.out.println(list);
		it = list.listIterator(2);
		while (it.hasNext()) {
			it.next();
			it.set("0000");
		}
		System.out.println(list);
	}
}

三、LinkedList

    LinkedList也像ArrayList一样实现了基本的List接口,但是它执行某些操作(在List的中间插入和移除)时比ArrayList更高效,但在随机访问操作方面却要逊色一些。

    LinkedList还添加了可以使其用作栈、队列和双端队列的方法。这些方法中有些彼此之间只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用(特别是在Queue中)。例如,getFirst()和element()完全一样,它们都返回列表的头(第一个元素),而并不移除它,如果List为空,则抛出NoSuchElementException。peek()方法与这两个方式只是稍有差异,它在列表为空时返回null。

    removeFirst()与remove()也是完全一样的,它们移除并返回列表的头,而在列表为空时抛出NoSuchElementException。poll()也是稍有差异,它在列表为空时返回null。

    addFirst()与add()和addLast()相同,它们都将某个元素插入到列表的尾(端)部。

    removeLast()移除并返回列表的最后一个元素。

    下面的示例展示了这些特性之间基本的相同性和差异性:

import java.util.LinkedList;

public class LinkedListFeatures {
	public static void main(String[] args) {
		LinkedList<String> list = new LinkedList<>();
		list.add("qwer");
		list.add("asdf");
		list.add("zxvc");
		list.add("1234");
		System.out.println(list);
		// 相同的
		System.out.println("list.getFirst():" + list.getFirst());
		System.out.println("list.element():" + list.element());
		// 在空列表时不同
		System.out.println("list.peek():" + list.peek());
		// 删除和返回第一个元素
		System.out.println("list.remove():" + list.remove());
		System.out.println("list.removeFirst():" + list.removeFirst());
		// 只在空列表时不同
		System.out.println("list.poll():" + list.poll());
		System.out.println(list);
		list.addFirst("0000");
		System.out.println("list.addFirst():" + list);
		list.offer("1111");
		System.out.println("list.offer():" + list);
		list.add("2222");
		System.out.println("list.add():" + list);
		System.out.println("list.removeLast()" + list.removeLast());
	}
}

    如果你浏览一下Queue接口就会发现,它在LinkedList的基础上添加了element()、offer()、peek()、poll()和remove()方法,以使其可以成为一个Queue的实现。

四、Stack

    “栈”通常是指“后进先出”(LIFO)的容器。有时栈也被称为叠加栈,因为最后“压入”栈的元素,第一个“弹出”栈。经常用来类比栈的事物是装有弹簧的储放器中的自助餐托盘,最后装入的托盘总是最先拿出使用的。

    LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用。不过,有时一个真正的“栈”更能把事情讲清楚:

import java.util.LinkedList;

public class Stack<T> {
	private LinkedList<T> storage = new LinkedList<>();

	public void push(T v) {
		storage.addFirst(v);
	}

	public T peek() {
		return storage.getFirst();
	}

	public T pop() {
		return storage.removeFirst();
	}

	public boolean empty() {
		return storage.isEmpty();
	}

	public String toString() {
		return storage.toString();
	}
}

    这里通过使用泛型,引入了在栈的类定义中最简单的可行示例。类名之后的<T>告诉编译器这将是一个参数化类型,而其中的类型参数,即在类被使用时将会被实际类型替换的参数,就是T。大体上,这个类是在声明“我们在定义一个可以持有T类型对象的Stack。”Stack是用LinkedList实现的,而LinkedList也被告知它将持有T类型对象。注意,push()接受的是T类型的对象,而peek()和pop()将返回T类型对象。peek()方法将提供栈顶元素,但是并不将其从栈顶移除,而pop()将移除并返回栈顶元素。

    如果你只需要栈的行为,这里使用继承就不合适了,因为这样会产生具有LinkedList的其他所有方法的类。

    下面演示了这个新的Stack类:

public class StackTest {
	public static void main(String[] args) {
		Stack<String> stack = new Stack<>();
		for (String s : "A B C D E".split(" ")) {
			stack.push(s);
		}
		while (!stack.empty()) {
			System.out.print(stack.pop() + " ");
		}
	}
}

    java.util.Stack:当数组默认的容量发生改变时,pop、push的性能会有较大降低;第二个:这个类是java.util.Vector的子类,由于是继承,stack就会将父类的方法继承过来。如:add(int index,E element)等(具体见Vector的API说明)。这个破坏了stack约定的规则(只能从栈顶进,栈顶出)。所以SUN公司自己也给出的解释就是不要轻易用这个类。

    尽管已经有了 java.util.Stack,但是栈最好是通过链表实现。所以LinkedList可以产生更好的Stack。

 如果本文对您有很大的帮助,还请点赞关注一下。

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

猜你喜欢

转载自blog.csdn.net/qq_40298351/article/details/104370138
今日推荐