为何使用PriorityQueue需实现Comparable?遍历顺序为何异于ArrayList?
嘿,我来帮你把这两个关于Java PriorityQueue的疑问彻底搞清楚,结合你的代码和运行结果来说明会更直观~
疑问1:为什么使用PriorityQueue必须实现Comparable接口?
其实准确来说,不是必须实现Comparable接口,而是你没有给PriorityQueue指定自定义的比较器(Comparator)时,它默认会依赖元素自身的Comparable实现来确定优先级。
PriorityQueue的核心是维护一个二叉堆结构,堆的工作逻辑必须知道元素之间的优先级顺序——哪个元素应该放在堆顶(也就是队列的队首),哪个元素该在下层。如果你的元素既没实现Comparable,又没在创建PriorityQueue时传入Comparator,JVM根本不知道怎么比较两个Book对象的优先级,所以调用add()方法时会直接抛出ClassCastException,告诉你无法把对象转换为Comparable类型。
举个例子,你完全可以不用让Book实现Comparable,而是创建队列时指定比较器:
Queue<Book> queue = new PriorityQueue<>(Comparator.comparingInt(Book::getId));
这样PriorityQueue就会用这个Comparator来判断Book的优先级,不需要Book类实现Comparable接口。
你之前用Comparable/Comparator给数组、列表排序,是主动触发排序操作;但PriorityQueue是在元素添加、删除时自动维护堆的优先级,这个过程必须有比较规则,所以要么元素自己实现Comparable,要么给队列传Comparator,二者选其一就行。
疑问2:为什么PriorityQueue遍历只有队首是最小id,而ArrayList排序后全有序?
这是因为PriorityQueue和ArrayList的排序逻辑完全不同:
PriorityQueue的内部结构是二叉堆:
它只保证堆顶元素(队首)是优先级最高(这里是id最小)的元素,底层的数组存储并不是全局有序的。你用for-each遍历的时候,其实是直接遍历底层的数组,所以会看到除了队首,其他元素的顺序是“乱”的——这堆元素只是满足堆的特性(每个父节点的优先级高于子节点),但不是完全有序的序列。
比如你的遍历结果里,队首是101(最小id),但后面的191、121这些元素的顺序并不符合id升序,这完全正常,因为堆的存储不需要全局有序,只要保证堆顶是最小的就行。ArrayList的Collections.sort是全排序:
Collections.sort会对整个列表进行完全排序,把所有元素按指定的Comparable规则排成一个有序序列,所以遍历的时候自然是全有序的。
如果想得到PriorityQueue的有序元素,正确的做法是每次调用poll()方法取出队首元素(每次poll后,堆会重新调整,下一个队首就是下一个优先级的元素),比如:
System.out.println("有序输出PriorityQueue元素:"); while (!queue.isEmpty()) { Book b = queue.poll(); System.out.println(b.id+" "+b.name+" "+b.author+" "+b.publisher+" "+b.quantity); }
这样输出的结果就会和ArrayList排序后的结果一致,是按id升序排列的。
再看你代码里queue.remove()之后的遍历结果,新的队首变成了121(剩下元素里最小的id),这也符合堆的特性——删除堆顶元素后,堆会重新调整,把下一个最小的元素放到堆顶。
内容的提问来源于stack exchange,提问作者elvis

