Java编程思想---第十一章 持有对象(上)

第十一章 持有对象(上)

  如果一个程序只包含固定数量的且其生命期都是已知的对象,那么这是一个非常简单的程序。为解决数组尺寸固定这一限制,Java实用库提供了一套相当完整的容器类来解决这个问题,其中基本类型是List、Set、Queue、Map,这些对象类型也称为集合类。

 

11.1 泛型和类型安全的容器

 

  现在,你可以把ArrayList当作一个可以自动扩充自身尺寸的数组来看待,使用ArrayList相当简单,创建一个实例,用add()插入对象,然后用get()访问这些对象,此时需要使用索引,就像数组一样,但是不需要方括号。ArrayList还有一个size()方法,使你知道已经有多少元素添加进来。

  在下面的例子中,Apple和Orange都放置在了容器中,然后将他们取出,正常情况下Java编译器会报告警告信息,因为这个实例没有使用泛型,这里我们使用@SuppreeeWarning注解及其参数表示只有有关不受检查的异常的井盖信息应该被抑制:

import java.util.ArrayList;

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());
        apples.add(new Orange());
        for(int i = 0; i< apples.size(); i++)
            ((Apple)apples.get(i)).id();
    }
}

 

  Apple和Orange类是有区别的,他们除了都是Object之外没有任何共性,因为ArrayList保存的是Object,因此你不仅可以通过ArrayList的add()方法将Apple对象放进这个容器,还可以添加Orange对象,而且在编译和运行时都不会有问题。当你使用get()方法取出你认为是Apple对象的时候,你得到的知识Object引用,必须将其转型为Apple,因此就有了以上的表达式。当运行时,当你试图将Orange对象转型为Apple时,你就会以前面提及的异常的形式得到一个错误。

  要想定义用来保存Apple对象的ArrayList,你可以声明ArrayList<Apple>,而不仅仅只是ArrayList:

import java.util.ArrayList;
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());
        for(int i = 0; i<apples.size(); i++)
            System.out.println(apples.get(i).id());
        for(Apple c : apples)
            System.out.println(c.id());
    }
}

 

输出结果为:

0

1

2

0

1

2

 

  现在编译器可以组织你将Orange放置到apples中,因此他变成了一个编译期错误,而不是运行时错误。

  当你指定了某个类型作为泛型参数时,你并不仅限于只能将该确切类型的对象放置到容器中,向上转型也可以像作用于其他类型一样作用于泛型:

import java.util.ArrayList;

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);
    }
}

 

  因此你可以将Apple的子类添加到指定为保存Apple对象的容器中。

 

11.2 基本概念

 

  Java容器类类库的用途是保存对象,并将其划分为两个不同的概念:

1、Collection:一个独立元素的序列,这些元素都服从一条或多条规则,List必须按照插入的顺序保存元素,而Set不能有重复元素,Queue按照排队规则来确定对象产生的顺序。

2、Map:一组成对的键值对对象,允许使用键值对来查找值,ArrayList允许你使用数字来查找值,因此在某种意义上讲,它将数字与对象关联在了一起,映射表允许我们使用另一个对象来查找某个对象,它也被成为关联数组。

 

11.3 添加一组元素

 

  在java.util的Arrays和Collections中有很多实用的方法,在一个Collection中添加一组元素,Arrays.asList()方法接收一个数组或是一个用逗号分隔的元素列表,并将其转换为一个List对象,将元素添加到Collection中:

import java.util.*;

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));
        Collections.addAll(collection,11,12,13,14,15);
        Collections.addAll(collection,moreInts);
        List<Integer> list = Arrays.asList(16,17,18,19,20);
        list.set(1,99);
    }
}

 

  Collection构造器可以接受另一个Collection,用它来将自身初始化,因此你可以使用Arrays.List()来为这个构造器产生输入,但是Collection.addAll()方法运行起来要快得多,而且一个不包含元素的Collection,然后调用Collection.addAll()这种方式更方便,因此它是首选。Collection.addAll()成员方法只能接收另一个Collection对象作为参数,因此它不如Arra.asList()或Collections.addAll()灵活,这两个方式使用的都是可变参数列表。

 

