Java编程思想 | 第8章 对象的容纳

懵懂的女人 提交于 2020-12-07 20:12:43

8.1 集合类

  • 1、集合(Collection):一组单独的元素,通常应用了某种规则。在这里,一个List(列表)必须按特定的顺序容纳元素,而一个Set(集)不可包含任何重复的元素。
  • 2、映射(Map):一系列"键 - 值"对。从表面看,这似乎应该成为一个"键 - 值"对的"集合",但假若视图按照那种方式实现它,就会发现实现过程相当笨拙。这进一步证明了应该分离成单独的概念。另一方面,可以方便地查看 Map 的某个部分。只需创建一个集合,然后用它表示那一部分即可。这样一来,Map 就可以返回自己键的一个Set、一个包含自己值的List 或者包含自己“键-值”对的一个List。和数组相似,Map 可方便扩充到多个“维”,毋需涉及任何新概念。只需简单地在一个Map 里包含其他 Map(后者又可以包含更多的Map,以此类推)。

虚线框代表“接口”,点线框代表“抽象”类,而实线框代表普通(实际)类。点线箭头表示一个特定的类准备实现一个接口(在抽象类的情况下,则是“部分”实现一个接口)。双线箭头表示一个类可生成箭头指向的那个类的对象。例如,任何集合都可以生成一个反复器(Iterator),而一个列表可以生成一个ListIterator(以及原始的反复器,因为列表是从集合继承的)。

8.1.1 使用 Collections

boolean add(Object) *保证集合内包含了自变量。如果它没有添加自变量,就返回 false(假)
boolean addAll(Collection) *添加自变量内的所有元素。如果没有添加元素,则返回 true(真)
void clear() *删除集合内的所有元素
boolean contains(Object) 若集合包含自变量,就返回“真”
boolean containsAll(Collection) 若集合包含了自变量内的所有元素,就返回“真”
boolean isEmpty() 若集合内没有元素,就返回“真”
Iterator iterator() 返回一个反复器,以用它遍历集合的各元素
boolean remove(Object) *如自变量在集合里,就删除那个元素的一个实例。如果已进行了删除,就返回
“真”
boolean removeAll(Collection) *删除自变量里的所有元素。如果已进行了任何删除,就返回“真”
boolean retainAll(Collection) *只保留包含在一个自变量里的元素(一个理论的“交集”)。如果已进
行了任何改变,就返回“真”
int size() 返回集合内的元素数量
Object[] toArray() 返回包含了集合内所有元素的一个数组
*这是一个“可选的”方法,有的集合可能并未实现它。若确实如此,该方法就会遇到一个
UnsupportedOperatiionException,即一个“操作不支持”违例
 //: Collection1.java
 // Things you can do with all Collections
 package c08.newcollections;
 import java.util.*;
 public class Collection1 {
 // Fill with 'size' elements, start
 // counting at 'start':
 public static Collection 
 fill(Collection c, int start, int size) {
 for(int i = start; i < start + size; i++)
 c.add(Integer.toString(i));
 return c;
 }
 // Default to a "start" of 0:
 public static Collection 
 fill(Collection c, int size) {
 return fill(c, 0, size);
 }
 // Default to 10 elements:
 public static Collection fill(Collection c) {
 return fill(c, 0, 10);
 }
 // Create & upcast to Collection:
 public static Collection newCollection() {
 return fill(new ArrayList());
 // ArrayList is used for simplicity, but it's
 // only seen as a generic Collection 
 // everywhere else in the program.
 }
 // Fill a Collection with a range of values:
 public static Collection 
 newCollection(int start, int size) {
 return fill(new ArrayList(), start, size);
 }
 // Moving through a List with an iterator:
 public static void print(Collection c) {
 for(Iterator x = c.iterator(); x.hasNext();)
 System.out.print(x.next() + " ");
 System.out.println();
 } 
 public static void main(String[] args) {
 Collection c = newCollection();
 c.add("ten");
 c.add("eleven");
 print(c);
 // Make an array from the List:
 Object[] array = c.toArray(); 
 // Make a String array from the List:
 String[] str = 
 (String[])c.toArray(new String[1]);
 // Find max and min elements; this means
 // different things depending on the way
 // the Comparable interface is implemented:
 System.out.println("Collections.max(c) = " +
 Collections.max(c));
 System.out.println("Collections.min(c) = " +
 Collections.min(c));
 // Add a Collection to another Collection
 c.addAll(newCollection());
 print(c);
 c.remove("3"); // Removes the first one
 print(c);
 c.remove("3"); // Removes the second one
 print(c);
 // Remove all components that are in the
 // argument collection:
 c.removeAll(newCollection());
 print(c);
 c.addAll(newCollection());
 print(c);
 // Is an element in this Collection?
 System.out.println(
 "c.contains(\"4\") = " + c.contains("4"));
 // Is a Collection in this Collection?
 System.out.println(
 "c.containsAll(newCollection()) = " + 
 c.containsAll(newCollection()));
 Collection c2 = newCollection(5, 3);
 // Keep all the elements that are in both
 // c and c2 (an intersection of sets):
 c.retainAll(c2);
 print(c);
 // Throw away all the elements in c that
 // also appear in c2:
 c.removeAll(c2);
 System.out.println("c.isEmpty() = " +
 c.isEmpty());
 c = newCollection();
 print(c);
 c.clear(); // Remove all elements
 System.out.println("after c.clear():");
 print(c);
 }
}    

