ThinkJava-容器深入研究

第17章 容器深入研究
17.2 填充容器
 
package com.cy.container;

import java.util.*;

class StringAddress {
  private String s;
  
  public StringAddress(String s) { 
      this.s = s; 
  }
  public String toString() {
    return super.toString() + " " + s;
  }
}

public class FillingLists {
  public static void main(String[] args) {
    List<StringAddress> list= new ArrayList<StringAddress>(Collections.nCopies(4, new StringAddress("Hello")));
    System.out.println(list);
    
    Collections.fill(list, new StringAddress("World!"));
    System.out.println(list);
  }
} 

/* Output: (Sample)
[StringAddress@82ba41 Hello, StringAddress@82ba41 Hello, StringAddress@82ba41 Hello, StringAddress@82ba41 Hello]
[StringAddress@923e30 World!, StringAddress@923e30 World!, StringAddress@923e30 World!, StringAddress@923e30 World!]
*///:~
这个示例展示了两种用对单个对象的引用来填充Collection的方式,第一种是使用
Collections.nCopies()创建传递给构造器的List,这里填充的是ArrayList.
StringAddress的toString()方法调用Object.toString()并产生该类的名字,后面紧跟该对象的
散列码的无符号十六进制表示.(通过hashCode()生成的)。从输出中你可以看到所有引用都被设
置为指向相同的对象,在第二种方法的Collection.fill()被调用之后也是如此。fill()方法的用处更
有限,因为它只能替换已经在List中存在的元素,而不能添加新的元素。
 
17.4.1 未获支持的操作
最常见的未获支持的操作,都来源于背后由固定尺寸的数据结构支持的容器。当你用
Arrays.asList()将数组转换为List时,就会得到这样的容器。你还可以通过使用Collections类中
"不可修改"的方法,选择创建任何会抛出UnsupportedOperationException的容器(包括Map ) 。
下面的示例包括这两种情况:
package com.cy.container;

import java.util.*;

public class Unsupported {
  static void test(String msg, List<String> list) {
    System.out.println("--- " + msg + " ---");
    Collection<String> c = list;
    Collection<String> subList = list.subList(1,8);
    // Copy of the sublist:
    Collection<String> c2 = new ArrayList<String>(subList);
    try { c.retainAll(c2); } catch(Exception e) {
      System.out.println("retainAll(): " + e);
    }
    try { c.removeAll(c2); } catch(Exception e) {
      System.out.println("removeAll(): " + e);
    }
    try { c.clear(); } catch(Exception e) {
      System.out.println("clear(): " + e);
    }
    try { c.add("X"); } catch(Exception e) {
      System.out.println("add(): " + e);
    }
    try { c.addAll(c2); } catch(Exception e) {
      System.out.println("addAll(): " + e);
    }
    try { c.remove("C"); } catch(Exception e) {
      System.out.println("remove(): " + e);
    }
    // The List.set() method modifies the value but
    // doesn't change the size of the data structure:
    try {
      list.set(0, "X");
    } catch(Exception e) {
      System.out.println("List.set(): " + e);
    }
  }
  
  
  public static void main(String[] args) {
    List<String> list = Arrays.asList("A B C D E F G H I J K L".split(" "));
    test("Modifiable Copy", new ArrayList<String>(list));
    test("Arrays.asList()", list);
    test("unmodifiableList()", Collections.unmodifiableList(new ArrayList<String>(list)));
  }
}

