《JAVA编程思想》学习备忘:Holding Your Objects-1

时间:2022-07-20 10:06:42

It's a farily simple program that only has a fixed quantity of objects with known lifetimes.

Generics and type-safe containers

泛型与类型安全容器

One of the problems of using pre-Java SE5 containers was that the compiler allowed you to insert an incorrect type into a container.

示例:

class Apple {
 private static long counter;
 private final long id = counter++;
 public long id(){return id;}
}

class Orange{}

public class ApplesAndOrangesWithoutGenerics {
 @SuppressWarnings("unchecked")
 public static void main(String[] args) {
  ArrayList apples = new ArrayList();
  for(int i = 0; i < 3; i++)
   apples.add(new Apple());
  // Not prevented from adding an Orange to apples:
  apples.add(new Orange());
  for(int i = 0; i < apples.size(); i++)
   ((Apple)apples.get(i)).id(); //此处注意:取得对象后须用圆括号整个括起来,再调用方法
  // Orange is detected only at run time
 }
}

控制台看运行输出

In this example,Apples and Oranges are placed into the container,then pulled out.Normally,the Java compiler will give you a warning because the example does not use generics.Here,a special Java SE5 annotation is used to suppress the warning.Annotations start with an '@' sign,and can take an argument;this one is @SuppressWarnings and the argument indecates that "unchecked" warnings only should be suppressed.

 

With generics,you're prevented,at compile time,from putting the wrong type of object into a container.Here's the example again,using generics:

public class ApplesAndOrangesWithGenerics {
 public static void main(String[] args) {
  ArrayList<Apple> apples = new ArrayList<Apple>();
  for(int i = 0; i < 3; i++)
   apples.add(new Apple());
  // Compile-time error:
  // apples.add(new Orange());
  for(int i = 0; i < apples.size(); i++)
   System.out.println(apples.get(i).id());
  // Using foreach:
  for(Apple c : apples)
   System.out.println(c.id());
 }
}

输出:

0
1
2
0
1
2

Also notice that the cast is no longer necessary when fetching items back out from the List.

The example also shows that,if you do not need to use the index of each element,you can use the foreach syntax to select each element in the List.

 

示例:

class GrannySmith extends Apple {}
class Gala extends Apple{}
class Fuji extends Apple{}
class Braeburn extends Apple{}

public class GenericsAndUpcasting {
 public static void main(String[] args) {
  ArrayList<Apple> apples = new ArrayList<Apple>();
  apples.add(new GrannySmith());
  apples.add(new Gala());
  apples.add(new Fuji());
  apples.add(new Braeburn());
  for(Apple c : apples)
   System.out.println(c);
 }
}

输出对象类名及哈希码(略)

Thus,you can add a subtype of Apple to a container that is specified to hold Apple objects.

 

Basic concepts

The Java container library takes the idea of "holding your objects" and divides it into two distinct concepts,expressed as the basic interfaces of the library:

1.Collection:a sequence of individual elements with one or more rules applied to them.A List must hold the elements in the way that they were inserted,a Set cannot have duplicate elements,and a Queue produces the elements in the order determined by a queuing discipline(usually the same order in which they are inserted).

2.Map:a group of key-value object pairs,allowing you to look up a value using a key.An ArrayList allows you to look up an object using a number,so in a sense it associates numbers to objects.A map allows you to look up an object using another object.It's also called an associative array,because it associates objects with other objects,or a dictionary,because you look up a value object using a key object just like you look up a definition using a word.Maps are powerful programming tools.

 

Although it's not always possible,ideally you'll write most of your code to talk to these interfaces,and the only place where you'll specify the precise type you're using is at the the point of creation.So you can create a List like this:

List<Apple> apples = new ArrayList<Apple>();

Notice that the ArrayList has been upcast to a List,in contrast to the way it was handled in the previous examples.The intent of using the interface is that if you decide you want to change your implementation,all you need to do is change it at the point of creation,like this:

List<Apple> apples = new LinkedList<Apple>();

This approach won't always work,because some classes have additional functionality.

 

The Collection interface generalizes the idea of a sequence-a way of holding a group of objects.Here's a simple example that fills a Collection(represented here with an ArrayList)with Integer objects and then prints each element in the resulting container:

