浅析java中的集合类(容器)

来源:互联网 发布:ubuntu 手机助手 编辑:程序博客网 时间:2024/06/11 15:11

泛型和类型安全的容器

在javaSE5之前,我们主要考虑的问题就是编译器允许你向容器插入不正确的类型。例如在一个apple对象的容器中,插入了orange。正常情况下,编译器会发出警告,因为没有使用泛型。我们可以使用javaSE5中的注解特性来取消警告。
class Apple{    private static long counter;    private final long id = counter++;    public long id(){        return id;    }}class Orange{}public class ApplesAndOrange{    @SuppressWarnings("unchecked")    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之外没有任何共性(如果一个类没有声明继承自哪个类,那么他们自动继承Object)。因为ArrayList保存的是Object,因此你不仅可以通过add()将Apple放进去,还可以把Orange()放进去,而且无论在编译还是运行过程中,都是正确的。但当你要从ArrayList中使用Apple时,其实得到的是Object引用,必须将其转化为Apple,这时我们就需要用(Apple)来强转。当你想要将Orange转为Apple时,就会出现异常情况了。

class Apple{    private static long counter;    private final long id = counter++;    public long id(){        return id;    }}class Orange{}public class ApplesAndOrange{    @SuppressWarnings("unchecked")    main(String[] args){        ArrayList<Apple> apples = new ArrayList<Apple>();        for(int i=0;i<3;i++){            apples.add(new Apple());            //apples.add(new Orange());        }        for(int i=0;i<apples.size();i++){            System.out.println(apples.get(i).id());        }        for(Apple c : apples){            System.out.println(apples.get(i).id());        }    }}

如果这样在将Orange放入ArrayList中,就会出现编译错误,而不是运行错误了。并且我们也不需要进行强转了,因为List知道其是什么类型,在调用get()时会替你执行转换操作。并且不需要索引,可以直接使用foreach语法来选择List中的每个元素。当我们确定了泛型后,我们不仅仅可以放入该确切的对象,还能将其子类添加其中。程序输出的是Object的toString方法,该方法打印类名,后面跟随散列码的无符号十六进制表示(该散列码是通过hashCode()方法产生的)。

基本概念

Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念。1.Collection。一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复的元素。Queue按照排队规则来确定对象产生的顺序。2.Map。一组成对的“键值对”对象,允许你使用键来查找值。ArrayList允许你使用数字来查找值,因此在某种意义上讲,它将数字与对象关联在一起。映射表允许我们使用另一个对象来查找某个对象,它也被称为“关联数组”,因为它将某些对象与另外一个对象关联在一起了;或者被称为“字典”。

通常在编写大部分代码都是在与接口打交道,并且你唯一需要指定所使用的精确类型的地方就是在创建的时候。
List<Apple> apples = new ArrayList<Apple>();//ArrayList已经被向上转型为List了。使用接口的目的在与如果你决定去修改你的实现,你所需的只是在创建处修改它。List<Apple> apples = new LinkedList<Apple>();//因此,你应该创建一个具体类的对象,将其转型为对应的接口,然后在其余的代码中都使用该接口。
但是这种方法并非总能有用,因为某些类具有额外的功能,例如:LinkedList具有List接口中未包含的额外方法,而TreeMap也具有在Map中未包含的方法。如果你需要这些方法,就不能使用向上转型。

Coolection接口概括了序列的概念——一种存放一组对象的方式。
public static void main(String[] args) {        Collection<Integer> c = new ArrayList<Integer>();        for(int i = 0; i < 10;i++){            c.add(i);        }        for (Integer i : c) {            System.out.print(i + ", ");        }    }
因为这个示例只使用了Collection方法,因此任何继承自Collection的类的对象都可以正常工作。

添加一组元素

在java.util包中的Arrays和Collections类中都有很多实用方法,可以在一个Collection中添加一组元素。Arrays.asList()方法接受一个数组或是一个用逗号分隔的元素列表,并将其转为一个List对象。Collections.addAll()方法接受个一Collection对象,以及一个数组或者是一个用逗号分隔的列表,并将元素添加到Collection中。
public static void main(String[] args) {        //Collection对象 接受了一个12345的数组        Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5));        System.out.println("collection init:" + collection);        Integer[] moreInts = {6,7,8,9,10};        //Collection 加入了整个moreInts数组        collection.addAll(Arrays.asList(moreInts));        System.out.println("collection addAllIntArr:" + collection);        //使用Collections 给Collection增加11-15        Collections.addAll(collection,11,12,13,14,15);        System.out.println("use Collections add int:" + collection);        Collections.addAll(collection,moreInts);        System.out.println("use Collections add moreInts:" + collection);        List<Integer> list = Arrays.asList(16,17,18,19,20);        System.out.println("list:" + list);        list.set(1, 99);        System.out.println("list change:" + list);    }//out putcollection init:[1, 2, 3, 4, 5]collection addAllIntArr:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]use Collections add int:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]use Collections add moreInts:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 6, 7, 8, 9, 10]list:[16, 17, 18, 19, 20]list change:[16, 99, 18, 19, 20]
Collection的构造器可以接受可以接受另一个Collection,用它来将自身初始化,因此你可以使用Arrays.List来为这个构造器产生输入。Collection.addAll()成员方法只能接受另一个Collection对象作为参数,因此它不如Arrays.asList()或Collections.addAll()灵活,这两个方法使用的都是可变参数列表。我们也可以直接使用Arrays.adList()的输出作为List。但是在这种情况下,其底层表示的是数组,因此不能调整尺寸。如果叫你试图用add或者delete方法,可能在运行时会活的Unsupported Operation。Arrays.asList()方法的限制是它对所产生的List的类型做出来最理想的假设,而并没有注意它是什么类型。
class Sonw{}class Power extends Sonw{}class Light extends Power{}class Heavy extends Power{}class Crusty extends Sonw{}class Slush extends Sonw{}public class TerminationCondition{    public static void main(String[] args) {        List<Sonw> snow1 = Arrays.asList(new Crusty(),new Slush(), new Power());        List<Sonw> snow2 = Arrays.asList(new Light(),new Heavy());        List<Sonw> snow3 = new ArrayList<Sonw>();        Collections.addAll(snow3, new Light(), new Heavy());        List<Sonw> snow4 = Arrays.<Sonw>asList(new Light(),new Heavy());    }}
当试图创建snow2时,Arrays.asList中只有Power类型,因此它会创建Power类型。正如创建Snow4时,可以在asList之前插入一条“线索”,来告诉编译器Arrays.asList()产生的List类型。Map更加复杂,并且出来用另一个Map之外,Java的标准类库中没有提供任何自动化初始它们的方式。

容器的打印

你必须使用Arrays.toString()来产生数组的可打印表示,但是容器无需任何帮助。
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>()));    }    //output[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在每个槽保存两个对象,即键和值。

各个容器的实现类的区别

ArrayList和LinkedList都属于List类型,它们都是按照被插入的顺序保存元素。不同之处是在执行某些类型操作时性能不一样,而且LinkedList的操作多于ArrayList。下篇将会提及到。HashSet,TreeSet和LinkedHashSet都属于Set类型,都继承了Set不能有相同元素的特性,但是输出也显示了它们存储元素的方式不同。HashSet使用的是非常复杂的方式来存储元素,目前我们只需要记住这种方式是最快获取元素的方式,因此,存储的顺序看起来并无实际的意义。如果对顺序很关系,那么可以使用TreeSet,他按照比较结果的升序保存对象;或者使用LinkedHashSet,它按照被添加的顺序保存对象。Map的3中对象排列顺序和Set一样,Hash查找最快,Tree是升序,Linked是插入顺序。Map中,key是唯一的,如果使用相同的key来保存数据,那么之前该key对应的值将被替换。

下节会仔细写到每个容器的特性和迭代器。

原创粉丝点击