<Head First Java>集合与泛型

来源:互联网 发布:投诉淘宝卖家有补偿吗 编辑:程序博客网 时间:2024/06/01 21:19

排序在java中只是雕虫小技。java的集合框架( Collection Framework )能够支持大多数你会用到的数据结构。它可以有 容易加入元素的列表、根据名称来搜索、自动排除重复项目的列表……


对String排序

事件起因:我们假设有一个song类,它有四个实例title、artist、rate、bgm,此时我们想对它进行排序工作!我们把这些song方便的放入ArrayList中,但是它却没有sort方法。

解决方案:使用TreeSet或者Collections.sort()方法。

TreeSet:如果把字符串放进TreeSet而不是ArrayList,这些String会自动地按照字幕的顺序排在正确的位置。没当你想要列出清单时,元素总是会以字母顺序出现。(稍后讨论)

Collection.sort():以下为方法详解

http://www.cjsdn.net/Doc/JDK60/java/util/Collections.html#sort(java.util.List)

作为Collections的静态方法,它可以对List进行排序,而感谢多态机制,ArrayList可以传入此方法。注意此处我们传入的是String[](暂时先没有用song类),根据sort方法中的参数类型<T extends Comparable<? super T>>,它是符合的,为什么呢?


*******************************************************插入************************************************************

泛型初体验

泛型意味着更好的类型安全性,几乎所有你会以泛型写的程序都会与处理集合有关。它主要目的还是让你能够写出有类型安全性的集合。也就是让编译器帮忙防止你把Dog加到一群Cat中。如果没有泛型,编译器无法注意到你加入集合中的东西是什么,因为所有的集合都写成处理Object类型。有点像是ArrayList<Object>。

关于泛型:

1、创建被泛型化类的实例。

下面这行程序

