JavaSE_day09【抽象类、多态、根父类】
JavaSE_day09【抽象類、多態、根父類】
今日內容
- 抽象類
- 多態
- 向上轉型
- 向下轉型
- native
- 根父類
學習目標
- 能夠聲明抽象類
- 能夠說出抽象類的特點
- 能夠繼承抽象類
- 能夠應用多態解決問題
- 理解向上轉型與向下轉型
- 能夠使用instanceof關鍵字判斷對象類型
- 了解native關鍵字
- 了解Object類的常用方法
- 會重寫Object的常用方法
第六章 面向對象基礎–中
6.8 抽象類
6.8.1 由來
抽象:即不具體、或無法具體
例如:當我們聲明一個幾何圖形類:圓、矩形、三角形類等,發現這些類都有共同特征:求面積、求周長、獲取圖形詳細信息。那么這些共同特征應該抽取到一個公共父類中。但是這些方法在父類中又無法給出具體的實現,而是應該交給子類各自具體實現。那么父類在聲明這些方法時,就只有方法簽名,沒有方法體,我們把沒有方法體的方法稱為抽象方法。Java語法規定,包含抽象方法的類必須是抽象類。
6.8.2 語法格式
- 抽象方法 : 沒有方法體的方法。
- 抽象類:被abstract所修飾的類。
抽象類的語法格式
【權限修飾符】 abstract class 類名{} 【權限修飾符】 abstract class 類名 extends 父類{}抽象方法的語法格式
【其他修飾符】 abstract 返回值類型 方法名(【形參列表】);注意:抽象方法沒有方法體
代碼舉例:
public abstract class Animal {public abstract void run(); } public class Cat extends Animal {public void run (){System.out.println("小貓在墻頭走~~~"); } } public class CatTest {public static void main(String[] args) {// 創建子類對象Cat c = new Cat(); // 調用run方法c.run();} } 輸出結果: 小貓在墻頭走~~~此時的方法重寫,是子類對父類抽象方法的完成實現,我們將這種方法重寫的操作,也叫做實現方法。
6.8.3 注意事項
關于抽象類的使用,以下為語法上要注意的細節,雖然條目較多,但若理解了抽象的本質,無需死記硬背。
抽象類不能創建對象,如果創建,編譯無法通過而報錯。只能創建其非抽象子類的對象。
理解:假設創建了抽象類的對象,調用抽象的方法,而抽象方法沒有具體的方法體,沒有意義。
抽象類中,也有構造方法,是供子類創建對象時,初始化父類成員變量使用的。
理解:子類的構造方法中,有默認的super()或手動的super(實參列表),需要訪問父類構造方法。
抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
理解:未包含抽象方法的抽象類,目的就是不想讓調用者創建該類對象,通常用于某些特殊的類結構設計。
抽象類的子類,必須重寫抽象父類中所有的抽象方法,否則,編譯無法通過而報錯。除非該子類也是抽象類。
理解:假設不重寫所有抽象方法,則類中可能包含抽象方法。那么創建對象后,調用抽象的方法,沒有意義。
6.8.4 練習
1、練習1
定義一個幾何圖形父類Graphic。所有幾何圖形都應該具備一個計算面積的方法。但是不同的幾何圖形計算面積的方式完全不同。
abstract class Graphic{public abstract double getArea(); } class Circle extends Graphic{private double radius;public Circle(double radius) {super();this.radius = radius;}public Circle() {super();}public double getRadius() {return radius;}public void setRadius(double radius) {this.radius = radius;}@Overridepublic double getArea() {return Math.PI * radius * radius;}} class Rectangle extends Graphic{private double length;private double width;public Rectangle(double length, double width) {super();this.length = length;this.width = width;}public Rectangle() {super();}public double getLength() {return length;}public void setLength(double length) {this.length = length;}public double getWidth() {return width;}public void setWidth(double width) {this.width = width;}@Overridepublic double getArea() {return length * width;} }2、練習2
1、聲明抽象父類:Person,包含抽象方法:
public abstract void walk();
public abstract void eat();
2、聲明子類Man,繼承Person
重寫walk():大步流星走路
重寫eat():狼吞虎咽吃飯
新增方法:public void smoke()實現為吞云吐霧
3、聲明子類Woman,繼承Person
重寫walk():婀娜多姿走路
重寫eat():細嚼慢咽吃飯
新增方法:public void buy()實現為買買買…
4、在測試類中創建子類對象,調用方法測試
public abstract class Person {public abstract void walk();public abstract void eat(); } public class Man extends Person {@Overridepublic void walk() {System.out.println("大步流星走路");}@Overridepublic void eat() {System.out.println("狼吞虎咽吃飯");}public void smoke(){System.out.println("吞云吐霧");} } public class Woman extends Person {@Overridepublic void walk() {System.out.println("婀娜多姿走路");}@Overridepublic void eat() {System.out.println("細嚼慢咽吃飯");}public void buy(){System.out.println("買買買...");} } public class TestExer1 {public static void main(String[] args) {Man m = new Man();m.eat();m.walk();m.smoke();System.out.println("-------------------------");Woman w = new Woman();w.eat();w.walk();w.buy();}}6.9 多態
6.9.1 引入
多態是繼封裝、繼承之后,面向對象的第三大特性。
生活中,比如求面積的功能,圓、矩形、三角形實現起來是不一樣的。跑的動作,小貓、小狗和大象,跑起來是不一樣的。再比如飛的動作,昆蟲、鳥類和飛機,飛起來也是不一樣的。可見,同一行為,通過不同的事物,可以體現出來的不同的形態。那么此時就會出現各種子類的類型。
但是Java是強類型靜態語言,既每一個變量在使用之前必須聲明它確切的類型,然后之后的賦值和運算時都是嚴格按照這個數據類型來處理的。例如:
int num = 10; String str = "hello"; Student stu = new Student();但是,有的時候,我們在設計一個數組、或一個方法的形參、返回值類型時,無法確定它具體的類型,只能確定它是某個系列的類型。
例如:想要設計一個數組用來存儲各種圖形的對象,并且按照各種圖形的面積進行排序,但是具體存儲的對象可能有圓、矩形、三角形等,那么各種圖形的求面積方式又是不同的。
例如:想要設計一個方法,它的功能是比較兩個圖形的面積大小,返回面積較大的那個圖形對象。那么此時形參和返回值類型是圖形類型,但是不知道它具體是哪一種圖形類型。
Circle[] arr = new Circle[長度]; //只能裝圓形對象 Rectangle[] arr = new Rectangle[長度]; //只能裝矩形對象 //無法統一管理各種圖形對象,例如:給各種圖形對象按照面積排序//需要重載很多個方法,增加一種具體的圖形,就需要增加一個方法 public static Circle maxArea(Circle c1, Circle c2){//只能比較兩個圓對象} public static Rectangle maxArea(Rectangle r1, Rectangle r2){//只能比較兩個矩形對象}這個時候,Java就引入了多態。
6.9.2 定義
1、格式
父類類型 變量名 = 子類對象;父類類型:指子類對象繼承的父類類型,或者實現的父接口類型。
例如:
class Person{private String name;private int age;Person(String name, int age){this.name = name;this.age = age;}public void speak(){System.out.println(name + "說:我今年" + age);} } class Man extends Person{Man(String name, int age){super(name,age);} } class Woman extends Person{Woman(String name, int age){super(name,age);} } class Test{public static void main(String[] args){Person[] arr = new Person[2];arr[0] = new Man("張三",23);arr[1] = new Woman("如花",18);for(int i=0; i<arr.length; i++){arr[i].speak();}System.out.println("------------------------");show(new Man("張三",23));show(new Woman("如花",18));}public static void show(Person p){p.speak();} }2、編譯時類型與運行時類型不一致問題
-
編譯時,看“父類”,只能調用父類聲明的方法,不能調用子類擴展的方法;
-
運行時,看“子類”,一定是執行子類重寫的方法體;
代碼如下:
定義父類:
public abstract class Animal { public abstract void eat(); }定義子類:
class Cat extends Animal { public void eat() { System.out.println("吃魚"); } public void catchMouse(){System.out.println("抓老鼠"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨頭"); } }定義測試類:
public class Test {public static void main(String[] args) {// 多態形式,創建對象Animal a1 = new Cat(); // 調用的是 Cat 的 eata1.eat(); //a1.catchMouse();//錯誤,catchMouse()是子類擴展的方法,父類中沒有/*多態引用,編譯時,看“父類”,只能調用父類聲明的方法;運行時,看“子類”,一定是執行子類重寫的方法體;*/// 多態形式,創建對象Animal a2 = new Dog(); // 調用的是 Dog 的 eata2.eat(); } }6.9.5 多態的應用
1、多態參數
實際開發的過程中,父類類型作為方法形式參數,傳遞子類對象給方法,進行方法的調用,更能體現出多態的擴展性與便利。代碼如下:
定義父類:
public abstract class Animal { public abstract void eat(); }定義子類:
class Cat extends Animal { public void eat() { System.out.println("吃魚"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨頭"); } }定義測試類:
public class Test {public static void main(String[] args) {// 多態形式,創建對象Cat c = new Cat(); Dog d = new Dog(); // 調用showCatEat showCatEat(c);// 調用showDogEat showDogEat(d); /*以上兩個方法, 均可以被showAnimalEat(Animal a)方法所替代而執行效果一致*/showAnimalEat(c);showAnimalEat(d); }public static void showCatEat (Cat c){c.eat(); }public static void showDogEat (Dog d){d.eat();}public static void showAnimalEat (Animal a){a.eat();} }由于多態特性的支持,showAnimalEat方法的Animal類型,是Cat和Dog的父類類型,父類類型接收子類對象,當然可以把Cat對象和Dog對象,傳遞給方法。
當eat方法執行時,多態規定,執行的是子類重寫的方法,那么效果自然與showCatEat、showDogEat方法一致,所以showAnimalEat完全可以替代以上兩方法。
不僅僅是替代,在擴展性方面,無論之后再多的子類出現,我們都不需要編寫showXxxEat方法了,直接使用showAnimalEat都可以完成。
所以,多態的好處,體現在,可以使程序編寫的更簡單,并有良好的擴展。
2、多態數組
例如:家里養了兩只貓,兩條狗,想要統一管理他們的對象,可以使用多態數組
public class TestAnimal {public static void main(String[] args) {Animal[] all = new Animal[4];//可以存儲各種Animal子類的對象all[0] = new Cat();all[1] = new Cat();all[2] = new Dog();all[3] = new Dog();for (int i = 0; i < all.length; i++) {all[i].eat();//all[i]編譯時是Animal類型,運行時看存儲的是什么對象}} }6.9.6 多態練習
練習1:
(1)聲明抽象父類Traffic,包含抽象方法public abstract void drive()
(2)聲明子類Car,Bicycle等,并重寫drive方法
(3)在測試類的main中創建一個數組,有各種交通工具,遍歷調用drive()方法
模擬馬路上跑的各種交通工具
練習2:
(1)聲明一個抽象父類Person類,publ
3ic abstract void toilet();
(2)聲明一個子類Woman類,重寫方法
(3)聲明一個子類Man類,重寫方法
(4)在測試類中聲明一個方法,
public static void goToToilet(Person p){
p.toilet();
}
在main中,創建不同子類對象,調用goToToilet方法進行測試
練習3:
1、聲明一個父類Employee員工類型,有屬性,姓名(String)
有方法,public abstract double earning() 用于返回實發工資
public String getInfo():顯示姓名和實發工資
2、聲明一個子類SalaryEmployee正式工,繼承父類Employee,增加屬性,薪資,工作日天數,請假天數
重寫方法,public double earning()返回實發工資,實發工資 = 薪資 - 薪資/工作日天數 * 請假天數,
3、聲明一個子類HourEmployee小時工,繼承父類Employee
有屬性,工作小時數,每小時多少錢
重寫方法,public double earning()返回實發工資, 實發工資 = 每小時多少錢 * 小時數
4、聲明一個子類Manager經理,繼承SalaryEmployee,增加屬性:獎金比例
重寫方法,public double earning()返回實發工資,實發工資 = (薪資 - 薪資/工作日天數 * 請假天數)*(1+獎金比例)
5、你現在是財務,需要查看每個人的實發工資,并查看工資總額。
聲明一個員工數組,存儲各種員工,并遍歷顯示他們的姓名和實發工資,并計算所有員工的工資總額
6.9.7 父子類之間的類型轉換
1、向上轉型
- 當父類引用指向一個子類對象時,便是向上轉型。這個過程是自動完成的
使用格式:
父類類型 變量名 = new 子類類型(); 如:Animal a = new Cat();注意:此時通過父類的變量就只能調用父類中有的方法,不能調用子類特有的方法了
2、向下轉型
- 向下轉型:父類類型向子類類型向下轉換的過程,這個過程是強制的。
使用格式:
子類類型 變量名 = (子類類型) 父類變量名; 如:Cat c =(Cat) a;為什么要向下轉型?
當使用多態方式調用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤。也就是說,不能調用子類擁有,而父類沒有的方法。編譯都錯誤,更別說運行了。這也是多態給我們帶來的一點"小麻煩"。所以,想要調用子類特有的方法,必須做向下轉型。
轉型演示,代碼如下:
定義類:
abstract class Animal { abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println("吃魚"); } public void catchMouse() { System.out.println("抓老鼠"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨頭"); } public void watchHouse() { System.out.println("看家"); } }定義測試類:
public class Test {public static void main(String[] args) {// 向上轉型 Animal a = new Cat(); a.eat(); // 調用的是 Cat 的 eat// 向下轉型 Cat c = (Cat)a; c.catchMouse(); // 調用的是 Cat 的 catchMouse} }3、轉型的異常:ClassCastException
轉型的過程中,一不小心就會遇到這樣的問題,請看如下代碼:
public class Test {public static void main(String[] args) {// 向上轉型 Animal a = new Cat(); a.eat(); // 調用的是 Cat 的 eat// 向下轉型 Dog d = (Dog)a; d.watchHouse(); // 調用的是 Dog 的 watchHouse 【運行報錯】} }這段代碼可以通過編譯,但是運行時,卻報出了 ClassCastException ,類型轉換異常!這是因為,明明創建了Cat類型對象,運行時,當然不能轉換成Dog對象的。這兩個類型并沒有任何繼承關系,不符合類型轉換的定義。
注意:不管是向上還是向下轉型都只是針對編譯時類型來說的,不是真的類型轉換,即運行時類型不變,一開始new的是什么類型還是什么類型。
4、instanceof運算符
為了避免ClassCastException的發生,Java提供了 instanceof 關鍵字,給引用變量做類型的校驗,只要用instanceof判斷返回true的,那么強轉為該類型就一定是安全的,不會報ClassCastException異常。
格式如下:
變量名/對象 instanceof 數據類型 如果變量/對象屬于該數據類型,返回true。 如果變量/對象不屬于該數據類型,返回false。所以,轉換前,我們最好先做一個判斷,代碼如下:
public class Test {public static void main(String[] args) {// 向上轉型 Animal a = new Cat(); a.eat(); // 調用的是 Cat 的 eat// 向下轉型 if (a instanceof Cat){Cat c = (Cat)a; c.catchMouse(); // 調用的是 Cat 的 catchMouse} else if (a instanceof Dog){Dog d = (Dog)a; d.watchHouse(); // 調用的是 Dog 的 watchHouse}} }哪些情況下instanceof判斷返回true
示例代碼:
class Person{//方法代碼省略... } class Woman extends Person{//方法代碼省略... } class ChineseWoman extends Woman{//方法代碼省略... } public class Test{public static void main(String[] args){Person p1 = new Person();Person p2 = new Woman();Person p3 = new ChineseWoman();if(p1 instanceof Woman){//false}if(p2 instanceof Woman){//true//p2轉為Woman類型安全}if(p3 instanceof Woman){//true//p3轉為Woman類型安全}}}5、練習
1、聲明一個父類Employee員工類型,
有屬性,姓名(String),出生日期(MyDate類型,也是自定義的含年,月,日屬性日期類型)
有方法,public abstract double earning()
public String getInfo():顯示姓名和實發工資
2、聲明一個子類SalaryEmployee正式工,繼承父類Employee
增加屬性,薪資,工作日天數,請假天數
重寫方法,public double earning()返回實發工資, 實發工資 = 薪資 - 薪資/工作日天數 * 請假天數,
重寫方法,public String getInfo():顯示姓名和實發工資,月薪,工作日天數,請假天數
3、聲明一個子類HourEmployee小時工,繼承父類Employee
有屬性,工作小時數,每小時多少錢
重寫方法,public double earning()返回實發工資, 實發工資 = 每小時多少錢 * 小時數
重寫方法,public String getInfo():顯示姓名和實發工資,時薪,工作小時數
增加方法,public void leave():打印查看使用工具是否損壞,需要賠償
4、聲明一個子類Manager經理,繼承SalaryEmployee
增加屬性:獎金,獎金比例
重寫方法,public double earning()返回實發工資, 實發工資 = (薪資 - 薪資/工作日天數 * 請假天數)*(1+獎金比例)
重寫方法,public String getInfo():顯示姓名和實發工資,月薪,工作日天數,請假天數,獎金比例
5、聲明一個員工數組,存儲各種員工,
你現在是人事,從鍵盤輸入當前的月份,需要查看每個人的詳細信息。
如果他是正式工(包括SalaryEmployee和Manager),并且是本月生日的,祝福生日快樂,通知領取生日禮物。如果是HourEmployee顯示小時工,就進行完工檢查,即調用leave方法
6.9.8 多態引用時關于成員變量與成員方法引用的原則
1、成員變量:只看編譯時類型
如果直接訪問成員變量,那么只看編譯時類型
public class TestField {public static void main(String[] args) {Father f = new Son();System.out.println(f.x);//只看編譯時類型} } class Father{int x = 1; } class Son extends Father{int x = 2; }2、非虛方法:只看編譯時類型
如果方法在編譯期就確定了具體的調用版本,這個版本在運行時是不可變的,這樣的方法稱為非虛方法。其他方法稱為虛方法。
- 靜態方法、私有方法、final方法、實例構造器、通過super調用的父類方法都是非虛方法。
- 靜態方法與類型直接關聯
- 私有方法在外部不可訪問
- final不可被繼承
小貼士:
靜態方法不能被重寫
調用靜態方法最好使用“類名.”
3、虛方法:靜態分派與動態綁定
(1)示例一:重寫
abstract class Animal { public abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println("吃魚"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨頭"); } }public class Test{public static void main(String[] args){Animal a = new Cat();a.eat();} }如上代碼在編譯期間先進行靜態分派:即確定是調用Animal類中的public void eat()方法,如果Animal類或它的父類中沒有這個方法,將會報錯。
而在運行期間動態的在進行動態分派:即確定執行的是Cat類中的public void eat()方法,因為子類重寫了eat()方法,如果沒有重寫,那么還是執行Animal類在的eat()方法
(2)示例二:重載
class MyClass{public void method(Father f) {System.out.println("father");}public void method(Son s) {System.out.println("son");}public void method(Daughter f) {System.out.println("daughter");} } class Father{} class Son extends Father{} class Daughter extends Father{} public class TestOverload {public static void main(String[] args) {MyClass my = new MyClass();Father f = new Father();Father s = new Son();Father d = new Daughter();my.method(f);//fathermy.method(s);//fathermy.method(d);//father} }如上代碼在編譯期間先進行靜態分派:即確定是調用MyClass類中的method(Father f)方法,如果Animal類或它的父類中沒有這個方法,將會報錯。
而在運行期間動態的在進行動態分派:即確定執行的是MyClass類中的method(Father f)方法,因為my對象的運行時類型還是MyClass類型。
有些同學會疑問,不是應該分別執行method(Father f)、method(Son s)、method(Daughter d)嗎?
因為此時f,s,d編譯時類型都是Father類型,因此method(Father f)是最合適的。
(3)示例三:重載
class MyClass{public void method(Father f) {System.out.println("father");}public void method(Son s) {System.out.println("son");} } class Father{} class Son extends Father{} class Daughter extends Father{} public class TestOverload {public static void main(String[] args) {MyClass my = new MyClass();Father f = new Father();Son s = new Son();Daughter d = new Daughter();my.method(f);//fathermy.method(s);//sonmy.method(d);//father} }如上代碼在編譯期間先進行靜態分派:即確定是分別調用MyClass類中的method(Father f),method(Son s),method(Father f)方法。
而在運行期間動態的在進行動態分派:即確定執行的是MyClass類中的method(Father f),method(Son s),method(Father f)方法,因為my對象的運行時類型還是MyClass類型。
有些同學會疑問,這次為什么分別執行method(Father f)、method(Son s)?
因為此時f,s,d編譯時類型分別是Father、Son、Daughter,而Daughter只能與Father參數類型匹配
(4)示例四:重載與重寫
class MyClass{public void method(Father f) {System.out.println("father");}public void method(Son s) {System.out.println("son");} } class MySub extends MyClass{public void method(Daughter d) {System.out.println("daughter");} } class Father{} class Son extends Father{} class Daughter extends Father{} public class TestOverload {public static void main(String[] args) {MyClass my = new MySub();Father f = new Father();Son s = new Son();Daughter d = new Daughter();my.method(f);//fathermy.method(s);//sonmy.method(d);//father} }如上代碼在編譯期間先進行靜態分派:即確定是分別調用MyClass類中的method(Father f),method(Son s),method(Father f)方法。
而在運行期間動態的在進行動態分派:即確定執行的是MyClass類中的method(Father f),method(Son s),method(Father f)方法。
有些同學會疑問,my對象不是MySub類型嗎,而MySub類型中有method(Daughter d)方法,那么my.method(d)語句應該執行MySub類型中的method(Daughter d)方法?
-
my變量在編譯時類型是MyClass類型,那么在MyClass類中,只有method(Father f),method(Son s)方法,
-
f,s,d變量編譯時類型分別是Father、Son、Daughter,而Daughter只能與Father參數類型匹配
-
而在MySub類中并沒有重寫method(Father f)方法,所以仍然執行MyClass類中的method(Father f)方法
(5)結論
方法的分派有兩個需要考慮的因素:
-
方法的參數:在編譯期間確定,根據編譯器時類型,找最匹配的
-
方法的所有者:
- 如果沒有重寫,就按照編譯時類型處理
- 如果有重寫,就按照運行時類型處理
6.10 native關鍵字
native:本地的,原生的
用法:
? 只能修飾方法
? 表示這個方法的方法體代碼不是用Java語言實現的,而是由C/C++語言編寫的。
? 但是對于Java程序員來說,可以當做Java的方法一樣去正常調用它,或者子類重寫它。
JVM內存的管理:
| 程序計數器 | 程序計數器是CPU中的寄存器,它包含每一個線程下一條要執行的指令的地址 |
| 本地方法棧 | 當程序中調用了native的本地方法時,本地方法執行期間的內存區域 |
| 方法區 | 存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。 |
| 堆內存 | 存儲對象(包括數組對象),new來創建的,都存儲在堆內存。 |
| 虛擬機棧 | 用于存儲正在執行的每個Java方法的局部變量表等。局部變量表存放了編譯期可知長度的各種基本數據類型、對象引用,方法執行完,自動釋放。 |
修飾符一起使用問題?
| public | √ | √ | × | √ | √ | × |
| protected | × | √ | × | √ | √ | × |
| private | × | √ | × | √ | √ | × |
| static | × | √ | √ | × | √ | × |
| final | √ | √ | × | × | √ | √ |
| abstract | √ | × | × | × | √ | × |
| native | × | × | × | × | √ | × |
不能和abstract一起使用的修飾符?
(1)abstract和final不能一起修飾方法和類
(2)abstract和static不能一起修飾方法
(3)abstract和native不能一起修飾方法
(4)abstract和private不能一起修飾方法
static和final一起使用:
(1)修飾方法:可以,因為都不能被重寫
(2)修飾成員變量:可以,表示靜態常量
(3)修飾局部變量:不可以,static不能修飾局部變量
(4)修飾代碼塊:不可以,final不能修改代碼塊
(5)修飾內部類:可以一起修飾成員內部類,不能一起修飾局部內部類(后面講)
6.11 Object根父類
6.11.1 如何理解根父類
類 java.lang.Object是類層次結構的根類,即所有類的父類。每個類都使用 Object 作為超類。
- Object類型的變量與除Object以外的任意引用數據類型的對象都多態引用
- 所有對象(包括數組)都實現這個類的方法。
- 如果一個類沒有特別指定父類,那么默認則繼承自Object類。例如:
6.11.2 Object類的API
? API(Application Programming Interface),應用程序編程接口。Java API是一本程序員的字典 ,是JDK中提供給我們使用的類的說明文檔。所以我們可以通過查詢API的方式,來學習Java提供的類,并得知如何使用它們。在API文檔中是無法得知這些類具體是如何實現的,如果要查看具體實現代碼,那么我們需要查看src源碼。
? 根據JDK源代碼及Object類的API文檔,Object類當中包含的方法有11個。今天我們主要學習其中的5個:
(1)toString()
public String toString()
①默認情況下,toString()返回的是“對象的運行時類型 @ 對象的hashCode值的十六進制形式"
②通常是建議重寫,如果在eclipse中,可以用Alt +Shift + S–>Generate toString()
③如果我們直接System.out.println(對象),默認會自動調用這個對象的toString()
因為Java的引用數據類型的變量中存儲的實際上時對象的內存地址,但是Java對程序員隱藏內存地址信息,所以不能直接將內存地址顯示出來,所以當你打印對象時,JVM幫你調用了對象的toString()。
例如自定義的Person類:
public class Person { private String name;private int age;@Overridepublic String toString() {return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';}// 省略構造器與Getter Setter }(2)getClass()
public final Class<?> getClass():獲取對象的運行時類型
因為Java有多態現象,所以一個引用數據類型的變量的編譯時類型與運行時類型可能不一致,因此如果需要查看這個變量實際指向的對象的類型,需要用getClass()方法
public static void main(String[] args) {Object obj = new String();System.out.println(obj.getClass());//運行時類型}(3)finalize()
protected void finalize():用于最終清理內存的方法
public class TestFinalize {public static void main(String[] args) {for (int i = 0; i < 10; i++) {MyData my = new MyData();}System.gc();//通知垃圾回收器來回收垃圾try {Thread.sleep(2000);//等待2秒再結束main,為了看效果} catch (InterruptedException e) {e.printStackTrace();}} } class MyData{@Overrideprotected void finalize() throws Throwable {System.out.println("輕輕的我走了...");}}面試題:對finalize()的理解?
-
當對象被GC確定為要被回收的垃圾,在回收之前由GC幫你調用這個方法,不是由程序員手動調用。
-
這個方法與C語言的析構函數不同,C語言的析構函數被調用,那么對象一定被銷毀,內存被回收,而finalize方法的調用不一定會銷毀當前對象,因為可能在finalize()中出現了讓當前對象“復活”的代碼
-
每一個對象的finalize方法只會被調用一次。
-
子類可以選擇重寫,一般用于徹底釋放一些資源對象,而且這些資源對象往往時通過C/C++等代碼申請的資源內存
(4)hashCode()
public int hashCode():返回每個對象的hash值。
hashCode 的常規協定:
- ①如果兩個對象的hash值是不同的,那么這兩個對象一定不相等;
- ②如果兩個對象的hash值是相同的,那么這兩個對象不一定相等。
主要用于后面當對象存儲到哈希表等容器中時,為了提高存儲和查詢性能用的。
public static void main(String[] args) {System.out.println("Aa".hashCode());//2112System.out.println("BB".hashCode());//2112}(5)equals()
public boolean equals(Object obj):用于判斷當前對象this與指定對象obj是否“相等”
①默認情況下,equals方法的實現等價于與“==”,比較的是對象的地址值
②我們可以選擇重寫,重寫有些要求:
A:如果重寫equals,那么一定要一起重寫hashCode()方法,因為規定:
? a:如果兩個對象調用equals返回true,那么要求這兩個對象的hashCode值一定是相等的;
? b:如果兩個對象的hashCode值不同的,那么要求這個兩個對象調用equals方法一定是false;
? c:如果兩個對象的hashCode值相同的,那么這個兩個對象調用equals可能是true,也可能是false
B:如果重寫equals,那么一定要遵循如下幾個原則:
? a:自反性:x.equals(x)返回true
? b:傳遞性:x.equals(y)為true, y.equals(z)為true,然后x.equals(z)也應該為true
? c:一致性:只要參與equals比較的屬性值沒有修改,那么無論何時調用結果應該一致
? d:對稱性:x.equals(y)與y.equals(x)結果應該一樣
? e:非空對象與null的equals一定是false
class User{private String host;private String username;private String password;public User(String host, String username, String password) {super();this.host = host;this.username = username;this.password = password;}public User() {super();}public String getHost() {return host;}public void setHost(String host) {this.host = host;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "User [host=" + host + ", username=" + username + ", password=" + password + "]";}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + ((host == null) ? 0 : host.hashCode());result = prime * result + ((password == null) ? 0 : password.hashCode());result = prime * result + ((username == null) ? 0 : username.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;User other = (User) obj;if (host == null) {if (other.host != null)return false;} else if (!host.equals(other.host))return false;if (password == null) {if (other.password != null)return false;} else if (!password.equals(other.password))return false;if (username == null) {if (other.username != null)return false;} else if (!username.equals(other.username))return false;return true;}}ate String host;
private String username;
private String password;
public User(String host, String username, String password) {
super();
this.host = host;
this.username = username;
this.password = password;
}
public User() {
super();
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return “User [host=” + host + “, username=” + username + “, password=” + password + “]”;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((host == null) ? 0 : host.hashCode());
result = prime * result + ((password == null) ? 0 : password.hashCode());
result = prime * result + ((username == null) ? 0 : username.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (host == null) {
if (other.host != null)
return false;
} else if (!host.equals(other.host))
return false;
if (password == null) {
if (other.password != null)
return false;
} else if (!password.equals(other.password))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
}
總結
以上是生活随笔為你收集整理的JavaSE_day09【抽象类、多态、根父类】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 易维帮助台:客户为王的时代,拿什么提升客
- 下一篇: Centos-部署论坛系统discuz