/* Output:
--- Modifiable Copy ---
--- Arrays.asList() ---
retainAll(): java.lang.UnsupportedOperationException
removeAll(): java.lang.UnsupportedOperationException
clear(): java.lang.UnsupportedOperationException
add(): java.lang.UnsupportedOperationException
addAll(): java.lang.UnsupportedOperationException
remove(): java.lang.UnsupportedOperationException
--- unmodifiableList() ---
retainAll(): java.lang.UnsupportedOperationException
removeAll(): java.lang.UnsupportedOperationException
clear(): java.lang.UnsupportedOperationException
add(): java.lang.UnsupportedOperationException
addAll(): java.lang.UnsupportedOperationException
remove(): java.lang.UnsupportedOperationException
List.set(): java.lang.UnsupportedOperationException
*///:~
View Code
因为Arrays.asList()会生成一个List,它基于一个固定大小的数组, 仅支持那些不会改变数组
大小的操作,对它而言是有道理的。任何会引起对底层数据结构的尺寸进行修改的方法都会产生
一个UnsupportedOperationException异常,以表示对未获支持操作的调用(一个编程错误).
注意,应该把Arrays.asList()的结果作为构造器的参数传递给任何Collection (或者使用
addAll()方法,或Collections.addAll()静态方法) ,这样可以生成允许使用所有的方法的普通容
器一一这在main()中的第一个对test()的调用中得到了展示, 这样的调用会产生新的尺寸可调的
底层数据结构。Collections类中的"不可修改"的方法将容器包装到了一个代理中,只要你执
行任何试图修改容器的操作,这个代理都会产生UnsupportedOperationException异常。使用这
些方怯的目标就是产生"常量"容器对象。"不可修改"的Collections方法的完整列表将在稍后
介绍。
test()中的最后一个try语句块将检查作为List的一部分的set()方法.这很有趣,因为你可以
看到"未获支持的操作"这一技术的粒度来的是多么方便-一所产生的"接口"可以在
Arrays.asList()返回的对象和Collections.unmodifiableList()返回的对象之间,在一个方法的粒
度上产生变化。Arrays.asList()返回固定尺寸的List,而Collections.unmodifiableList()产生不可
修改的列表.正如从输出中所看到的.修改Arrays.asList()返回的List中的元素是可以的,因为
这没有违反该List“尺寸固定"这一特性.但是很明显, unmodlfiableList()的结果在任何情况
下都应该不是可修改的。如果使用的是接口,那么还需要两个附加的接口,一个具有可以工作
的set()方法,另外一个没有, 因为附加的接口对于Collection的各种不可修改的子类型来说是必
需的。
 
 
 
17.6 Set和存储顺序
在HashSet上打星号表示,如果没有其他的限制,这就应该是你默认的选择,因为它对速度
进行了优化。
定义hashCode()的机制将在本章稍后进行介绍。你必须为散列存储和树型存储都创建一个
equals()方法,但是hashCode()只有在这个类将会被置于HashSet (这是有可能的,因为它通常
是你的Set实现的首选)或者LinkedHashSet中时才是必需的。但是,对于良好的编程风格而言,
你应该在覆盖equals()方法时,总是同时覆盖hashCode()方法。
package com.cy.container;

import java.util.*;

class SetType {
  int i;
  public SetType(int n) { 
      i = n; 
  }
  public boolean equals(Object o) {
    return o instanceof SetType && (i == ((SetType)o).i);
  }
  public String toString() { 
      return Integer.toString(i); 
  }
}

class HashType extends SetType {
  public HashType(int n) { super(n); }
  public int hashCode() { 
      return this.i; 
  }
}

class TreeType extends SetType implements Comparable<TreeType> {
  public TreeType(int n) { super(n); }

    @Override
    public int compareTo(TreeType arg) {
        return (arg.i < i ? -1 : (arg.i == i ? 0 : 1));
    }
}

public class TypesForSets {
  static <T> Set<T> fill(Set<T> set, Class<T> type) {
    try {
      for(int i = 0; i < 10; i++){
          T t = type.getConstructor(int.class).newInstance(i);
          set.add(t);
      }
    } catch(Exception e) {
      throw new RuntimeException(e);
    }
    return set;
  }
  static <T> void test(Set<T> set, Class<T> type) {
    fill(set, type);
    fill(set, type); // Try to add duplicates
    fill(set, type);
    System.out.println(set);
  }
  public static void main(String[] args) {
    test(new HashSet<HashType>(), HashType.class);
    test(new LinkedHashSet<HashType>(), HashType.class);
    test(new TreeSet<TreeType>(), TreeType.class);
    
    // Things that don't work:
    test(new HashSet<SetType>(), SetType.class);
    test(new HashSet<TreeType>(), TreeType.class);
    test(new LinkedHashSet<SetType>(), SetType.class);
    test(new LinkedHashSet<TreeType>(), TreeType.class);
    
    try {
      test(new TreeSet<SetType>(), SetType.class);
    } catch(Exception e) {
      System.out.println(e.getMessage());
    }
    try {
      test(new TreeSet<HashType>(), HashType.class);
    } catch(Exception e) {
      System.out.println(e.getMessage());
    }
  }
} 

