持有对象(thinking in Java)

Java中有多种方式保存对象(应该说是对对象的引用)。例如前面曾经学习过的数组,它是编译器支持的类型。数组是保存一组对象最有效的方式,如果你想保存一组基本类型数据,也推荐用这种方式。但是数组具有固定的尺寸,更一般的情况下,你在写程序时并不知道需要多少个对象,或者是否需要更复杂的方式来存储对象,因此数组尺寸固定这一限制显得过于受限了。

Java实用类库还提供了一套相当完整的容器类来解决这个问题,其中基本的类型是List、Set、Queue和Map。这些对象类型也称为集合类,但由于Java类库中使用了Collection这个名字来指代该类库的一个特殊子集,所以我们使用了范围更广的术语“容器”称呼它们。容器提供了完善的方法来保存对象。

容器还有其他一些特性。例如,Set对于每个值都只保存一个对象,Map是允许你将某些对象与其他一些对象关联起来的关联数组,Java容器类都可以自动地调整自己的尺寸。因此与数组不同,在编程时,你可以将任意数量的对象放置到容器中,并且不需要担心容器应该设置为多大。

使用Java泛型来创建类会非常复杂。但是,应用预定的泛型通常会很简单。例如,要想定义用来保存Apple对象的ArrayList,你可以声明ArrayList,而不仅仅时ArrayList,其中尖括号括起来的时类型参数,它指定了这个容器实例可以保存的类型。通过使用泛型,就可以在编译器防止将错误类型对象放置到容器中

public class Apple {
    private static long counter;
    private final long id = counter++;
    public long id(){
        return id;
    }
}
public class Oragnge {
}
import java.util.ArrayList;

public class AppleAndOrangeWithGener {
    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对象放入容器中
还应该注意到,在将元素从List中取出时,类型转换也不再是必需的了。因为List知道它保存的是什么类型,因此它会在调用get()时替你执行转型。这样,通过使用泛型,你不仅知道编译器将会检查你放置容器中的对象类型,而且在使用容器中的对象时,可以使用更加清晰的语法

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

import java.util.ArrayList;

class Smith extends Apple{}
class Gala extends Apple{}
class Fuji extends Apple{}
class Brae extends Apple{}

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

因此,你可以将Apple的子类添加到被指定为保存Apple对象的容器中。
程序的输出是从Object默认的toString()方法产生的,该方法将打印类名,后面跟随该对象的散列码的无符号十六进制表示(这个散列码是通过hashCode()方法产生的)。

基本概念

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

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

2)Map。一组成对的“键值对”对象,允许你使用键来查找值。ArrayList允许你使用数字来查找值,因此在某种意义上讲,它将数字与对象关联在了一起。映射表允许我们使用另一个对象来查找某个对象,它也被称为“关联数组”,因为它将某些对象与另外一些对象关联在了一起;或者被称为“字典”,因为你可以使用键对象来查找值对象,就像在字典中使用单词来定义一样。

尽管并非总是这样,但在理想情况下,你编写的大部分代码都是在与这些接口打交道,并且你唯一需要指定所用的精确类型的地方就是在创建的时候。因此,你可以像下面这样创建一个List:

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

注意,ArrayList已经被向上转型为List,这与前一个实例中的处理方式正好相反。使用接口的目的在于如果你决定去修改你的实现,你所需的只是在创建出修改它,像下面这样。

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

因此,你应该创建一个具体类的对象,将其转型为对应的接口,然后在其余的代码中都使用这个接口。

Collection接口概括了序列的概念——一种存放一组对象的方式。下面这个简单的示例用Integer对象填充了一个Collection(这里用ArrayList表示):

import java.util.ArrayList;
import java.util.Collection;

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

因为这个示例只使用了Collection方法,因此任何继承自Collection的类都可以正常工作。
add()方法的名称就表明它是要将一个新元素放置到Collection中,但是,文档中非常仔细地叙述到:“要确保这个Collection包含指定的元素”这是因为考虑到了Set的含义,因为在Set中只有元素不存在的情况下才会添加。在使用ArrayList,或者任何种类的List时,add()总是表示把它放进去,因为List不关心是否存在重复。

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(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]
[rat, cat, dog]
[cat, dog, rat]
[rat, cat, dog]
{rat=Fuzzy, cat=Rags, dog=Spot}
{cat=Rags, dog=Spot, rat=Fuzzy}
{rat=Fuzzy, cat=Rags, dog=Spot}

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

查看输出会发现,默认的打印行为(使用容器提供的toString()方法)即可生成可读性很好的结果,Collection打印出来的结果用方括号括住,每个元素由逗号分隔。Map则用大括号括住,键与值由等号联系
第一个fill()方法可以作用于所以类型的Collection,这些类型都实现了用添加新元素的add()方法。

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

HashSet、TreeSet 和LinkedHashSet 都是Set类型,输出显示在Set中,每个相同的项只有保存一次,但是输出也显示了不同的Set实现存储元素的方式也不同。HashSet使用的是相当复杂的方式来存储元素,这种方式是最快获取元素的方式,因此,存储的顺序看起来并无实际意义。

Map(也被称为关联数组)使得你可以用键来查找对象,就像一个简单的数据库。键所关联的对象称为值。使用Map可以将每个州名与其首府联系起来,如果想知道Ohio首府,可以将Ohio作为键进行查找,几乎就像使用数组下标一样。正由于这种行为,对于每一个键,Map只接受存储一次。

Map.put(key,value)方法将增加一个值,并将它与某个键关联起来。Map.get(key)方法将产生与这个键相关联的值。

注意,不必指定Map的尺寸,因为它自己会自动地调整尺寸,Map还知道如何打印自己,它会显示相关联的键和值。键和值在Map中的保存顺序并不是它们的插入顺序,因为HashMap实现使用的是一种非常快的算法来控制顺序。
本例使用了三种基本风格的Map:HashMap、TreeMap和LinkedHashMap。与HashSet一样,HashMap也提供了最快的查找技术,也没有按照任何明显的顺序来保存其元素。TreeMap按照比较结果的升序保存键,而LinkedHashMap按照插入顺序保存键,同时保留HashMap的查询速度

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值