持有对象(5):Queue、Collection和Iterator

一、Queue

    队列是一个典型的先进先出(FIFO)的容器。即从容器的一端放入事物,从另一端取出,并且事物放入容器的顺序与取出的顺序是相同的。队列常被当作一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要,因为它们能可以安全地将对象从一个任务传输给另一个任务。下面是Queue接口提供的方法:

public interface Queue<E> extends Collection<E> {
   
    boolean add(E e);

   
    boolean offer(E e);

    
    E remove();

   
    E poll();

    
    E element();

   
    E peek();
}

    LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现。通过将LinkedList向上转型为Queue,下面的示例使用了在Queue接口中与Queue相关的方法:

import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;

public class QueueDemo {
	public static void printQ(Queue queue) {
		while (queue.peek() != null) {
			System.out.print(queue.remove() + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		Queue<Integer> queue = new LinkedList<>();
		Random r = new Random(47);
		for (int i = 0; i < 10; i++)
			queue.offer(r.nextInt(i + 10));
		printQ(queue);
		Queue<Character> qc = new LinkedList<>();
		for (char c : "abcdefg".toCharArray())
			qc.offer(c);
		printQ(qc);
	}
}

    offer()方法是与Queue相关的方法之一,它在允许的情况下,将一个元素插入到队尾,或者返回false。peek()和element()都将在不移除的情况下返回队头,但是peek()方法在队列为空时返回null,而element()会抛出NoSuchElementException异常。poll()和remove()方法将移除并返回队头,但是poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常。

    自动包装机制会自动地将nextInt()方法的int结果转换为queue所需的Integer对象,将char c转换为qc所需的Character对象。Queue接口窄化了对LinkedList的方法的访问权限,以使得只有恰当的方法才可以使用,因此,你能够访问的LinkedList的方法会变少(这里你实际上可以将queue转型回LinkedList,但是至少我们不推荐这么做)。

    注意,与Queue相关的方法提供了完整而独立的功能。即,对于Queue所继承的Collection,在不需要使用它的任何方法的情况下,就可以拥有一个可用的Queue。

二、PriorityQueue

    先进先出描述了最典型的队列规则。队列规则是指在给定一组队列中的元素的情况下,确定下一个弹出队列的元素的规则。先进先出声明的是下一个元素应该是等待时间最长的元素。

    优先级队列声明下一个弹出元素是最需要的元素(具有较高的优先级)。例如,在飞机场,当飞机临近起飞时,这架飞机的乘客可以在办理登记手续时排到队头。如果构建了一个消息系统,某些消息比其他消息更重要,因而应该更快地得到处理,那么它们何时得到处理就与它们何时到达无关。PriorityQueue添加到java SE5中,是为了提供这种行为的一种自动实现。

    当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然顺序,但是你可以通过提供自己的Comparator来修改这个顺序。PriorityQueue可以确保当你调用peek()、poll()和remove()方法时,获取的元素将是队列中优先级最高的元素。

    让PriorityQueue与Integer、String和Character这样的内置类型一起工作易如反掌。在下面的示例中,第一个值集与前一个示例中的随机值相同,因此你可以看到它们从PriorityQueue中弹出的顺序与前一个示例不同:

import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;

public class PriorityQueueDemo {
	public static void printQ(Queue queue) {
		while (queue.peek() != null) {
			System.out.print(queue.remove() + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		Queue<Integer> queue = new PriorityQueue<>();
		Random r = new Random(47);
		for (int i = 0; i < 10; i++)
			queue.offer(r.nextInt(i + 10));
		printQ(queue);
		Queue<Character> qc = new PriorityQueue<>();
		for (char c : "abcdabcefg".toCharArray())
			qc.offer(c);
		printQ(qc);

		Queue<Integer> qp = new PriorityQueue<>((o1, o2) -> {
			return o2 - o1;
		});
		for (int i = 0; i < 10; i++)
			qp.offer(r.nextInt(i + 10));
		printQ(qp);
	}
}

    你可以看到,重复是允许的,最小的值拥有最高的优先级(如果是String,空格也可以算作值,并且比字母优先级高)。并且你可以在PriorityQueue构造器中自己定义Comparator对象来改变排序。

    Integer、String和Character可以与PriorityQueue一起工作,因为这些类已经内建了自然排序。如果你想在PriorityQueue中使用自己的类,就必须包括额外的功能以产生自然排序,或者必须提供自己的Comparator。

三、Collection和Iterator

    Collection是描述所有序列容器的共性的根接口,它可能会被认为是一个“附属接口”,即因为要表示其他若干个接口的共性而出现的接口。另外,java.util.AbstractCollection类提供了Collection的默认实现,使得你可以创建AbstractCollection的子类型,而其中没有不必要的代码重复。

    使用接口描述的一个理由是它可以使我们能够创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多的对象类型。因此,如果我编写的方法将接受一个Collection,那么该方法就可以应用于任何实现了Collection的类--这也就使得一个新类可以选择去实现Collection接口,以便我的方法可以使用它。但是,有一点很有趣,就是我们注意到标准C++类库中并没有其容器的任何公共基类--容器之间的所有共性都是通过迭代器达成的。在java中,遵循C++的方式看起来似乎很明智,即用迭代器而不是Collection来表示容器之间的共性。但是,这两种方法绑定到了一起,因为实现Collection就意味着需要提供iterator()方法:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

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

	public static void display(Collection<String> colleciton) {
		for (String string : colleciton) {
			System.out.print(string + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		String str = "A B C D E F G";
		List<String> list = new ArrayList<>();
		Collections.addAll(list, str.split(" "));
		Set<String> set = new HashSet<>();
		Collections.addAll(set, str.split(" "));
		display(list);
		display(set);
		display(list.iterator());
		display(set.iterator());
		Map<String, String> map = new HashMap<>();
		map.put("1", "A");
		map.put("2", "B");
		map.put("3", "C");
		map.put("4", "D");
		display(map.values());
		display(map.values().iterator());
	}
}

    两个版本的display()方法都可以使用Map或Collection的子类型来工作,而且Collection接口和Iterator都可以将display()方法与底层容器的特定实现解耦。

    在本例中,这两种方式都可以奏效。事实上,Collection要更方便一点,因为它是Iterable类型,因此,在display(Collection)实现中,可以使用foreach结构,从而使代码更加清晰。

    当你要实现一个不是Collection的外部类时,由于让它去实现Collection接口可能非常困难或麻烦,因此使用Iterator就会变得非常吸引人。例如,如果我们通过继承一个持有String对象的类来创建一个Collection的实现,那么我们必须实现所有的Collection方法,即使我们在display()方法中不必使用它们,也必须如此。尽管这可以通过继承AbstractCollection而很容易地实现,但是你无论如何还是要被强制去实现iterator()和size(),以便提供AbstractCollection没有实现:

import java.util.AbstractCollection;
import java.util.Iterator;

public class CollectionSequence extends AbstractCollection<String> {

	private String[] str = { "A", "B", "C", "D", "E" };

	@Override
	public Iterator<String> iterator() {
		return new Iterator<String>() {
			private int index = 0;

			@Override
			public boolean hasNext() {
				return index < str.length;
			}

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

			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}

	@Override
	public int size() {
		return str.length;
	}

	public static void main(String[] args) {
		CollectionSequence c = new CollectionSequence();
		InterfaceVsIterator.display(c);
		InterfaceVsIterator.display(c.iterator());
	}
}

    remove()方法是一个“可选操作”,这里不必实现它,如果你调用它,它会抛出异常。

    从本例中,你可看到,如果你实现Collection,就必须实现Iterator()并且只拿实现iterator()与继承AbstractCollection相比,话费的代价只有略微减少。但是,如果你的类已经继承了其他类,那么你就不能再继承AbstractCollection了。在这种情况下,要实现Collection,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力就会显得容易多了:

import java.util.Iterator;

class StringSequence {
	protected String[] str = { "A", "B", "C", "D", "E" };
}

public class NonCollectionSequence extends StringSequence {
	public Iterator<String> iterator() {
		return new Iterator<String>() {

			private int index = 0;

			@Override
			public boolean hasNext() {
				return index < str.length;
			}

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

			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}

	public static void main(String[] args) {
		NonCollectionSequence nc = new NonCollectionSequence();
		InterfaceVsIterator.display(nc.iterator());
	}
}

    生成Iterator是将队列与消息队列的方法连接在一起耦合度最小的方式,并且与实现Collection相比,它在序列类上所施加的约束也少得多。

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

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

猜你喜欢

转载自blog.csdn.net/qq_40298351/article/details/104406168