public class SimpleCollection {
 public static void main(String[] args) {
  Collection<Integer> c = new ArrayList<Integer>();
  for(int i = 0; i < 10; i++)
   c.add(i); // Autoboxing
  for(Integer i : c)
   System.out.print(i + ", ");
 }
}

输出:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

 

Adding groups of elements

示例:

public class AddingGroups {
 public static void main(String[] args) {
  Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(
    1, 2, 3, 4, 5));
  Integer[] moreInts = { 6, 7, 8, 9, 10 };
  collection.addAll(Arrays.asList(moreInts));
  // Runs significantly faster,but you can't
  // construct a Collection this way:
  Collections.addAll(collection, 11, 12, 13, 14, 15);
  Collections.addAll(collection, moreInts);
  // Produces a list "backed by" an array:
  List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);
  list.set(1, 99); // Ok -- modify an element
  // list.add(21;) // Runtime error because the underlying array cannot be resized.
 }
}

The constructor for a Collecion can accept another Collection which it uses for initializing itself,so you can use Arrays.asList() to produce input for the constructor.

 

A limitation of Arrays.asList() is that it takes a best guess about the resulting type of the List,and doesn't pay attention to what you're assigning it to.Sometimes this can cause a problem:

class Snow {}
class Powder extends Snow{}
class Light extends Powder{}
class Heavy extends Powder{}
class Crusty extends Snow{}
class Slush extends Snow{}
public class AsListInference {
 public static void main(String[] args) {
  List<Snow> snow1 = Arrays.asList(new Crusty(),new Slush(),new Powder());
  // Won't compile:
  // List<Snow> snow2 = Arrays.asList(new Light(),new Heavy());
  // Compiler says:
  // found:java.util.List<Powder>
  // required:java.util.List<Snow>
  // Collections.addAll() doesn't get confused:
  List<Snow> snow3 = new ArrayList<Snow>();
  Collections.addAll(snow3, new Light(),new Heavy());
  // Give a hint using an
  // explicit type argument specification:
  List<Snow> snow4 = Arrays.<Snow>asList(new Light(),new Heavy());
 }
}
As you can see from the creation of snow4,it's possible to insert a "hint" in the middle of Arrays.asList(),to tell the compiler what the actual target type should be for the resulting List type prodeced by Arrays.asList().This is called an explicit type argument specification.

 

Printing containers

关于基本容器的例子:

public class PrintingContainers {
 static Collection fill(Collection<String> collection){
  collection.add("rat");
  collection.add("cat");
  collection.add("dog");
  collection.add("dog");
  return collection;
 }
 static Map fill(Map<String,String> map){
  map.put("rat","Fuzzy");
  map.put("cat", "Rags");
  map.put("dog", "Bosco");
  map.put("dog", "Spot");
  return map;
 }
 public static void main(String[] args) {
  System.out.println(fill(new ArrayList<String>()));
  System.out.println(fill(new LinkedList<String>()));
  System.out.println(fill(new HashSet<String>()));
  System.out.println(fill(new TreeSet<String>()));
  System.out.println(fill(new LinkedHashSet<String>()));
  System.out.println(fill(new HashMap<String,String>()));
  System.out.println(fill(new TreeMap<String,String>()));
  System.out.println(fill(new LinkedHashMap<String,String>()));
 }
}
输出:

[rat, cat, dog, dog]
[rat, cat, dog, dog]
[cat, dog, rat]
[cat, dog, rat]
[rat, cat, dog]
{cat=Rags, dog=Spot, rat=Fuzzy}
{cat=Rags, dog=Spot, rat=Fuzzy}
{rat=Fuzzy, cat=Rags, dog=Spot}

从以上输出可以看到Set容器的唯一性,Hash类则表现为数据的“后进先出”。Collection输出的数据以方括号包围,Map数据的输出以花括号包围,Map键值以等号连接。

ArrayList和LinkedList均为List类型。

HashSet,TreeSet和LinkedHashSet都属于Set。

HashSet的技术点在于取回数据的最快捷方式,但其存储顺序似乎毫无章法。如果存储顺序重要,你可以使用TreeSet,其以升序排列,或使用LinkedHashSet,其会按添加顺序来保存数据。

一个Map(也被称为关联数组)允许你按键查看对象,就象一个简单的数据库。关联的对象被称为值。一个Map具有键的唯一性。你无需指定Map的大小,因为它会自动增长。

HashMap在无序性与快捷查询特性上与HashSet相同。

(待续)