前言:Java集合框架能支持大多数数据结构,如创建自动排除重复项目的列表,可加入元素的列表
应用场景:点歌数据记录在一个文本文件
目标:管理点播记录,产生报表,管理歌本(依靠内存上的数据集合和记录用的文本文件)
前面已经叙述如何读取与解析文件,并用ArrayList排序
import java.util.*;
import java.io.*;
public class Jukebox1 {
ArrayList<String> songList = new ArrayList<String>();
public static void main(String[] args){
new Jukebox1().go();
}
public void go(){ //载入文件并列出内容
getSongs();
System.out.printIn(songList);
}
void getSongs(){
try{
File file = new File("SongList.txt");
BufferedReader reader = new BufferedReader(new FileReader(file));
String line = null;
while((line = reader.readLine()) != null){
addSong(line);
}
}catch(Exception ex){
ex.printStackTrace();
}
}
void addSong(String lineToParse){
String [] tokens = LineToParse.split("/") //用反斜线拆开歌曲内容
songList.add(tokens[0]); //只添加歌名
}
}
上例问题在于歌曲顺序不是按字母排序,查询ArrayList没有排序方法
ArrayList不是唯一的集合,还有其他集合,比如
TreeSet
以有序状态保持并可防止重复
HashMap
可用成对的name/value保存与取出
LinkedList
针对经常插入和删除元素所设计的集合
HashSet
防止重复的集合,可快速找寻相符的元素
LinkedHashMap
类似哈希图,但可记住元素插入顺序,也可设定为依照元素上次存取的先后来排序
综上,将字符串放入哈希图可确保按字母排序,但当增加新元素时,会增加排序的成本(ArrayList直接放在后面就行了)
Collections类有个sort()方法,参数为list,而ArrayList实现了list接口,所以根据多态机制,可以把ArrayList传入该方法,则修改为:
public void go(){ //载入文件并列出内容
getSongs();
System.out.printIn(songList);
Collections.sort(songList);//按字母排序
System.out.printIn(songList);
}
程序要求提升
list里面改为Song这个类的实例
toString()定义在Object类中,所以所有类都会继承到,因为对象被 System.out.printIn(object);列出时,都会调用toString(),所以当把list列出时,每个对象的toString()都会被调用一次,
class Song{
String title
String artist;
String rating;
String bpm;
Song(String t,String a ,String r,String b){
title=t;artist=a;rating=r;bpm=b;
}
publiv String getTitle(){
return title;
}
public String getArtist(){
return artist;
}
.....
public String toString(){
return title;//覆盖该方法,返回歌名
}
}
修改
ArrayList<Song> songList = new ArrayList<Song>();
void addSong(String lineToParse){
String [] tokens = LineToParse.split("/") //用反斜线拆开歌曲内容
Song nextSong = new Song(tokens[0],tokens[1],tokens[2],tokens[3]);
songList.add(nextSong); //只添加歌名
}
但编译型报错,表示没有sort()方法,问题出现了:ArrayList可应对string对象但应对不了song对象
重新看API文件,sort()的参数为List<T>,因为sort运用到泛型,<>表示泛型
泛型意味着更好的类型安全性
几乎所有以泛型写的程序都与处理集合有关,泛型主要目的是写出有类型安全性的集合即让编译器帮忙防止将dog加入cat集合中
泛型之前,所有集合都写成处理Object类型,可以把任何对象放入集合中
没有泛型
各种对象以引用形式加入到ArrayList中,没办法声明ArrayList的类型
取出来时会是Object的引用
使用泛型
仅有相应对象能以引用形式加入ArrayList中,创建类型安全性更好的集合
取出来是会是某一特定对象(如鱼对象)的引用
关于泛型
1)创建被泛型化类(如ArrayList)的实例 /创建泛型的类的实例
创建ArrayList时必须指定所容许的对象
new ArrayList<Song>()
2)声明与指定泛型类型的变量
多态遇到泛型类型
List<Song> songlist = new ArrayList<Song>()
3)声明与调用取用泛型类型的方法
若某方法取用Song对象ArrayList的参数
void foo(List<song> list)
使用泛型的类
ArrayList是最常用的泛型化类,ArrayList的文件如下
public class ArrayList <E> extends AbstractList<E> implements List<E>...{
public boolean add(E o)
//more
}
E代表ArrayList中元素类型(类型参数),ArrayList是AbstractList的子类,所以指定给ArrayListd的类型会自动用在AbstractList上,注意实现了list接口
泛型的类代表类的声明用到类型参数,泛型的方法代表方法声明,特征用到类型参数
方法中类型参数的不同运用方式
1)使用定义在类声明的类型参数
public class ArrayList<E> extends AbstractList <E>...{
public boolean add(E o)//只能在此使用E,因为其被定义为类的一部分
当声明类的类型参数时,可以直接把类或接口类型用在任何地方,如add(E o),参数的类型声明会以用来初始化类的类型,即ArrayList<E>中的<E>来取代
2)使用未定义在类声明的类型参数
public <T extends Animal> void take(ArrayList<T> list)
前面已经声明T,此处ArrayList<T> list可以使用<T>
若类(ArrayList)本身未使用类型参数(<T extends Animal>),可以通过其他位置指定给方法(在返回类型之前,void之前)
对比
1)public <T extends Animal> void take(ArrayList<T> list)
2)public void take(ArrayList<Animal> list)
两语句意义不同,<T extends Animal>是方法声明的一部分,表示任何声明为Animal或其子类的ArrayList是合法的,可使用其子类ArrayList<Dog>来调用方法
但ArrayList<Animal> list代表只能此类型是合法的,子类不可以
回到sort()方法,对Song对象不能排序,String对象则可以,因为其只能接受Comparable对象或其子类的list,Song不符合条件,所以不行
public static <T extends Comparable<? super T>>void sort(List<T> list)
<? super T>表示Comparable的类型参数必须是T的或T的父型
List<T> list表示仅能传入继承Comparable的参数化类型的list
Comparable实际是个接口,String只是实现该接口,extends似乎不合理
以泛型的观点来说,extend代表extend或implement,适用于类和接口
Java有提供一种对参数化类型加上限制的方法,所以可加上限制,如只能是Dog的子类,但有时该限制是只允许有实现某特定接口的类,所以说需要出现皆可适用的语法,即
extend代表extend或implement
<T extends Comparable>理解为实现该接口的类型
所以Song类需要实现Comparable接口,即
public interface Comparable<T>{
int compareTo(T o);
}
compareTo比较大小的方式:从某个Song对象来调用,然后传入其他Song对象的引用,进行判别谁先谁后,返回负值,代表传入方法的对象大于执行方法的对象,反之,相反,返回0,代表相等(只是该比较大小方式相等)。
更新,更好,更comparable的Song类
String本身有方法来比较字母的先后顺序
程序修改为:
class Song implements Comparable<Song>{
String title
String artist;
String rating;
String bpm;
public int compareTo(Song s){//s为要传入比较的对象
return title.compareTo(s.getTitle());//title为String类型,待比较的歌曲名
Song(String t,String a ,String r,String b){
title=t;artist=a;rating=r;bpm=b;
}
集合的元素排序只能实现一个compareTo(),即sort()方法只能按一种方式排序
需要增加按歌星排列的情况
查询API,发现sort()方法可取用Comparator参数
sort()有重载版本,可以取用Comparator参数,还需要取得能比较歌星的Comparator
使用自制的Comparator
使用compareTo()方法时,sort()方法只能按一种方式排序,但Comparator是独立于所比较元素之外的,即是独立的类,可采用不同的比较方法,再调用参数为List和Comparator的sort()方法
取用参数为List和Comparator的sort()方法,则该方法以Comparator来排序,进一步地调用Comparator的compare()方法
public interface Comparator<T>{
int compare(T o1,T o2);
}
总结:
1)调用单一参数的sort(List o)方法代表由list元素的compareTo()方法决定顺序,所以元素必须实现Comparable这个接口
2)调用sort(List o,Comparator c)方法,则该方法以Comparator来排序,进一步地调用Comparator的compare()方法,元素不需要实现Comparable这个接口
新的版本
(1)创建并实现Comparator的内部类,以compare()方法来取代compareTo()方法
(2)创建Comparator的实例
(3)调用重载版的sort(),传入歌曲的list和Comparator的实例
修改内容
class ArtistCompare implements Comparator<Song>{
public int compare(Song one,Song two){
return one.getArtist().compareTo(two.getArtist());
//String的compareTo()方法,one.getArtist()返回String
}
}
ArtistCompare artistCompare = new ArtistCompare();
Collections.sort(songList,artistCompare);