ArrayList<String> thislist = new ArrayList<String>
代表这个ArrayList:(参照ArrayList的说明文件http://www.cjsdn.net/Doc/JDK60/java/util/ArrayList.html)

public class ArrayList<E> extends AbstractList<E>...{public boolean add(E o)//更多代码} 
会被编译器这样看待:

public class ArrayList<String> extends AbstractList<String>...{public boolean add(String o)//更多代码}
也就是说,E会被所指定的真正类型所取代(又被称为类型参数)。
2、运用泛型的方法

public <T extends Animal> void takeThing(ArrayList<T> list)
跟下面这个是不一样的
public void takething(ArrayList<Animal> list)

两者都合法,但意义不同。

第一个的方法表示被声明为Animal或者Animal的子类的ArrayList都是合法的。因此你可以使用ArrayList<Dog>、ArrayList<Cat>或者ArrayList<Animal>来调用上面的方法。

而第二个方法代表只有ArrayList<Animal>是合法的。

*******************************************************插入结束*************************************************

对Song类进行排序

再看一下sort方法中<T extends Comparable<? super T>>,然而String只实现了Comparable<String>接口,所以此处的extends意为extend或者implement,表示的意思是“是一个”,且不管接口或类都适用。

我们再看一下Comparable接口,它只有一个方法

http://www.cjsdn.net/Doc/JDK60/java/lang/Comparable.html

也就是说,除了java给我们已经implement该接口的类,我们自己的类只要implement接口并实现方法,就可以sort了。假设我有个song类(title,artist,rating,bpm四个实例),我想按照title顺序排序,我只需要

class song implement Comparable<Song>{public int comparaTo(Song s){return title.compareTo(s.getTitle());}}
即可用sort方法对ArrayList(Song)进行排序。

那如果又有新的要求了(天啊),需要用artist进行排序,而我们的compareTo只能实现一个啊!怎么办?则用sort的重载办法更加合理


而这个Comparator又是什么?http://www.cjsdn.net/Doc/JDK60/java/util/Comparator.html

所以,如果传Comparator给sort()方法,则排序是由Comparator而不是元素的comparaTo()方法来决定。所以需要以下几步就可以实现更新操作:

(1)创建并实现Comparator的内部类,以compare()方法取代compareTo()方法。

(2)制作该类的实例。

(3)调用重载版的sort(),传入歌曲的list以及Comparator的实例。

关键操作如下:

......class ArtistCompare implements Comparator<Song>{    public int compare(Song one,Song two){}{return one.getArtist().compareTo(two.getArtist());}}......ArtistCompare artistCompare = new ArtistCompare();Collections.sort(songList,artistCompare);

这样就可以很方便的进行更新操作。

解决重复问题

出现问题:数据有重复

解决方案:我们来看看Collection还有哪些宝藏

(1)List:对付顺序的好帮手

是一种知道索引位置的集合

List知道某物在系列集合中的位置。可以有多个元素引用相同的对象。

(2)SET:注重独一无二的性质

不允许重复的集合

它知道某物是否已经存在于集合中。不会有多个元素引用相同的对象。

(3)MAP:用key来搜索的专家

使用成对的键值和数据值。

Map会维护与key有关联的值。两个key可以引用相同的对象,但key不能重复,典型的key会是String,但也可以是任何对象。

尝试1:采用HashSet取代ArrayList

如果直接这样做

HashSet<Song> songSet = new HashSet<Song>();songSet.addAll(SongList);//SongList为ArrayList
则此时打印出的结果不但没有去重,还被打乱了顺序。所以  对象要怎样才算相等?这带出两个议题

引用相等性:堆上同一对象的两个引用相等(hashCode()方法返回相同(返回的是每个对象特有的序号)且引用的字节数也会一样(==得出相同))。

对象相等性:堆上的两个对象在意义上是相等的。(必须通过覆盖hashCode()方法以及equals()方法才可以)

所以HashSet检查重复是靠hashCode()以及equals()。


穿插问题:为什么有hashCode()还要equals()?

解答:因为杂凑算法会存在碰撞的情况,两个不同的对象会传回相同的杂凑值。也就是说,hashCode是用来缩小寻找成本,但最后还是要用equals()才能认定是否真的找到相同的项目。


回归问题,我们在song中重写这两个方法就哦啦!

public boolean equals(Object aSong){Song s = (Song) aSong;return getTitle().equals(s.getTitle());//因为歌名是String,且String本来就覆盖过的equals().所以我们可以调用}public int hashCode(){return title.hashCode();//String也覆盖过hashCode.注意hashCode()与equals()使用相同的实例变量}


出现问题:数据不重复了,但顺序又乱了

尝试方法2:用TreeSet

TreeSet<Song> songSet = new TreeSet<Song>();//调用没有餐宿的构造函数来用TreeSet取代HashSet意味着以对象的compareTo()方法来进行排序songSet.addAll(SongList); 
当然也可以用comparator去排序,写好comparator塞入它就好啦!和前面sor方法类似,就不写了。


***********************************************分割线**********************************************************************************************************************************

Map简单了解

问题结束,再来看一下Map。

Map中的元素实际上是两个对象:关键字和值。值可以重复,但是key不行。一个例子就懂基本了:

import java.util.*;public class newTest{public static void main(String[] args){HashMap<String,Integer> scores = new HashMap<String,Integer>();scores.put("Kathy",42);scores.put("Bert",343);scores.put("Skyler",420);System.out.println(scores);System.out.println(scores.get("Bert"));}}

结果输出正确。

泛型终决战

终于回到泛型,以多态化的观点来看之前的泛型,发现有一些古怪的,接下来我们再深入一些验证一下。

先定义三个类,它们有继承关系

abstract public class Animal {void eat() {System.out.println("animal eat");}}
public class Cat extends Animal {void meow() {}}
public class Dog extends Animal {void bark() {}}
使用多态参数与泛型

import java.util.ArrayList;import java.util.List;public class TestGenericsl {public static void main(String[] orgs){new TestGenericsl().go();}public void go(){ArrayList<Animal> animals = new ArrayList<Animal>();animals.add(new Dog());animals.add(new Cat());animals.add(new Dog());takeAnimals(animals);}public void takeAnimals(ArrayList<Animal> animals){for(Animal a:animals){a.eat();}}}
输出结果没有问题

这和我们之前用的Animal[ ]数组(稍微想象一下)的工作方式差不多。

幺蛾子来啦!此时修改一下片段:

//ArrayList<Animal> animals = new ArrayList<Animal>();ArrayList<Dog> dogs = new ArrayList<Dog>();//animals.add(new Dog());dogs.add(new Dog());//animals.add(new Cat());dogs.add(new Dog());//animals.add(new Dog());//takeAnimals(animals);takeAnimals(dogs);
此时编译器会告诉你

The method takeAnimals(ArrayList<Animal>) in the type TestGenericsl is not applicable for the arguments (ArrayList<Dog>)

多态的意义在于Animal可以做的事情,Dog也都能做。所以对Dog引用调用eat()应该没问题啊。但是如果该方法里如下:

public void takeAnimals(ArrayList<Animal> animals){for(Animal a:animals){animals.add(new Cat());a.eat();}}
那就会出问题了,Dog中会混进一个Cat。所以如果把声明成取用ArrayList<Animal>,它就只会去用ArrayList<Animal>参数,ArrayList<Dog>与ArrayList<Cat>都不行。

而同样的问题,数组会在运行时被java虚拟机发现。因为数组的类型是在运行期间检查的,但集合的类型检查只会发生在编译期间。那怎么多态化集合参数呢?

解决办法:采用万用字符

public void takeAnimals(ArrayList<? extends Animal> animals){for(Animal a:animals){a.eat();}}
这样就可以传入小猫小狗了,但是注意:在该方法中,编译器不允许加入任何东西到集合众!

还有另一种方法声明,效果一样,根据情况自己选用:

public <T extends Animal> void takeThing(ArrayList<T> list)













1 0