接18.1内容,若排序过程,出现数据重复,则需要使用Set集合
Collection的三个主要接口:List,Set和Map
1)LIST:应对排序问题
知道索引位置的集合,但可能多个元素引用相同的对象
2)SET:注重唯一性
不允许重复
3)MAP:用key来搜索的专家
使用键值对,两个key可能引用相同的对象,但key不可重复,key可以是String,但也可以是任何对象
MAP特殊性在于实际没有继承Collection这个接口,但是其体系框架的一份子,即map也是Java集合的一种
HashSet取代ArrayList后程序代码
HashSet<Song> songSet = new HashSet<Song>();
songSet.addAll(songList);//复制其他集合元素加入新的集合
System.out.printIn(songSet);
但发现set还是会重复
对象的相等和引用的相等
引用相等性
引用到堆上的同一对象的两个引用是相等的,若对这两个引用调用hashCode()会得到相同的结果,hashCode()未覆盖情况下返回每个对象的特有序号(大部分按照内存位置计算)
可使用==来比较变量上的字节组合来判断引用是否相等,若引用到相同的对象,字节组合也会一样
Song foo;
Song bar;
if(foo == bar){
//}
foo.equals(bar);
对象相等性
堆上的两个不同对象在意义上是相同的
若想把两个不同的Song对象视为相等,则必须覆盖hashCode()方法和equals()方法
,只有这样,才能确保对象有相同的hashCode,且equals()返回true
if(foo.equals(bar) && foo.hashCode() == bar.hashCode()){
}
HashSet检查重复
其检查重复的方法是通过hashCode来进行,若相等,再调用其中以对象的equals()方法来检查是否真的相同,若真相同,则加入的操作不发生
有覆盖过hashCode()和equals()的Song类
public boolean equals(Object aSong){
Song s = (Song) aSong;
return getTitle().equals(s.getTitle());//调用String的equals()方法
public inthashCode(){
return title.hashCode(); //调用String的调用hashCode()方法
}
API文件对对象的状态制定出必须遵守的规则
1)对象相等,其hashCode必定相等,反之,不成立
2)对象相等,其中一对象调用equals()时,必定返回true
3)equals()被覆盖,则hashCode()也必须被覆盖
4)hashCode()的默认行为是对在heap,即堆上的对象产生独特的值,若没有覆盖此方法,某类的两个对象无论如何都不会相同(解释了明明相同的歌名却认为不一样)
5)equals()的默认行为是执行==的比较,会去测试两个引用是否是对堆上的同一个对象,若没有覆盖此方法,某类的两个对象无论如何都不会相同,因为不同对象有不同的字节组合
a.equals(b)必须与a.hashCode() == b.hashCode()等值,反之,不成立
若想要保持有序,使用TreeSet
TreeSet防止重复的方法类比HashSet,但其会一直保持集合有序状态,若使用TreeSet默认的构造函数,即没有参数,它工作起来会像sort()一样使用对象的compareTo()方法来排序,但也可以选择传入Comparator给TreeSet的构造函数
TreeSet<Song> songSet = new TreeSet<Song>();
//默认的构造函数,使用对象的compareTo()方法来排序
songSet.addAll(songList);
TreeSet用法
下列方式二选一
1)其元素一定是Comparable,即必须指定对象排序方法,不然执行TreeSet的add()方法时,再添加第二个元素时,会提示无法调用对象的compareTo()方法
2)使用重载,取用Comparator参数的构造函数来创建TreeSet
public class BookCompare implements Comparator<Book>{
public int compare(Book one, Book two){
return (one.title.compareTo(two.title));
}
}
BookCompare bCompare = new BookCompare();
TreeSet<Book> tree = new TreeSet<Book>(bCompare);
Map
使用名称来取得值,key通常是String,但也可以用任何Java对象(或通过autoboxing的primitive)
Map中元素实际上是两个对象:关键字和值,关键词(key)不可重复,值可以
HashMap需要两个类型参数
HashMap<String,Integer>scores = new HashMap<String,Integer>();
scores.put("kate",12);
scores.put("Tom",11); //put()取代add(),且参数为两个
System.out.printIn(scores.get("Tom"));//get()取用关键字参数,返回值
//列出方式为key=value形式打印出,外面以{}包裹
回到泛型
以多态的观点来说,取用泛型参数的方法古怪?首先回顾数组参数如何以多态化运行,然后再看同样的工作以泛型集合来完成
如方法的参数为animal的数组,则也可以取用animal子类,如Dog类型的数组,即
void foo(Animal[] a){}
则
foo(anAnimalArray);
foo(aDogArray);
普通数组工作方式
public void go(){
Animal[] animals = {new Dog(),new Cat(),new Dog()};//带有Dog和Cat
Dog[] dogs = {new Dog(),new Dog(),new Dog()};
takeAnimals(animals);
takeAnimals(dogs); //多态发挥作用
public void takeAnimals(Animal[] animals){
for(Animal a: animals ){
a.eat();
//只能调用声明在Animal中的方法,因为其参数是Animal数组,且无需类型转换
//而且猫狗怎么转换?
}
数组Array换成ArrayList
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)
以上的方法参数为ArrayList<Animal>,只会取用ArrayList<Animal>这一参数,在数组中传入其子类数组也行,但ArrayList<Dog>不行(为了不让猫混入狗的ArrayList)
而在数组中猫不会混入狗的数组原因是数组类型是在运行期间检查的,但集合类型检查只会发生在编译期间,即如此操作,会通过编译,但JVM会发现
万用字符
为了解决上述需求(即能使用多态化集合参数,但又能避免上述矛盾),有一种能创建出接受Animal子型参数的方法,即使用万用字符
public void takeAnimals(ArrayList<? extends Animal>animals)
使用带有<?>的声明时,编译器不会让你加入任何元素到集合中,extends同以前所述,既代表继承又代表实现(extends+接口)
在方法参数中使用万用字符,编译器会阻止任何可能破坏引用参数所指集合的行为,能调用list中任何元素,但不能加入元素,保障执行期间的安全性
相同功能的另一种语法
返回类型声明类型参数(只声明一次,使用到T时以该方式声明)
public <T extends Animal>void takeThing(ArrayList<T> list)
等价于
public void takeThing(ArrayList<? extends Animal> list)
所谓只说明一次,见下例
public <T extends Animal>void takeThing(ArrayList<T> list1,ArrayList<T> list2)
而不用
public void takeThing(ArrayList<? extends Animal> list1,ArrayList<? extends Animal> list2)
结语:研一上学期自学Java告一段落,寒假我来啦!