/* Output: (Sample)
[2, 4, 9, 8, 6, 1, 3, 7, 5, 0]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[9, 9, 7, 5, 1, 2, 6, 3, 0, 7, 2, 4, 4, 7, 9, 1, 3, 6, 2, 4, 3, 0, 5, 0, 8, 8, 8, 6, 5, 1]
[0, 5, 5, 6, 5, 0, 3, 1, 9, 8, 4, 2, 3, 9, 7, 3, 4, 4, 0, 7, 1, 9, 6, 2, 1, 8, 2, 8, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
java.lang.ClassCastException: SetType cannot be cast to java.lang.Comparable
java.lang.ClassCastException: HashType cannot be cast to java.lang.Comparable
*///:~
HashType继承自SetType , 并且添加丁hashCode()方法,该方法对于放置到Set的散列实现
中的对象来说是必需的。
TreeType实现了Comparable接口,如果一个对象被用于任何种类的排序容器中,例如
SortedSet (TreeSet是其唯一实现) ,那么它必须实现这个接口。
从输出中可以看到, HashSet以某种神秘的顺序保存所有的元素(这将在本章稍后进行解释) ,
LinkedHashSet按照元素插入的顺序保存元素,而TreeSet按照排序顺序维护元素(按照
compareTo()的实现方式,这里维护的是降序)。
如果我们尝试着将没有恰当地支持必需的操作的类型用于需要这些方法的Set ,那么就会有
大麻烦了。对于没有重新定义hashCode()方法的SetType或TreeType,如果将它们放置到任何散
列实现中都会产生重复值,这样就违反了Set的基本契约。这相当烦人,因为这甚至不会有运行
时错误。但是,默认的hashCode()是合法的,因此这是合法的行为,即便它不正确。确保这种程
序的正确性的唯一可靠方法就是将单元测试合并到你的构建系统中.
如果我们尝试着在TreeSet中使用没有实现Comparable的类型,那么你将会得到更确定的结
果: 在TreeSet试图将该对象当作Comparable使用时,将抛出一个异常。
 
 
17.6.1 SortedSet
SortedSet中的元素可以保证处于排序状态,这使得它可以通过在SortedSet接口中的下列方
法提供附加的功能:
Comparator comparator()返回当前Set使用的Comparator , 或者返回null ,表示以自然方式排序。
Object flrst() 返回容器中的第一个元素。
Object last() 返回容器中的最末一个元素。
SortedSet subSet(fromElement, toElement) 生成此Set的子集, 范围从fromElement (包含)到toEIement (不包含)。
SortedSet headSet(toElement) 生成此Set的子集,由小于toElement的元素组成。
SortedSet tailSet(fromElement) 生成此Set的子集,由大于或等于fromElement的元素组成。
package com.cy.container;

import java.util.*;
import static com.java.util.Print.*;

public class SortedSetDemo {
  public static void main(String[] args) {
    SortedSet<String> sortedSet = new TreeSet<String>();
    Collections.addAll(sortedSet,"one two three four five six seven eight".split(" "));
    print(sortedSet);
    
    String low = sortedSet.first();
    String high = sortedSet.last();
    print(low);
    print(high);
    
    Iterator<String> it = sortedSet.iterator();
    for(int i = 0; i <= 6; i++) {
      if(i == 3) low = it.next();
      if(i == 6) high = it.next();
      else it.next();
    }
    print(low);
    print(high);
    print(sortedSet.subSet(low, high));
    print(sortedSet.headSet(high));
    print(sortedSet.tailSet(low));
  }
} 
/* Output:
[eight, five, four, one, seven, six, three, two]
eight
two
one
two
[one, seven, six, three]
[eight, five, four, one, seven, six, three]
[one, seven, six, three, two]
*///:~
View Code
注意, SortedSet的意思是"按对象的比较函数对元素排序",而不是指"元素插入的次序"。
插入顺序可以用LinkedHashSet来保存。

 

 

