Java基礎(chǔ)知識填坑繼續(xù)。
Java 集合與泛型
數(shù)組 VS ArrayList
數(shù)組大概是我們學(xué)習(xí)任何語言時接觸到的第一個集合碑韵。
String[] strs = new String[10];
strs[0] = "a";
strs[1] = "b";
List<String> lists = new ArrayList<>();
數(shù)組也是對象;相較于普通的數(shù)組作瞄,ArrayList在創(chuàng)建時不必指定大小,會在進(jìn)行增刪操作時動態(tài)的調(diào)整自己的大小桐愉。
數(shù)組與List相互轉(zhuǎn)換
//數(shù)組轉(zhuǎn)換為List
lists = Arrays.asList(strs);
//List 轉(zhuǎn) 數(shù)組
strs = lists.toArray(strs);
將集合中的對象進(jìn)行排序
使用Collections.sort()方法對集合中的對象進(jìn)行排序的兩種方式财破。
- 該對象實(shí)現(xiàn)了Comparable接口,明確指定了排序方式从诲。
public class Student implements Comparable<Student>{
private String name;
private int id;
public Student(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public int compareTo(Student student) {
//按照name(字符串值)從小到大排序
return name.compareTo(student.name);
}
}
這樣左痢,由Student對象構(gòu)成的集合就可以使用Collections.sort()方法進(jìn)行排序了。
List<Student> students = new ArrayList<>();
for(int i=0;i<10;i++) {
Student student = new Student(i + "-name", i);
students.add(student);
}
Collections.sort(students);
- 實(shí)現(xiàn)Comparator接口系洛,動態(tài)定義排序方式俊性。
private static class ComprareByName implements Comparator<Student> {
@Override
public int compare(Student student, Student t1) {
//按name 從大到小進(jìn)行排序
return t1.getName().compareTo(student.getName());
}
}
這樣就可以使用重載的sort方法進(jìn)行排序
Collections.sort(students,new ComprareByName());
Comparator的compare實(shí)現(xiàn)方式會覆蓋集合元素中默認(rèn)的實(shí)現(xiàn),也就說雖然Student內(nèi)部是從小到大排序描扯,但會被這里ComprareByName的實(shí)現(xiàn)所覆蓋定页。
為了使集合有序,我們也可以使用TreeSet绽诚。
TreeSet 以有序的狀態(tài)存儲元素典徊,并防止重復(fù)杭煎。
因此,為了正確的使用TreeSet 需要注意以下兩點(diǎn):
- 同上面第一點(diǎn)宫峦,加入到TreeSet集合中對象(元素)必須實(shí)現(xiàn)了Comparable接口岔帽。
- 使用重載,用Comparator參數(shù)的構(gòu)造函數(shù)來創(chuàng)建TreeSet导绷。
TreeSet<Student> mStudents = new TreeSet<>(new ComprareByName());
集合分類
- Collections
- List 索引位置明確的集合
- Set 不允許重復(fù)元素的集合
- Map 使用成對key和value的集合
以上三種集合的類圖如下:
從中可以看到我們常用的一些類犀勒,如ArrayList,HashMap 等妥曲。
如何檢查對象的重復(fù)性贾费?如何判定兩個對象相等?
Student a = new Student("a", 1);
Student b = new Student("b", 2);
Student c = a;
Student d = new Student("b", 2);
System.err.println("a.hashCode()="+a.hashCode());
System.err.println("b.hashCode()="+b.hashCode());
System.err.println("c.hashCode()="+c.hashCode());
System.err.println("d.hashCode()="+d.hashCode());
輸出
a.hashCode()=1118140819
b.hashCode()=1975012498
c.hashCode()=1118140819
d.hashCode()=1808253012
通過打印a,b,c,d 四個對象的hashcode 值檐盟,可以看到引用變量a和c 指向的是堆上的同一個對象褂萧,因此他們的hash值必然是相等的。引用對象b和d雖然創(chuàng)建的對象內(nèi)容是一致的葵萎,但他們?nèi)稳皇欠謩e指向兩個不同的對象导犹,因此hash值也是不同的。
下面我們用equals 方法比較一下這四個對象
System.out.println("a.equals(b) " + a.equals(b));
System.out.println("a.equals(c) " + a.equals(c));
System.out.println("b.equals(d) " + b.equals(d));
輸出
a.equals(b) false
a.equals(c) true
b.equals(d) false
結(jié)果很明顯羡忘,因?yàn)镺bject的equals 默認(rèn)執(zhí)行的是對象引用是否相等的比較谎痢,因此b.equals(d)的結(jié)果為false。
public boolean equals(Object obj) {
return (this == obj);
}
但是卷雕,從我們創(chuàng)建對象的代碼可以得知节猿,b和d 這兩個對象內(nèi)容是一樣的;因此漫雕,這兩個對象就應(yīng)該是同一個滨嘱,他們應(yīng)該是相等的;因此浸间,我們可以覆蓋默認(rèn)的hashCode()和equals()方法太雨。
public class Student implements Comparable<Student>{
private String name;
private int id;
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object o) {
Student mStudent= (Student) o;
return getName().equals(mStudent.getName());
}
public String getName() {
return name;
}
}
現(xiàn)在再次測試,就可以看到b.equals(d)的結(jié)果為true了魁蒜。
因此躺彬,我們可以得出以下結(jié)論:
- 如果兩個對象相等,則hashcode值必須相等
- 兩個對象的hashcode值相等梅惯,他們也不一定是相等的宪拥。(當(dāng)然,這種幾率應(yīng)該很邢臣酢)
- equals()默認(rèn)執(zhí)行的是== 比較她君,也就是說會去測試兩個引用是否對堆上同一個對象引用;因此葫哗,對于我們自己創(chuàng)建的對象缔刹,應(yīng)該同時覆蓋equals()方法和hashCode()方法球涛,規(guī)范檢測對象一致性的標(biāo)準(zhǔn)。
泛型
使用泛型可以構(gòu)建出類型更加安全的集合校镐,讓問題盡可能的在編譯期就被發(fā)現(xiàn)亿扁,而不是等到了執(zhí)行期才冒出來
public class ArrayList<E> extends AbstractList<E> implements List<E>
以常用的ArrayList為例,使用泛型后代表以后所有對ArrayList的操作鸟廓,添加从祝,刪除或返回的對象類型都是E,不能是其他類型引谜。
List<Student> students = new ArrayList<>();
for(int i=0;i<10;i++) {
Student student = new Student(i + "-name", i);
students.add(student);
}
students 中添加的元素只能是Student類型的牍陌,不能是其他;同時從students結(jié)合中獲取到的對象也一定是Student類型员咽。
從泛型的角度看毒涧,extends和implements是等價的,都表示當(dāng)前類是一個……贝室。對ArrayList來說契讲,他既是一個AbstractList,也是List滑频。
當(dāng)泛型遇到多態(tài)
現(xiàn)在有Student的子類:SeniorStudent和CollegeStudent怀泊。
private static void printStudents(List<Student> students) {
for (Student mStudent : students) {
System.out.println(mStudent.getName());
}
//面對這樣的情況,這個方法只能接受List<Student>類型的參數(shù)
students.add(new SeniorStudent("hacker", 999));
}
public static void main(String[] args) {
ArrayList<Student> students = new ArrayList<>();
//因?yàn)榉盒臀笈浚琇ist現(xiàn)在可以接受所有Student類型的對象
students.add(new Student("mike",001));
students.add(new CollegeStudent("lucy",002));
students.add(new SeniorStudent("tom", 003));
//
printStudents(students);
List<CollegeStudent> colleges = new ArrayList<>();
colleges.add(new CollegeStudent("a", 100));
colleges.add(new CollegeStudent("b", 101));
colleges.add(new CollegeStudent("c", 102));
//這樣做是不行的
printStudents(colleges);
}
在上面的代碼中,printStudents(List<Student> students)务傲,我們可以這樣
printStudents(ArrayList<Student>)
但卻不能這樣
printStudents(List<CollegeStudent>)
在泛型方法中凉当,參數(shù)中的集合可以是多態(tài),但集合中的對象不能是多態(tài)售葡。其中的道理我們通過printStudents 方法中最后一行語句很容易理解看杭。因?yàn)槟悴荒鼙WC使用集合的方法,會對集合做怎樣的操作挟伙。為了保證集合的安全性楼雹,這是很好的做法。
但是尖阔,這樣不就喪失了多態(tài)的意義嗎贮缅?如果不能用子類作為集合的類型,那難道要為每一個Student的子類型介却,單獨(dú)寫一個printStudents()方法嗎谴供? 其實(shí)不必,只要做如下改動即可:
private static void printStudents(List<? extends Student> students) {
for (Student mStudent : students) {
System.out.println(mStudent.getName());
}
//當(dāng)使用通配符聲明后齿坷,將不能再向集合中添加元素桂肌,因此以下語句非法
students.add(new SeniorStudent("hacker", 999));
}
這種情況雖然從語法角度看似合理数焊,但編譯器會幫我們做限制,限制再次修改集合中的元素
這樣printStudents(List<CollegeStudent>)就變得合法了崎场。
當(dāng)然佩耳,為了更容易理解,也可以這樣聲明:
private static <T extends Student> void printStudents(List<T> students) {
for (Student mStudent : students) {
System.out.println(mStudent.getName());
}
//當(dāng)使用通配符聲明后谭跨,將不能再向集合中添加元素干厚,因此以下語句非法
students.add(new SeniorStudent("hacker", 999));
}