一 、抽象類
1.1 、什么是抽象類?
- 就是當一個類不能描述具體的對象時,那么這個類就可以寫成抽象類。比如說 Animal ,我們知道 Animal 不能非常清楚的描述一個具體的動物,所以可以把 Animal 寫成抽象類。還有就是我們知道父類中的方法,我們在子類中可能需要重寫,我們想,當調用重寫方法時,肯定是去調用子類中的重寫方法,所以父類中的重寫方法中的方法體(即方法的實現那部分代碼)就會顯得很多余(就是用不到),那么我們就可以使用抽象類來讓這個方法變為抽象方法,此時就不需要實現方法體了
1.2、抽象類的語法
- 畢竟還是個類,所以還需要class修飾
- 還需要加上 abstract關鍵字修飾
public abstract class Animal(){}
1.2.1、抽象類中的抽象方法
- 使用abstract 修飾成員方法就能把這個方法變為抽象方法
- 當我們把抽象類中的成員方法變為抽象方法時,那么這個抽象方法就不需要再實現方法體(就是不需要寫方法里面的代碼)
- 并且在子類中必須要重寫父類中的抽象方法,除非這個子類是抽象類,如果是抽象類就不需要重寫,直到有普通類繼承,就要重寫
public abstract void eat();
1.3、抽象類的特征
- 使用 abstract 關鍵字修飾類
- 抽象類是不能實例化對象的,但是他有什么用呢? 他是只能被繼承,那么作為父類有什么優點呢?下文知曉,雖然不能實例化對象,但是可以用抽象類來定義引用,這點和普通類一樣,都是為了實現類型統一,即讓代碼發生向上轉型,進而發生多態
- 為什么不能實例化對象呢? 因為抽象類是不能很具體的描述一個對象?并且 里面的抽象方法沒有方法體
- 抽象類中可以有和普通類一樣的 普通的成員(包含成員變量和成員方法),也可以有抽象方法,也可以含有構造方法,但是構造方法不能變成抽象方法,因為抽象方法是被重寫的,而構造方法是不能重寫。
- 因為抽象方法需要重寫,所以不能使用 static ,private,final,修飾
public abstract void Animal(){public int a ;public void eat(){sout("正在吃法....")
}
// 抽象方法不需要實現方法體public abstract void run();}
- 含有抽象方法的類 ,一定是抽象類,否則報錯
- 抽象類中不一定必須得有抽象方法,沒有抽象方法這個抽象類也能定義
- 當一個普通類繼承了抽象類后,這個普通類需要重寫父類抽象類中的抽象方法
- 如果一個抽象類A 繼承了抽象類B,那么在抽象類A中可以不重寫抽象類B中的方法,但是當一個普通類繼承C 繼承了這個抽象類A,此時在普通類C中不僅要重寫抽象類B當中的方法還要重寫抽象類A的方法
1.4、抽象類的好處
- 抽象類中的抽象方法不需要實現方法體,使得代碼變得簡單
- 抽象類中的抽象方法在(非抽象類)子類中一定需要重寫,如果子類中沒有重寫抽象方法就會報錯,所以幫我們效驗代碼的合法性。如果這個子類是抽象類,那么抽象類就不需要重寫,直到有普通類繼承,就要重寫
1.5、抽象類和普通類的區別
- 抽象類需要使用 abstract 修飾
- 抽象類有抽象方法,并且抽象方法不需要實現方法體
- 抽象類不能實例化對象
二、接口
- 為什么要有接口?
我們知道,Java 是不支持多繼承的(就是一個類繼承多個類),為了彌補這個缺點,便有了接口,接口是支持實現多個的,并且接口是支持多繼承的(就是一個接口繼承多個接口)
// 類和接口之間使用implements,就一個類實現倆個接口public class Dog implements IRunning,ISwimming{}
//接口與接口之間用extends,就是用一個接口繼承(實現)兩個接口
interface IAmphibious extends IRunning,ISwimming{}
// 和上面的Dog 效果一樣
public class Dog implements IAmphibious{}
2.1、什么是接口?
- 接口:顧名思義,就好像電腦的USB接口一樣,全部的USB接口都是遵循一個通訊協議的,就像鼠標,鍵盤連接電腦的USB線,只要需要使用,拿起來插入電腦就行。
- 所以接口是公共的一個功能,如哪個類需要使用,實現這個功能的接口就可以。我們知道 Dog 會跑, Fish 會游泳。那么我們知道,實現這兩個方法不難啊,在Dog類中把 running 這個方法寫為 Dog類中的特色方法不就行了嗎,在Fish類中把swimming這個方法寫成 Fish 類中的特色方法也不就行了嗎,確實是可以,但是我們想一下,如果再有Cat呢? Cat 也會跑,那我們又要在Cat 類在中寫一個running的特色方法,那么不就是會跑的動物,都需要我們寫running的方法嗎。那么有什么方法能讓我們不寫這個running呢?有人又說,那我們把 running 和 swimming 都寫在父類Animal中,不就解決了嗎?但是我們要知道,一個父類中的成員是所有子類所共有的成員,那么我問你,所有的動物都會游泳和跑嗎?答案是不行的。所以此時就有了接口,一個接口我們可以設置一個功能,比如說跑,游泳,當我們需要用這個功能時,實現接口就行,比如說Dog ,Cat 實現跑這個接口,Fish 實現游泳這個接口就行
2.2 、接口的語法
- 創建接口 用 interface 關鍵字 比如創建一個IRunning接口
interface IRunning {}
- 在類實現接口時,使用 implements 關鍵字 類可以實現多個接口
public class Dog implements IRunning,ISwimming{}
- 在接口繼承接口時,使用extends關鍵字 接口可以繼承多個接口
interface IAmphibious extends IRunning,ISwimming{}
2.3、接口的規則
- 接口的名字一般第一個和第二個字母大寫,并且第一個字母是I 。如IRunnig ,ISwimming。還有結尾是able的
- 還有接口的訪問權限和修飾符,默認是 public abstract interface,也就是說接口是抽象的
public abstract interface IRunning{}
2.4、接口的特性
- 接口也是不能實例化對象的
- 接口一般都是 public abstract interface 修飾的,也就是說是抽象的 ,換句話說,類實例化了這個接口,就必須重寫接口 里面 的方法,除非這個類是抽象類就不需要重寫,但是出來混遲早要還,什么意思呢?就是當有類繼承這個抽象類時,就必須重寫抽象類中的方法,還要重寫接口里面的方法
- 依然像類一樣可以發生向上轉型,動態綁定,多態
2.4.1、接口中的成員
- 成員方法是 默認public abstract + 返回值類型 修飾的.換句話說接口里面的方法默認是抽象方法 ,抽象方法不需要實現方法體,并且在實例化接口的類中,必須重寫接口里面的方法。在類中的重寫方法,訪問修飾限定符只能是public。但是如果非要實現這個抽象方法的話,使用static 或 default修飾后就能實現方法體
public abstract void run();
- 成員變量是默認 public static final 修飾的。換句話說,這個變量是常量,并且是靜態的,所以定義的同時就要初始化 ,所以接口里面不需要構造方法并且也不能有靜態代碼塊
public static final a = 10;
2.5、類和接口的關系
- 類實現接口需要使用 implements 關鍵字
public class Dog implements IRunning,ISwimming{}
- 因為接口是abstract修飾的,里面的成員方法也是abstract修飾的。所以在類中需要重寫接口里面的方法
- 如果是抽象類實現接口,就不需要重寫,但是有類繼承這個抽象類后,這個類就要重寫抽象類里面的方法還有接口里面的方法
- 換句話說,一個類實現多個接口時,每個接口中的抽象方法都要實現,否則類必須設置為抽象類
2.6、接口和接口的關系
- 接口和接口之間的關系是繼承,所用的關鍵字是 extends
- 因為接口是抽象的,所以在 IAmphibious 接口中不需要重寫 IRunning 中的方法,當有類實現了IAmphibious這個接口,就要重寫IAmphibious接口和IRunning中的方法
- 接口間的繼承相當于把多個接口合并在一起
interface IAmphibious extends IRunning{}
2.7、普通類 和接口 的區別
- 普通類是 訪問修飾限定符 + class + 類名
- 接口是默認(系統指定) public abstract interface + 接口名
- 普通類中:有成員變量,成員方法,成員變量是 訪問修飾限定符 + 類型 + 變量名,成員方法是 訪問修飾限定符 + 返回值類型 + 方法名
- 接口中 :有成員變量,成員方法,成員變量默認(系統指定)是 public + static + final +類型 +變量名 + 初始化,成員方法默認(系統指定)是 public abstract 返回值類型 + 方法名
- 普通類中有構造方法
- 接口中沒有構造方法
- 都不能夠實例化對象
2.8、抽象類 和接口的區別
- 抽象類是 public abstract class + 類名
- 接口是默認(系統指定) public abstract interface + 接口名
- 區別是 類用class 接口用interface
- 抽象類中:可以有普通成員變量,普通成員方法,抽象方法
- 接口中:有默認(系統指定)是 public + static + final +類型 +變量名 + 初始化 的成員變量,和 抽象方法
- 抽象類中可以有構造方法
- 接口中沒有構造方法
- 都不能實例化對象
2.9、Comparable接口
- Comparable 是一個接口,他里面有個 compareTo 的抽象方法,作用是比較兩個對象,我們如果需要這個接口的功能就可以實現這個接口
- 可以簡單的認為這個接口是功能接口
- 有很多的類型都實現有這個接口,比如 String,Integer 等等,換句話說,這些類型都有屬于自己的比較規則。
- 但是有個缺點,你們發現沒,就是一旦你的比較規則寫好后,那么這個類實例化出來的對象就只能按照你的比較規則來走,什么意思呢?就是如果我在這個Student類中需要實現兩個比較規則,一個比較規則是比較age,一個比較規則就是name。那么compareTo 就很難實現這個要求。
- 還有就是這個比較規則寫好后,這個類中其他的方法也調用這個比較規則,那么我想要改變這個比較規則的話,其他的方法中的代碼也需要改變。
- 那怎么辦呢?,此時就有了下面的 Comparator 接口
public class Student implements Comparable<Studen>{public int age;public String name;public int compareTo(Student o){//根據自己需求實現return o.age - this.age;}
}
- 根據自己的需求實現 重寫方法 comparaTo 的方法體
- 還有就是在實現接口時,要加上比較比較 對象的類型 < Student >,如果沒加重寫方法的形參的 類型就是Object類型
還有注意 compareTo 方法是一個形參的
2.10、Comparator接口
- Comparator 也是一個用來比較對象的接口,里面有兩個抽象方法,一個是compare ,另一個是equals。
- compare 方法是兩個形參的,那么我說這個有什么用呢? 我的意思是有兩個形參,那么就意味著可以傳兩個對象,換句話說不像compareTo 那樣需要依賴對象。這里的不依賴對象和static那個不依賴對象有點區別。這里的不依賴對象是不依賴對象去調用。
- 所以說我們可以創建一個類專門用來實現一個比較規則,那么這種類就叫做比較器
class AgeCompare implements Comparator<Student>{public int compare(Student o1, Student o2) {return o1.age - o2.age;}
}
- 實現這個接口時,也需要告訴這個接口,你要比較的對象的類型,否則重寫方法中的形參的類型是Object類型
- 這樣寫的好處是,你想有多少個比較規則都沒問題
2.10.1、Comparable接口 和 Comparator接口 的區別
- 我們知道上文所說,Comparable 中的compareTo是只有一個形參的
- 而 Comparator 中的compare是有兩個形參的。那么我說 compareTo 是需要依賴對象的 ,而compare是不依賴對象的。(這里的依賴對象和static的依賴對象不一樣)
- Comparable 和 Comparator 是可以共存的,默認的話就是調用comparable中的compareTo ,有需要的話就調用比較器。在什么時候用到這些比較規則呢?比如說,我實例化了三個對象,分別是 student1 ,student2,student3。并用students 數組接收這三個對象。當我們需要這個數組排序時,Student類中有兩個成員變量,那么排序方法怎么知道我們要排序哪個變量呢? 此時的比較規則就起到了關鍵作用。排序方法會根據Student類中的compareTo 來進行排序。默認情況下,排序方法會判斷要排序的類 有沒有實現Comparable并重寫compareTo方法,如果沒有,就會拋異常。如果沒有實現Comparable并重寫compareTo方法,還可以通過給排序方法傳入我們自己寫的比較器。一句話:默認調compareTo ,調用傳compare;
public class Tect {public static void main(String[] args) {Student student1 = new Student(1,"1");Student student2 = new Student(2,"2");Student student3 = new Student(3,"3");Student[] students ={student1 ,student2 ,student3 };//默認使用Student類中的compareTo的比較方法Arrays.sort(students);//傳入比較器,進行排序AgeCompare ageCompare = new AgeCompare();Arrays.sort(students,ageCompare);}
}
class Tect {main(){Student student1 = new Student();Student student2 = new Student();//比較方法一:默認使用重寫比較規則student1.compareTo(student2);//比較方法二:傳入比較器,按比較器規則來AgeCompare agecompare = new AgeCompare();compare(student1,student2);}
}
這里如果我還需要比較Student中的name 的話,Comparable 中的compareTo就難以實現了,用Comparator 中的 compare 的話,還可以定義一個比較器,要使用就new對象,調用compare 傳兩個要比較的對象即可。這便彌補了Comparable的缺點
2.11、Clonable接口和深拷貝
- 了解深拷貝我們先知道淺拷貝,那什么是拷貝呢?
2.11.1、什么是拷貝?
- 拷貝就是復制出與原來一摸一樣的東西
- 那么代碼怎么實現拷貝呢?
- 我們知道Object中有一個拷貝方法,那么既然有了拷貝方法那么我們該如何調用呢?那不簡單嗎,因為Object是所有類的父類,所以new個對象直接調用clone方法就可以了啊。
class Tect{public static void main(String[] args) {Student student1 = new Student(12,"張三");Student student2 = student1.clone;}}
按道理來說,這個代碼應該就能執行了。但是實際是沒有clone方法。這是為什么呢?原因很簡單,因為Object類中的 clone 方法是 protected 修飾的,父類和子類不在一個包,雖然是子類,但不是在自己本身的類(Student)中使用,而是在其他的類(Tect)中使用 ,所以 protected Object clone() throws CloneNotSupportedException {
return super.clone();
}用不了。那怎么辦呢?
- 既然在Student中能使用,那么就重寫clone方法 ,在方法中返回父類Object中的clone方法 不就行了嗎
class Student{protected Object clone() throws CloneNotSupportedException {return super.clone();}}
此時我們還發現clone源碼中,有throw…,這其實是異常,所以我們需要在main方法的后面說明這個方法是合法的
class Tect{public static void main(String[] args) throws CloneNotSupportedException{Student student1 = new Student(12,"張三");Student student2 = student1.clone;}}
此時還沒完,我們看源碼,發現調用完父類的clone后,返回值是Object,所以需要強轉
- 當我們以為拷貝成功了,但實際是運行時拋了CloneNotSupportedException這個異常,那有異常就需要處理,那怎么處理呢?
在需要拷貝的類 實現 Clonable接口,這個接口里面什么東西都沒有,盲猜,他是用來標記這個類能被拷貝的。
public class Student implements Cloneable{}
- 到這里就拷貝完成了
2.11.2、什么是淺拷貝?
- 比如說,一個對象中有成員變量,成員方法,還有一個引用指向另一個對象。淺拷貝就是把成員變量,成員方法,引用,都拷貝到另一個對象,但是對象中的引用所指的那個對象沒有拷貝
- 這樣就是淺拷貝,這樣的話,我們看圖,發現通過people1 ,people2 都能改變 對象的money所指的對象 ,那么就會非常不安全了
2.11.1、怎么深拷貝?
- 深拷貝就是把對象里的引用所指的對象一起拷貝了
- 那么這種深拷貝怎么通過代碼實現呢?簡單,就是在拷貝了的前提,再對money進行拷貝
class Student implements Cloneable{
protected Object clone() throws CloneNotSupportedException {Student tem = (Student) super.clone();tem.money = (Money) this.money.clone();return tem;}}
在Student 中的重寫clone中,在對象的拷貝的前提下,再拷貝一次對象里面的引用所指的對象
深拷貝和淺拷貝本質區別是代碼怎么寫,而不是取決于某個工具
三、內部類
- 日常使用最多的是匿名內部類。
- 內部類也是封裝的一種體現
- 什么是內部類,顧名思義是在內部里的類,那在誰的內部呢?在類的內部。
- 那什么情況下需要定義內部類呢?
- 我們知道類是對一個真實的事物進行描述。在一個類中,還有一部分可以抽取出來當做一個類的,此時就可以定義內部類了
class OutClass {class InClass {}
}
3.1、什么是內部類?
- 內部類是類中類,或者方法中類
- 我們知道類中的成員有,成員變量,成員方法。如今增加了成員類,我們知道成員有靜態成員,和非靜態成員,那么內部類是否也是有靜態內部類 和非靜態內部類的呢?內部類的位置那么特殊,是成員,又是類,那么他是否具有這兩種的特點呢?我們拭目以待吧。
3.2、內部類的分類
- 靜態內部類
- 實例內部類
- 局部內部類
- 匿名內部類
3.3、靜態內部類
- 靜態內部類和靜態成員一樣,都是使用static修飾的
class OutClass{static class InClass{}
}
- 作為成員,是被static修飾的,被static修飾后,最大的特點就是不依賴對象,變成類類了。所以可以通過外部類直接點就能使用靜態內部類。
- 作為類,那么他是可以實例化對象的,那么如何實例化對象呢?
OutClass.InClass inClass = new OutClass.InClass();
此時引用能訪問靜態內部類里面的非靜態成員,因為內部類里面靜態成員是通過類名直接點訪問的,而外部類的非靜態成員變量,需要外部類實例化對象,并通過外部類的對象加點才能訪問。外部的靜態成員,是外部類類名直接點就訪問。所以綜上,通過內部類對象不能訪問外部類的成員
- 在靜態內部類當中不能直接訪問外部類當中的非靜態的成員變量(硬要訪問只能通過外部類對象來訪問),換句話說在靜態內部類中只能訪問外部類中的靜態成員
3.4、實例內部類
- 什么是實例內部類,我們知道類中的非靜態成員,也叫做實例成員。
- 所以說內部類是需要依賴對象的,依賴誰的對象呢?依賴外部類的對象
- 作為成員,是外部類的對象加點才能使用
- 作為類,那么他是怎么實例化對象的呢?
//方法一OutClass2.InClass inClass2 = new OutClass2().new InClass();//方法二OutClass2 outClass2 = new OutClass2();OutClass2.InClass inClass3 = outClass2.new InClass();
實例化出來的對象,也是只能調用內部類直接的非靜態成員。因為內部類里面靜態成員是通過類名直接點訪問的,而外部類的非靜態成員變量,需要外部類實例化對象,并通過外部類的對象加點才能訪問。外部的靜態成員,是外部類類名直接點就訪問。所以綜上,通過內部類對象不能訪問外部類的成員
- 外部類中的任何成員都可以在實例內部類中直接訪問
- 我們知道類是只能被 public 或者默認修飾的,但現在作為成員,因此也受protected private等訪問限定符修飾
- 在實例內部類中的非靜態方法是默認實現了外部類實例化對象。也就是說,如果實例內部類中的成員變量 和外部類的成員變量重名了,我們知道,如果重名了肯定優先使用自己的(實例內部類的),但是我偏要訪問外部類重名的變量。此時就可以直接外部類名 + this + 點 + 變量名,就能調用外部類的成員變量
- 在外部類中,不能直接訪問實例內部類的成員,如果非要訪問,必須先創建實例內部類的對象。
3.5、匿名內部類
- 顧名思義,匿名所以是沒有名的內部類,在聲名的同時完成實例化,通常在只使用一次的情況下定義。
- 怎么定義?
// SuperType 可以是接口,抽象類和普通類
new SuperType(){}
- 可以定義一個Super Type的引用去接收他
SuperType superType = new SuperType(){}
- 匿名類中可以定義和正常類一樣的成員變量。
3.6、局部內部類
- 是定義在外部類的方法體中,這種類只能在定義的范圍中使用( { } )。
public class OutClass {public void tect(){class InClass{}}
}
- 局部內部類只能在所定義的方法體內部使用
- 不能被public static 等修飾
四、Object類
一、toStiring 方法
- 是用來打印的
二、equals 方法
- 是用來比較對象的,根據自己的需求重寫。