2009-01-28

集合(Collections)与泛型(Generic)基础

我们先讨论集合

  在Java中,集合有这几种:List、Set和Map。从继承树上看,List和Set都是java.util.Collection的拓展接口。而Map则和java.util.Collection没有关系,但是它们都是集合。那么它们之间的区别呢?


  • List 又称为序列,就是有序的Collection,它能够保存索引位置。List可以有多个元素引用相同的对象。
  • Set 是一个不包含重复元素的的Collection
  • Map 是映射的集合,其数据是成对的,即键值(Key)和数据之(Value),Map中Key不允许重复,Value可以重复。

  而这几个集合都是接口,而接口加上其他的特性拼凑起来就是各种对应的集合,比如LinkedList就是链表List实现,HashSet就是基于哈希表的Set实现。

  可以看出,这三种主要的集合其中一个重要的区别就是是否容许重复的内容,但是这里的重复是如何判定的呢?

  这就涉及到引用相等和对象相等的问题:如果两个引用引用了同一个实例,那么它们就是引用相等,实际上就是同一个东西。而对象相等是两个引用引用了两个不同的实例,但是这两个实例在逻辑上被认为是相等的(比如两个int值都是22的Integer对象)。因此要判定是否相等就有比较复杂的过程了。

Java对对象的的相等性有指定一个规则(设两对象分别为A和B):

  • 两个对象相等⇔(A.hashCode() == b.hashCode() && A.equals(B)==0)
  • 两个对象有相同的hashCode()值,但未必相等。两个对象相等,则其hashCode()的值必然相等
  • hashCode()的默认行为是对heap上的对象产生独特的值,因此一个类的未override过的hashCode()方法在两个类的实例上一定产生不同的值。
  • equals()的默认行为是进行==比较,因此一个类的未override过的equals()方法用来测试两个类的实例一定不相等。

  以上是集合的基本信息,如果需要某些特别的功能的集合,可以从继承树上查看。




接下来是泛型(Generic):

  哈哈,高手说,不懂泛型的是假高手,懂了泛型的未必是高手。。。同学,恭喜你终于往高级的地方走了。。。。

  泛型这个特性直到Java5里才被加入。泛型能够使集合获得更好的类型安全性,当然,也有其他的好处。使用泛型编程的程序如果有类型问题,则它在编译阶段就会被编译器卡住,而不是等到执行时。对于我们初学者来说,在Java中最早见到的泛型的例子恐怕就是ArrayList的情况了。让我们看看ArrayList的类的定义:

public class ArrayList<e> extends AbstractList<e> implements List<e>, RandomAccess, Cloneable, Serializable

  这里有个<e> 显然很奇怪,不过我们可以把这个e当作集合所要处理的元素类型(element)。

  如果我们再看ArrayList的一个方法的定义我们就能更好的理解它了:

public boolean add(E o)



  这样结合起来不难理解。这个<e>所在的部分会被类使用时真正使用的类型所替代。这就是所谓的泛型。因此我们才能使用ArrayList ArrayList这样的语法来声明保存不同类型的ArrayList。

  除了上面那种声明外,还有两种使用泛型的声明方法:

public <T extends A> void TestClass(ArrayList<T> list)

public void TestClass(ArrayList<? extends A> list)


  这种声明法使得后面的T可以是任何一种A的子类(或者子接口,都是用extends)这两种方法是等价的,只是前一种先什么了以后后面可以直接用T来表示A的子类,而不需要每次都像后面那种写法写。


Head First Java在说明泛型的时候用的是一个对对象排序的例子,虽然脱离开它一样能够很容易的说明泛型,但是对像排序本身还是很常用的,所以值得一说。

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



对象排序

  假设我们要对一份歌曲列表进行排序,最简单的情况下我们只有歌曲名,那么,当使用歌曲名进行字母表排序的话,我们有很简单的方法:

  1. 用TreeSet读入歌曲名,TreeSet读入的对象会被自动按照顺序存储,因此只需要直接打印出来即可。

  2. 用ArrayList读入歌曲名,使用Collections.sort()方法来对ArrayList的元素进行升序排列。(Collections.sort()方法将改变ArrayList,事实上java.util.Collections提供了一系列静态方法用来处理集合)

考虑到性能,有时候我们必须要用到后一种方法,实际上,java.util.Collections.sort()这个方法使用合并排序算法,在Java5中文档说明,保证能提供nlog(n)的性能。

这个方法的原型是:
public static <T extends Comparable<? super T>> void sort(List<T> list)
我们要用的时候先声明一个List的子类对象,比如ArrayListArrayList<string> al = new ArrayList<string>();之后导入元素,要排序时只需要Collections.sort(al);这样就达成目的了。
  那么,如果现在我要排序的歌曲是一个自定义的对象,并且还要求要能够按照多种条件排序。这种时候要怎么办呢?显然,从上面sort方法的定义我们可以看出,sort能够排序的对象必须是实现了Cmparable接口的类对象。所以我们必须要在定义自己的类时实现这个接口。下面是一个Music的范例:

class Music implements Comparable<music> {
String title;
String artist;

public int compareTo(Music o) {
return title.compareTo(o.getTitle());
}

public Music(String t,String a) {
title = t;
artist = a;
}

public String getTitle() {
return this.title;
}

public String getArtist() {
return this.artist;
}
}

  这个Music类实现了Comparable接口,其中只有一个方法,compareTo,正式这个方法提供了sort对象进行排序的依据。因此只要把上面那个例子中的String对象直接换成Music就可以使用了。但是还有一个问题是我们不仅要按照歌名排序,还要按照歌曲作者排序。所以这种时候我们就应该用Collections的另外一个排序方法:

public static <t> void sort(List<t> list,Comparator<? super T> c)

  这个sort版本有两个参数,第一个还是要被排序的对象,第二个是一个Comparator的子类的对象。所以我们需要只需要新建一个实现了Comparator接口的类,然后调用这个Comparator的实例来排序即可。
Class ArtistCompare implements Comparator<music> {
public int compare(Music first, Music second) {
return first.getArtist().compartTo(second.getArtist());
}

这样就解决了排序的问题了。

没有评论: