13.1 集合接口
本节将介绍Java结合框架的基本设计,展示使用他们的方法,并解释一些颇具争议的特性背后的考虑。
13.1.1 将集合的接口与实现分离
队列接口指出可以在队列的尾部添加元素,在队列的头部删除元素,并且可以查找队列中元素的个数。
队列的实现方式一般有两种:一种是循环数组;另一种的链表实现。
接口集合与实现在概念上的不同:如果需要一个循环数组队列->ArrayDeque类来实现;如果需要一个
链表队列,就直接使用->LinkeList类。这两个类实现了Queue接口。
我们说,接口的本身并不能说明哪种实现的效率高,循环数组要比链表更高效,但是容量有限,链表
相反。如果程序中要收集的对象数量没有上限,最好使用链表来实现。
13.1.2 Java类库中的集合接口和迭代器接口
集合类的基本接口就是Collection接口。
public interface Collection<E>{ boolean add(E element); Iterator<E> iterator(); ... }
add 方法用于向集合中添加元素。添加元素成功返回ture,未成功【集合没有发生变化】返回false。例如
向集合中添加一个对象,而这个对象在集合中已经存在,这个添加没有成功,因为集合中不允许有重复的对象。
iterator 方法用于返回一个 实现了Iterator接口的对象。可以使用这个迭代器对象依次访问集合中的元素。
1)迭代器
Iterator接口包含3个方法:
public interface Iterator<E>{ E next(); boolean hasNext(); void remove(); }
因此,需要在调用next之前调用hasNext方法。
hasNext 方法 用于检查序列中是否还有元素。
Collection<String> c = ...; Iterator<String> iter = c.iterator(); while(iter.hasNext()){ String element = iter.next(); do something with element ... }
注:
从Java SE 5.0 起,这个循环可以采用一种更加优雅的缩进方法。
for(String element :c){ do something with element ... }
Collection接口扩展了Iterable接口。因此,对于标准库中的任何集合都可以使用"for each" 循环。
元素的访问的顺序取决于集合类型。ArrayList迭代,迭代器从索引 0 开始,每迭代一次,索引值 加1。
HashSet迭代之后,每个元素将会按照 某种随机的次序出现。但是对于计算总和 或者 统计某个条件的
元素个数这类与顺序无关来说不是什么问题。
2)删除元素
remove 方法用于删除上次调用next方法是返回的元素。如果想要删除指定位置上的元素,仍然需要越过这个元素。
if.remove(); it.remove();//错误 //删除两个相邻的元素 it.remove(); it.next(); it.remove();//正确
下面十一检测任意集合是否包含指定元素的泛型方式
//这样做的好处就是不必自己重新构建这些方法了。contains就是这样一个方法 public static <E> boolean contains(Collection<E> c,Object obj){ for(E element: c) if(element.equals(obj)) return true; return false; }
int size(); boolean isEmpty(); boolean contains(Object obj); boolean containsAll(Collection<?> c) boolean equals(Object other); boolean addAll(Collection<? extends E> from); boolean remove(Object obj); boolean removeAll(Collection<?> c>; void clear();<pre name="code" class="java">如果我们在实现每个类都要提供如此多的例行方法将是很麻烦的。为了实现者能够更容易地实
现这个接口,java库类提供了一个类AbstractCollection,它将基础方法size() 、iterator()抽象化。
public abstract class AbstractCollection<E> implements Collection<E>{ ... public abstract Iterator<E> iterator(); public boolean contains(Object obj){ for(E element: this)//迭代器 iterator if(element.equals(obj)) return true; return false; } ... } boolean retainAll(Collection<?> c); Object[] toArray(); <T> T[] toArray(T[] arrayToFill);
是由AbstractCollection超类提供。如果子类有更加有效的方式去实现contains方法也可以由子类提供,这点
没有任何限制。
对于类框架来说,这个设计很好。集合类用户可以使用泛型接口(Collection 、Iterator等)中一组更加丰富的方法,而实际的数据结构
实现者并没有需要实现所有例行方法的负担。
13.2.1 链表
由于最近数据结构老师在将链表,对于c实现的链表的创建、查找、删除、略微懂,这里不说了。
链表 和 顺序表 相比,优势在于在一组数据中间添加和删除一个元素的代价。
下面代码实例中,先添加3个元素,再将第二个元素删除:
package com.test1; import java.util.*; /** * linkedList 链表 * 一些需要注意的点: * 1.链表与泛型集合的重要区别:链表是ordered collection 每个对象的位置很重要 * 2.链表的缺点是不能高效的随机访问对象元素,虽然LinkedList类提供了get方法,但其效率不敢恭维 * 3.使用链表的唯一理由就是 减少在列表中间插入或删除元素所付出的代价 * 4.List 中只有少数几个元素 我们可以使用ArrayList * @author PeersLee * 该代码首先创建两个链表,合并,然后从第二个链表中每隔一个元素删除一个元素,最后测试removeAll */ public class test2 { public static void main(String[] args) { // TODO Auto-generated method stub List<String> staff = new LinkedList<>(); staff.add("A"); staff.add("B"); staff.add("C"); ListIterator<String> iter = staff.listIterator(); String first = iter.next(); System.out.println("first:"+first); String second = iter.next(); System.out.println("second:"+second); //注意此时挡板在 AB|C iter.previous();//此方法和next()一样,返回刚越过的对象 //A|BC String testString = iter.previous(); //|ABC System.out.println("testString:"+testString); int ni = iter.nextIndex(); System.out.println("ni:"+ni); int pi = iter.previousIndex(); System.out.println("pi:"+pi); //ni = 0 //pi =-1 //说明游标在初始位置 } }
链表与泛型集合之间有一个重要的区别。链表有序,对象的位置很重要。LinkedList.add() 方法将对象添加到链表的尾
部。如果将元素添加到链表的中间时,由于迭代器是描述集合中位置的,所以这种依赖位置的add()方法将由迭代器负
责。只有对自然有序的集合使用迭代器添加元素才有实际意义。集(set)类型,其中元素完全无序,因此,在Iterator
接口中就没有add()方法。然而子接口ListIterator包含add()方法[这个方法不返回boolean类型的值,它假定添加操作总
会改变链表],并且还可以反向的遍历链表。
下面的代码实现ListIterrator中的一些方法:
package com.test1; import java.util.*; public class test2 { public static void main(String[] args) { // TODO Auto-generated method stub List<String> staff = new LinkedList<>(); staff.add("A"); staff.add("B"); staff.add("C"); ListIterator<String> iter = staff.listIterator(); String first = iter.next(); System.out.println("first:"+first); String second = iter.next(); System.out.println("second:"+second); //注意此时挡板在 AB|C iter.previous();//此方法和next()一样,返回刚越过的对象 //A|BC String testString = iter.previous(); //|ABC System.out.println("testString:"+testString); int ni = iter.nextIndex(); System.out.println("ni:"+ni); int pi = iter.previousIndex(); System.out.println("pi:"+pi); //ni = 0 //pi =-1 //说明游标在初始位置 } <span style="font-size:18px;">}</span>
虽然链表随机访问元素的效率十分低,但是LinkedList还是提供了一个 get();方法,当然,如果你使用了这个方法
就说明你选择错了数据结构。
使用链表的唯一理由就是尽可能降低 在列表中间插入或者删除元素 的代价,如果链表只有几个元素,完全可以
使用ArrayList()。
下面的代码是通过调用AbstractCollection类中的toString方法打印出链表a中的所有元素:
package com.test1; import java.util.*; public class test3 { public static void main(String[] args) { // TODO Auto-generated method stub List<String> a = new LinkedList<>(); a.add("A"); a.add("B"); a.add("C"); List<String> b = new LinkedList<>(); b.add("D"); b.add("E"); b.add("F"); b.add("G"); //将b中的对象移动到 a中 ListIterator<String> aIter = a.listIterator(); Iterator<String> bIter = b.iterator(); while(bIter.hasNext()){ if(aIter.hasNext()) aIter.next(); //个人理解 游标在新插入对象右边 aIter.add(bIter.next()); } System.out.println(a); System.out.println(b); //删除位置代号是偶数的对象(坐标从‘1’开始) bIter = b.iterator(); while(bIter.hasNext()){ bIter.next(); if(bIter.hasNext()){ bIter.next(); bIter.remove(); } } System.out.println(b); //从链表a中将b中的所有元素删除 a.removeAll(b); System.out.println(a); } }
13.2.2 数组列表
13.2.3 散列表
package com.test3; import java.util.*; /** * 散列集 * 如果不在乎元素的顺序,则有几种能够快速查找元素的数据结构,他们是按照有利于其操作目的的原则组织数据 * HashTable 散列表 快速查找所需要的对象 * 散列码 由 hashCode 函数得出 由对象的实例域产生的一个整数 * 每个列表被称为一个 bucket 桶 * 要计算出对象的位置就要就算出散列码,与桶的总数取余,所得的结果就是保存这个元素的一个引索 * * @author PeersLee * *该程序实现 读取输入的所有单词,将他们添加到散列集中,遍历,打印出数量 */ public class SetTest { public static void main(String[] args) { // TODO Auto-generated method stub //HashSet 实现Set 其底层实现为HashMap Set<String> words = new HashSet<>(); long totalTime = 0; Scanner in = new Scanner(System.in); /* * hasNext() * Returns true if this scanner has another token in its input. */ int n = 1; while(in.hasNext() && n <= 20) { /* * next() * Finds and returns the next complete token from this scanner. */ String word = in.next(); /* * currentTimeMillis() * Returns the current time in milliseconds. */ long callTime = System.currentTimeMillis(); words.add(word); callTime = System.currentTimeMillis() - callTime; totalTime += callTime; n++; } Iterator<String> iter = words.iterator(); for(int i = 1; i <= 20 && iter.hasNext(); i++) { System.out.println(iter.next()); System.out.println("..."); System.out.println(words.size() + " distinct words(不同的词) " + totalTime + " milliseconds."); } } }
13.2.4 树集 TreeSet
13.2.5 对象的比较
目录:
TreeSetTest:
package com.test1; import java.util.*; /** * 1.创建了两个Item对象的树集 * 2.第一个按照部件的编号排序,这是 Item 对象的默认顺序 * 3.第二个是通过定制的比较器来按照描述信息排序 * @author PeersLee * */ public class TreeSetTest { public static void main(String[] args) { // TODO Auto-generated method stub /** * SortedSet是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的。 * TreeSet类实现Set 接口,该接口由TreeMap 实例支持。此类保证排序后的 set 按照升序排列元素 */ SortedSet<Item> parts = new TreeSet<>(); parts.add(new Item("Toaster[烤面包片机]", 1234)); parts.add(new Item("Widget[装饰品]", 4567)); parts.add(new Item("Modem[调制解调器]", 6789)); System.out.println("parts: " + parts); SortedSet<Item> sortByDescription = new TreeSet<>(new Comparator<Item>() { public int compare(Item a, Item b) { String descrA = a.getDescription(); String descrB = b.getDescription(); return descrA.compareTo(descrB); } }); sortByDescription.addAll(parts); System.out.println(sortByDescription); } }
Item
package com.test1; import java.util.*; public class Item implements Comparable<Item> { //partNumber -> 零件号码 private String description; private int partNumber; // construct an item. public Item(String aDescription, int aPartNumber) { description = aDescription; partNumber = aPartNumber; } //Gets public String getDescription() { return description; } public String toString() { return "[description=" + description +", partNumber=" + partNumber +"]"; } public boolean equals(Object otherObject) { if(this == otherObject) return true; if(otherObject == null) return false; if(getClass() != otherObject.getClass()) return false; Item other = (Item)otherObject; return Objects.equals(description, other.description) && partNumber == other.partNumber; } public int hashCode() { return Objects.hash(description, partNumber); } public int compareTo(Item other) { return Integer.compare(partNumber, other.partNumber); } }