8.1.2 使用 Lists

List(接口)顺序是 List 最重要的特性;它可保证元素按照规定的顺序排列。List 为 Collection 添加了大量方法,以便我们在 List 中插入和删除元素(只推荐对 LinkedList 这样做)。List 也会生成一个 ListIterator(列表反复器),利用它可在一个列表里朝两个方向遍历,同时插入和删除位于列表中部的元素(只建议对 LinkedList 这样做)。

ArrayList 由一个数组后推得到的 List。作为一个常规用途的对象容器使用,用于替换原先的 Vector。允许我们快速访问元素,但在从列表中部插入和删除元素时,速度却稍嫌慢。一般只应该用ListIterator 对一个ArrayList 进行向前和向后遍历,不要用它删除和插入元素;ArrayList效率低于LinkedList效率。(查询快,增删慢

LinkedList 提供优化的顺序访问性能,同时可以高效率地在列表中部进行插入和删除操作。但在进行随机访问时,速度却相当慢。(增删快,查询慢

下面这个例子中的方法每个都覆盖了一组不同的行为:每个列表都能做的事情(basicTest()),通过一个反复器遍历(iterMotion())、用一个反复器改变某些东西(iterManipulation())、体验列表处理的效果(testVisual())以及只有 LinkedList 才能做的事情等:

//: List1.java
// Things you can do with Lists
package c08.newcollections;
import java.util.*;
public class List1 {
 // Wrap Collection1.fill() for convenience:
 public static List fill(List a) {
 return (List)Collection1.fill(a);
 }
 // You can use an Iterator, just as with a
 // Collection, but you can also use random
 // access with get():
 public static void print(List a) {
 for(int i = 0; i < a.size(); i++)
 System.out.print(a.get(i) + " ");
 System.out.println();
 }
    
 static boolean b;
 static Object o;
 static int i;
 static Iterator it;
 static ListIterator lit;
 public static void basicTest(List a) {
 a.add(1, "x"); // Add at location 1
 a.add("x"); // Add at end
 // Add a collection:
 a.addAll(fill(new ArrayList()));
 // Add a collection starting at location 3:
 a.addAll(3, fill(new ArrayList())); 
 b = a.contains("1"); // Is it in there?
 // Is the entire collection in there?
 b = a.containsAll(fill(new ArrayList()));
 // Lists allow random access, which is cheap
 // for ArrayList, expensive for LinkedList:
 o = a.get(1); // Get object at location 1
 i = a.indexOf("1"); // Tell index of object
 // indexOf, starting search at location 2:
 i = a.indexOf("1", 2);
 b = a.isEmpty(); // Any elements inside?
 it = a.iterator(); // Ordinary Iterator
 lit = a.listIterator(); // ListIterator
 lit = a.listIterator(3); // Start at loc 3
 i = a.lastIndexOf("1"); // Last match 
 i = a.lastIndexOf("1", 2); // ...after loc 2
 a.remove(1); // Remove location 1
 a.remove("3"); // Remove this object
 a.set(1, "y"); // Set location 1 to "y"
 // Keep everything that's in the argument
 // (the intersection of the two sets):
 a.retainAll(fill(new ArrayList()));
 // Remove elements in this range:
 a.removeRange(0, 2);
 // Remove everything that's in the argument:
 a.removeAll(fill(new ArrayList()));
 i = a.size(); // How big is it?
 a.clear(); // Remove all elements
 }
 public static void iterMotion(List a) {
 ListIterator it = a.listIterator();
 b = it.hasNext();
 b = it.hasPrevious();
 o = it.next();
 i = it.nextIndex();
 o = it.previous();
 i = it.previousIndex();
 }
 public static void iterManipulation(List a) {
 ListIterator it = a.listIterator();
 it.add("47");
     
 // Must move to an element after add():
 it.next();
 // Remove the element that was just produced:
 it.remove(); 
 // Must move to an element after remove():
 it.next();
 // Change the element that was just produced:
 it.set("47");
 }
 public static void testVisual(List a) {
 print(a);
 List b = new ArrayList();
 fill(b);
 System.out.print("b = ");
 print(b);
 a.addAll(b);
 a.addAll(fill(new ArrayList()));
 print(a);
 // Shrink the list by removing all the 
 // elements beyond the first 1/2 of the list
 System.out.println(a.size());
 System.out.println(a.size()/2);
 a.removeRange(a.size()/2, a.size()/2 + 2);
 print(a);
 // Insert, remove, and replace elements
 // using a ListIterator:
 ListIterator x = a.listIterator(a.size()/2);
 x.add("one"); 
 print(a);
 System.out.println(x.next());
 x.remove();
 System.out.println(x.next());
 x.set("47");
 print(a);
 // Traverse the list backwards:
 x = a.listIterator(a.size());
 while(x.hasPrevious())
 System.out.print(x.previous() + " ");
 System.out.println();
 System.out.println("testVisual finished");
 }
 // There are some things that only
 // LinkedLists can do:
 public static void testLinkedList() {
 LinkedList ll = new LinkedList();
 Collection1.fill(ll, 5);
 print(ll);
 // Treat it like a stack, pushing:
 ll.addFirst("one");
 ll.addFirst("two");
 print(ll);
 // Like "peeking" at the top of a stack: 

 System.out.println(ll.getFirst());
 // Like popping a stack:
 System.out.println(ll.removeFirst());
 System.out.println(ll.removeFirst());
 // Treat it like a queue, pulling elements
 // off the tail end:
 System.out.println(ll.removeLast());
 // With the above operations, it's a dequeue!
 print(ll);
 }
 public static void main(String args[]) {
 // Make and fill a new list each time:
 basicTest(fill(new LinkedList()));
 basicTest(fill(new ArrayList()));
 iterMotion(fill(new LinkedList()));
 iterMotion(fill(new ArrayList()));
 iterManipulation(fill(new LinkedList()));
 iterManipulation(fill(new ArrayList()));
 testVisual(fill(new LinkedList()));
 testLinkedList();
 }
}    

8.1.3 使用 Sets

Set 拥有与 Collection 完全相同的接口,所以和两种不同的 List 不同,它没有什么额外的功能。相反,Set 完全就是一个 Collection,只是具有不同的行为(这是实例和多形性最理想的应用:用于表达不同的行为)。在这里,一个 Set 只允许每个对象存在一个实例(正如大家以后会看到的那样,一个对象的"值"的构成是相当复杂的)。

Set(接口)添加到 Set 的每个元素都必须是独一无二的;否则 Set 就不会添加重复的元素。添加到 Set 里的对象必须定义 equals(),从而建立对象的唯一性。Set 拥有与 Collection 完全的接口。一个 Set 不能保证自己按任何特定的顺序维持自己的元素

HashSet 用于除非常小的以外的所有 Set。对象也必须定义 HashCode()

ArraySet 由一个数组后推得到的 Set。面向非常小的Set 设计,特别是那些需要频繁创建和删除的。对于小Set,与HashSet 相比,ArraySet 创建和反复所需付出的代价都要小得多。但随着 Set 的增大,它的性能也会大打折扣。不需要HashCode()

TreeSet 由一个 "红黑树"后推得到的顺序 Set。

下面这个例子并没有列出用一个Set 能够做的全部事情,因为接口与Collection 是相同的,前例已经练习过了。相反,我们要例示的重点在于使一个 Set 独一无二的行为:

//: Set1.java
// Things you can do with Sets
package c08.newcollections;
import java.util.*;
public class Set1 {
 public static void testVisual(Set a) {
 Collection1.fill(a);
 Collection1.fill(a);
 Collection1.fill(a);
 Collection1.print(a); // No duplicates!
 // Add another set to this one:
 a.addAll(a);
 a.add("one"); 
 a.add("one"); 
 a.add("one");
 Collection1.print(a);
 // Look something up:
 System.out.println("a.contains(\"one\"): " +
 a.contains("one"));
 }
 public static void main(String[] args) {
 testVisual(new HashSet());
 testVisual(new TreeSet());
 }
}

重复的值被添加到 Set,但在打印的时候,我们会发现 Set 只接受每个值的一个实例。

运行这个程序时,会注意到由HashSet 维持的顺序与 ArraySet 是不同的。这是由于它们采用了不同的方法来保存元素,以便它们以后的定位。ArraySet 保持着它们的顺序状态,而HashSet 使用一个散列函数,这是特别为快速检索设计的)。创建自己的类型时,一定要注意 Set 需要通过一种方式来维持一种存储顺序。

//: Set2.java
// Putting your own type in a Set
package c08.newcollections;
import java.util.*;
class MyType implements Comparable {
 private int i;
 public MyType(int n) { i = n; }
 public boolean equals(Object o) {
 return
 (o instanceof MyType) 
 && (i == ((MyType)o).i);
 }
 public int hashCode() { return i; }
 public String toString() { return i + " "; }
 public int compareTo(Object o) {
 int i2 = ((MyType) o).i;
 return (i2 < i ? -1 : (i2 == i ? 0 : 1));
 }
}
public class Set2 {
 public static Set fill(Set a, int size) {
 for(int i = 0; i < size; i++)
 a.add(new MyType(i));
 return a;
 }
 public static Set fill(Set a) {
 return fill(a, 10);
 }
 public static void test(Set a) {
 fill(a);
 fill(a); // Try to add duplicates
 fill(a);
 a.addAll(fill(new TreeSet()));
 System.out.println(a);
 }
 public static void main(String[] args) {
 test(new HashSet());
 test(new TreeSet());
 }
}     

8.1.4 使用 Maps

Map(接口) 维持“键-值”对应关系(对),以便通过一个键查找相应的值

HashMap 基于一个散列表实现(用它代替HashTable)。针对"键-值"对的插入和检索,这种形式具有最稳定的性能。可通过构建器这一性能进行调整,以便设置散列表的"能力"和"装载因子"

ArrayMap 由一个ArrayList后推得到的 Map。对反复的顺序提供了精确的控制。面向非常小的Map设计,特别是那些需要经常创建和删除的。对于非常小的Map,创建和反复所付出的代价要比HashMap低得多。但在 Map 变大以后,性能也会相应地大幅降低。

TreeMap 在一个"红-黑"树的基础上实现。查看键或者"键-值"对时,它们会按固定的顺讯排列(取决于Comparable 或 Comparator)。TreeMap最大的好处就是我们得到的是已排好序的结果。TreeMap 是含有 subMap()方法的唯一一种Map,利用它可以返回树的一部分。

下例包含了两套测试数据以及一个 fill()方法,利用该方法可以用任何两维数组(由Object 构成)填充任何Map。这些工具也会在其他 Map 例子中用到。

//: Map1.java
// Things you can do with Maps
package c08.newcollections;
import java.util.*;
public class Map1 {
 public final static String[][] testData1 = {
 { "Happy", "Cheerful disposition" },
 { "Sleepy", "Prefers dark, quiet places" },
 { "Grumpy", "Needs to work on attitude" },
 { "Doc", "Fantasizes about advanced degree"},
 { "Dopey", "'A' for effort" },
 { "Sneezy", "Struggles with allergies" },
 { "Bashful", "Needs self-esteem workshop"},
 };
 public final static String[][] testData2 = {
 { "Belligerent", "Disruptive influence" },
 { "Lazy", "Motivational problems" },
 { "Comatose", "Excellent behavior" }
 };
 public static Map fill(Map m, Object[][] o) {
 for(int i = 0; i < o.length; i++)
 m.put(o[i][0], o[i][1]);
 return m;
 }
 // Producing a Set of the keys:
 public static void printKeys(Map m) {
 System.out.print("Size = " + m.size() +", ");
 System.out.print("Keys: ");
 Collection1.print(m.keySet());
 }
 // Producing a Collection of the values:
 public static void printValues(Map m) {
 System.out.print("Values: ");
 Collection1.print(m.values());
 }
 // Iterating through Map.Entry objects (pairs):
 public static void print(Map m) {
 Collection entries = m.entries();
 Iterator it = entries.iterator();
 while(it.hasNext()) {
 Map.Entry e = (Map.Entry)it.next();
 System.out.println("Key = " + e.getKey() +
 ", Value = " + e.getValue());
 }
 }
 public static void test(Map m) {
 fill(m, testData1);
 // Map has 'Set' behavior for keys:
 fill(m, testData1);
 printKeys(m);
 printValues(m);
 print(m);
 String key = testData1[4][0];
 String value = testData1[4][1];
 System.out.println("m.containsKey(\"" + key +
 "\"): " + m.containsKey(key));
 System.out.println("m.get(\"" + key + "\"): "
 + m.get(key));
 System.out.println("m.containsValue(\"" 
 + value + "\"): " + 
 m.containsValue(value)); 
 Map m2 = fill(new TreeMap(), testData2);
 m.putAll(m2);
 printKeys(m);
 m.remove(testData2[0][0]);
 printKeys(m);
 m.clear();
 System.out.println("m.isEmpty(): " 
 + m.isEmpty());
 fill(m, testData1);
 // Operations on the Set change the Map:
 m.keySet().removeAll(m.keySet());
 System.out.println("m.isEmpty(): " 
 + m.isEmpty());
 }
 public static void main(String args[]) {
 System.out.println("Testing HashMap");
 test(new HashMap());
 System.out.println("Testing TreeMap");
 test(new TreeMap());
 }
}    
    
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!