多態(tài)的概念
通俗來說,就是多種形態(tài),具體點(diǎn)就是去完成某個行為,當(dāng)不同的對象去完成時會產(chǎn)生出不同的狀態(tài)。
多態(tài)實(shí)現(xiàn)條件
必須要滿足如下幾個條件,缺一不可:
- 必須在繼承體系下
- 子類必須要對父類中方法進(jìn)行重寫
- 通過父類的引用調(diào)用重寫的方法
// Animal.java
public class Animal {
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "吃飯");
}
}
// Cat.java
public class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name+"吃魚~~~");
}
}
// Dog.java
public class Dog extends Animal {
public Dog(String name, int age){
super(name, age);
}
@Override
public void eat(){
System.out.println(name+"吃骨頭~~~");
}
}
定義測試類TestAnimal.java
public class TestAnimal {
public static void eat(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Cat cat = new Cat("小橘",2);
Dog dog = new Dog("小黑", 1);
eat(cat);
eat(dog);
}
}
編譯器在編譯代碼時,并不知道要調(diào)用Dog 還是 Cat 中eat的方法,等程序運(yùn)行起來后,形參animal引用的具體對象確定后才知道調(diào)用哪個方法。此時animal這個引用調(diào)用eat方法可能會有多種不同的表現(xiàn)(和animal引用的實(shí)例相關(guān)),這種行為就稱為多態(tài)。
注意:此處的形參類型必須是父類類型才可以。
重寫
重寫(override):也稱為覆蓋。重寫是子類對父類非靜態(tài)、非private修飾,非final修飾,非構(gòu)造方法等的實(shí)現(xiàn)過程進(jìn)行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!重寫的好處在于子類可以根據(jù)需要,定義特定于自己的行為。 也就是說子類能夠根據(jù)需要實(shí)現(xiàn)父類的方法。
【方法重寫的規(guī)則】
- 子類在重寫父類的方法時,一般必須與父類方法原型一致: 返回值類型 方法名 (參數(shù)列表) 要完全一致。
- 被重寫的方法返回值類型可以不同,但是必須是具有父子關(guān)系的
- 訪問權(quán)限不能比父類中被重寫的方法的訪問權(quán)限更低。例如:如果父類方法被public修飾,則子類中重寫該方法就不能聲明為protected。
- 父類被static、private修飾的方法、構(gòu)造方法都不能被重寫。
- 重寫的方法, 可以使用 @Override 注解來顯式指定. 有了這個注解能幫我們進(jìn)行一些合法性校驗。例如不小心將方法名字拼寫錯了(比如寫成 aet),那么此時編譯器就會發(fā)現(xiàn)父類中沒有 aet 方法,就會編譯報錯, 提示無法構(gòu)成重寫。
【重寫和重載的區(qū)別】
區(qū)別點(diǎn) | 重寫(override) | 重載(override) |
---|---|---|
參數(shù)列表 | 一定不能修改 | 必須修改 |
返回類型 | 一定不能修改(除非構(gòu)成父子關(guān)系) | 可改可不改 |
訪問限定符 | 子類 ≥ 父類 | 可改可不改 |
方法重載是一個類的多態(tài)性表現(xiàn),而方法重寫是子類與父類的一種多態(tài)性表現(xiàn)。
向上轉(zhuǎn)型和向下轉(zhuǎn)型
向上轉(zhuǎn)型
向上轉(zhuǎn)型:實(shí)際就是創(chuàng)建一個子類對象,將其當(dāng)成父類對象來使用。
父類類型 對象名 = new 子類類型();
比如:
Animal animal = new Cat("小橘",2);
animal是父類類型,但可以引用一個子類對象,因為是從小范圍向大范圍的轉(zhuǎn)換。
public class TestAnimal {
// 方法傳參:形參為父類型引用,可以接收任意子類的對象
public static void eatFood(Animal animal){
animal.eat();
}
// 作返回值:返回任意子類對象
public static Animal buyAnimal(String var){
if("狗".equals(var) ){
return new Dog("dog",1);
}else if("貓" .equals(var)){
return new Cat("cat", 1);
}else{
return null;
}
}
public static void main(String[] args) {
// 直接賦值:子類對象賦值給父類對象
Animal cat = new Cat("小橘",2);
Dog dog = new Dog("小黑", 1);
eatFood(cat);
eatFood(dog);
Animal animal = buyAnimal("狗");
animal.eat();
animal = buyAnimal("貓");
animal.eat();
}
}
向上轉(zhuǎn)型的優(yōu)點(diǎn):讓代碼實(shí)現(xiàn)更簡單靈活。
向上轉(zhuǎn)型的缺陷:不能調(diào)用到子類特有的方法。
向下轉(zhuǎn)型
將一個子類對象經(jīng)過向上轉(zhuǎn)型之后當(dāng)成父類方法使用,再無法調(diào)用子類的方法,但有時候可能需要調(diào)用子類特有的方法,此時:將父類引用再還原為子類對象即可,即向下轉(zhuǎn)型。
public class Animal {
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "吃飯");
}
}
// Cat.java
public class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
public void mew(){
System.out.println(name+"喵喵叫");
}
@Override
public void eat(){
System.out.println(name+"吃魚~~~");
}
}
// Dog.java
public class Dog extends Animal {
public Dog(String name, int age){
super(name, age);
}
public void bark(){
System.out.println(name+"汪汪叫");
}
@Override
public void eat(){
System.out.println(name+"吃骨頭~~~");
}
}
// TestAnimal.java
public class TestAnimal {
public static void main(String[] args) {
Cat cat = new Cat("小橘",2);
Dog dog = new Dog("小黑", 1);
// 向上轉(zhuǎn)型
Animal animal = cat;
animal.eat();
animal = dog;
animal.eat();
// 向下轉(zhuǎn)型
cat = (Cat)animal;
cat.mew();
// animal本來指向的就是狗,因此將animal還原為狗也是安全的
dog = (Dog)animal;
dog.bark();
}
}
避免在構(gòu)造方法中調(diào)用重寫的方法
一段有坑的代碼, 我們創(chuàng)建兩個類,B 是父類,D 是子類。D 中重寫 func 方法。并且在 B 的構(gòu)造方法中調(diào)用 func。文章來源:http://www.zghlxwxcb.cn/news/detail-678467.html
class B {
public B() {
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
B b = new D(); //執(zhí)行結(jié)果:D.func() 0
}
}
原因:構(gòu)造 D 對象的同時,會調(diào)用 B 的構(gòu)造方法。B 的構(gòu)造方法中調(diào)用了 func 方法,此時會觸發(fā)動態(tài)綁定,會調(diào)用到 D 中的 func此時 D 對象自身還沒有構(gòu)造,此時 num 處在未初始化的狀態(tài),值為 0。
結(jié)論:盡量不要在構(gòu)造器中調(diào)用方法,如果這個方法被子類重寫,就會觸發(fā)動態(tài)綁定,但是此時子類對象還沒構(gòu)造完成,可能會出現(xiàn)一些隱藏的但是又極難發(fā)現(xiàn)的問題。文章來源地址http://www.zghlxwxcb.cn/news/detail-678467.html
到了這里,關(guān)于【JavaSE】面向?qū)ο笾鄳B(tài)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!