11.4 容器的打印

 

  你必须使用Arrays.toString()来产生数组的可打印表示,但是打印容器无需任何帮助,下面是一个例子:

import java.util.*;

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(new ArrayList<String>());
        System.out.println(new LinkedList<String>());
        System.out.println(new HashSet<String>());
        System.out.println(new TreeSet<String>());
        System.out.println(new LinkedHashSet<String>());
        System.out.println(new HashMap<String,String>());
        System.out.println(new TreeMap<String,String>());
        System.out.println(new LinkedHashMap<String,String>());
    }
}

 

  这里展示了Java容器类库中的两种主要类型,他们的区别在于每个槽保存的元素个数,Collection在每个槽中只能保存一个元素,此类容器包括:List,它以特定的顺序保存一组元素;Set,元素不能重复;Queue,值允许在容器的一端插入对象,并从另外一端移除对象。Map在每个槽内保存了两个对象,即键和值。

  ArrayList和LinkList都是List类型,从输出可以看出,他们都按照被插入的顺序保存元素,两者的不同之处不仅在于执行某些类型的操作时的性能,而且LinkList包含的操作也多余ArrayList。

  HashSet、TreeList和LinkedHashSet都是Set类型,输出显示在Set中,每个相同的项只有保存一次,但是输出也显示了不同的Set实现的存储元素打方式也不同。

  Map使得你可以用键来查找对象,就像一个简单的数据库,键所关联的对象成为值,使用Map可以像使用数组下标一样,正由于这个所以对于每一个键Map只接受存储一次。

 

11.5 List

 

  List接口在Collection基础上添加了大量的方法,使得可以在List中间插入和移除元素。

  有两种类型的List:

1、基本的ArrayList,它长于随机访问元素,但是在List的中间插入和移除元素时比较慢

2、LinkedList,它通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问,在随机访问方面相对比较慢,但是它的特性集较ArrayList更大。

 

  你可以用contains()方法来确定某个对象是否在列表中,如果想移除一个对象则可以将这个对象的引用传递给remove()方法,可以使用indexOf()老发现该对象在List所处位置的索引编号。当确定一个元素是否属于某个List,发现某个元素的索引,以及从某个List中移除一个元素时,都会用到equals()方法。

 

11.6 迭代器

 

  任何容器类都必须有某种方式可以插入元素将他们再次取回,毕竟持有事物是容器最基本的工作,对于List,add()是插入元素的方法之一,而get()是取出元素的方法之一。

  如果从更高的角度思考,会发现这有个缺点,要使用容器必须对容器的确切类型编程,初看起来这没什么不好,但是考虑这个情况:如果原本是对着List编码的,但是后来发现如果能把相同的代码应用于Set,将会显得非常方便,这时怎么做?

  迭代器的概念可以用于达成此目的,迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层结构,此外,迭代器通常被称为轻量级对象,创建他的代价小,因此经常可以见到对迭代器有些奇怪的限制。例如,Java的Iterator只能单向移动,这个Iterator只能用来

  1、使用方法iterator()要求容器返回一个Iterator,Iterator将准备好返回序列中的第一个元素

  2、使用next()获得序列中的下一个元素

  3、使用hasNext()检查序列中是否还有元素

  4、使用remove()将迭代器新近返回的元素删除

 

  有了Iterator就不必为容器中元素的数量操心了,那是hasNext()和next()关心的事情。如果你只是向前遍历List并不打算修改List对象本身,那么foreach将会显得更加简洁。Iterator还可以移除由next()产生的最后一个元素,这意味着在调用remove()之前必须先调用next()。

 

 

猜你喜欢

转载自www.cnblogs.com/parable/p/11522478.html