17.7 队列
除了并发应用, Queue在Java SE5中仅有的两个实现是LinkedList和PriorityQueue ,它们的
差异在于排序行为而不是性能。下面是涉及Queue实现的大部分操作的基本示例(并非所有的
操作在本例中都能工作) ,包括基于并发的Queue。你可以将元素从队列的一端插入,并于另
端将它们抽取出来:

 

package com.cy.container;

import java.util.concurrent.*;
import java.util.*;
import com.java.util.*;

public class QueueBehavior {
  private static int count = 10;
  
  static <T> void test(Queue<T> queue, Generator<T> gen) {
    for(int i = 0; i < count; i++){
        queue.offer(gen.next());
    }
      
    while(queue.peek() != null){
        System.out.print(queue.remove() + " ");
    }
      
    System.out.println();
  }
  
  static class Gen implements Generator<String> {
    String[] s = ("one two three four five six seven eight nine ten").split(" ");
    int i;
    public String next() { 
        return s[i++]; 
    }
  }
  
  public static void main(String[] args) {
    test(new LinkedList<String>(), new Gen());
    test(new PriorityQueue<String>(), new Gen());
    test(new ArrayBlockingQueue<String>(count), new Gen());
    test(new ConcurrentLinkedQueue<String>(), new Gen());
    test(new LinkedBlockingQueue<String>(), new Gen());
    test(new PriorityBlockingQueue<String>(), new Gen());
  }
} /* Output:
one two three four five six seven eight nine ten
eight five four nine one seven six ten three two
one two three four five six seven eight nine ten
one two three four five six seven eight nine ten
one two three four five six seven eight nine ten
eight five four nine one seven six ten three two
*///:~
package com.java.util;

public interface Generator<T> { 
    T next(); 
}
你可以看到,除了优先级队列,Queue将精确地按照元素被置于Queue中的顺序产生它们.
 
1 7.7.1 优先级队列
在第11章曾经绘出过优先级队列的一个简单介绍.其中更有趣的问题是to-do列表,该列表
中每个对象都包含一个字符串和一个主要的以及次要的优先级值。该列表的排序顺序也是通过
实现Comparable而进行控制的:
package com.cy.container;

import java.util.*;

class ToDoList extends PriorityQueue<ToDoList.ToDoItem> {
  static class ToDoItem implements Comparable<ToDoItem> {
    private char primary;
    private int secondary;
    private String item;
    
    public ToDoItem(String td, char pri, int sec) {
      primary = pri;
      secondary = sec;
      item = td;
    }
    
    public int compareTo(ToDoItem arg) {
        if(primary > arg.primary)
          return +1;
        if(primary == arg.primary)
          if(secondary > arg.secondary)
            return +1;
          else if(secondary == arg.secondary)
            return 0;
        return -1;
    }
    
    public String toString() {
      return Character.toString(primary) + secondary + ": " + item;
    }
  }
  
  public void add(String td, char pri, int sec) {
    super.add(new ToDoItem(td, pri, sec));
  }
  
  public static void main(String[] args) {
    ToDoList toDoList = new ToDoList();
    toDoList.add("Empty trash", 'C', 4);
    toDoList.add("Feed dog", 'A', 2);
    toDoList.add("Feed bird", 'B', 7);
    toDoList.add("Mow lawn", 'C', 3);
    toDoList.add("Water lawn", 'A', 1);
    toDoList.add("Feed cat", 'B', 1);
    
    while(!toDoList.isEmpty())
      System.out.println(toDoList.remove());
  }
} 
/* Output:
A1: Water lawn
A2: Feed dog
B1: Feed cat
B7: Feed bird
C3: Mow lawn
C4: Empty trash
*///:~
你可以看到各个项的排序是如何因为使用了优先级队列而得以自动发生的.
 
 
 

 

 

 

-----------------

转载于:https://www.cnblogs.com/tenWood/p/8366221.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值