Java繼承、覆寫與多型
1.繼承的定義與使用
- 繼承的主要作用在於:在已有基礎上繼續進行功能的擴充(可重用)
- 繼承能夠消除結構定義上的重複
-
1.1繼承的實現
- 繼承使用extends關鍵字來實現
class 子類 extends 父類
- Java是單繼承 : 一個子類只有一個父類
- 子類也稱為派生類,父類也成為超類 / 基類
-
1.2 繼承的限制
子類物件例項化前一定會首先呼叫父類的構造方法,例項化父類物件後再呼叫子類構造方法,進行子類物件初始化。
- 實際上在子類的構造方法之中,相當於隱含了一個語句 super();子類和父類都有預設的構造方法時,不呼叫。
- super( )呼叫父類的構造方法,this( )呼叫當前物件的構造方法
public class Super{
public static void main(String[] args){
new Son("abc");
}
}
class Father{
//無引數的構造方法——預設的構造方法 public Father(){}
}
class Son extends Father{
public Son(String name){
//Father super();
}
}
- Java不允許多重繼承,但是允許多層繼承
- 子類會繼承父類的所有結構
- 顯示繼承:子類能夠使用所有非private操作,可直接呼叫。
- 隱式繼承:所有的private操作無法被直接呼叫,需要通過其他形式呼叫
程式碼示例:
public class Inherit{ public static void main(String[] args){ //學生繼承了Person類,而Person類由於有自定義的構造方法,因此沒有預設的構造方法 //所以必須在Student中定義構造方法 Student student = new Student("Jack","男","1","bit"); //呼叫Student.toString,而Student.toString是繼承Person.toString //因此列印的是Person.toString System.out.println(student); } } //學生在Person的基礎上增加學號、學校 //Person是Student的父類 //Studnt是Person的子類 class Student extends Person{ private String num; private String school; public Student(String name,String gender,String num,String school){ //雖然Student繼承了Person,但是由於name、gender是私有的,因此Student不能訪問 //this.name=name; //error //直接賦值 ->能夠訪問到父類的屬性 //通過構造方法 -> 父類提供構造方法(帶引數) //通過setter方法 ->能夠訪問到父類的setter方法 super(name,gender); //呼叫父類的構造方法 this.num=num; this.school=school; System.out.println("這是子類的構造方法"); } public String getNum(){ return this.num; } public String getSchool(){ return this.school; } //方法覆寫 public String toString(){ return super.toString()+" 學號:"+this.num+" 學校:"+this.school; } // public String toString(){ // return " 姓名:"+this.getName()+" 性別:"+this.getGender()+" 學號:"+this.num+" 學校:"+this.school; // } } //面向物件的一個特性——封裝,對方封裝好的部分進行擴充套件, //開閉原則:對擴充套件開放,對修改關閉 class Person{ private String name; private String gender; //構造方法 public Person(String name,String gender){ this.name=name; this.gender=gender; System.out.println("這是父類的構造方法"); } //getter方法 public String getName(){ return this.name; } public String getGender(){ return this.gender; } public String toString(){ return " 姓名:"+this.name+" 性別:"+this.gender; } }
2.覆寫
如果子類定義了與父類完全相同(不算許可權)的方法或者屬性的時候,這樣的操作稱為覆寫。
-
2.1 方法的覆寫
子類定義了與父類方法名稱、引數列表、返回值完全相同的方法。被覆寫的方法不能擁有比父類更為嚴格的訪問控制權限。
-
判斷呼叫的是父類方法或子類方法:
a.看new在哪兒(當前使用的物件是通過哪個類new的)
b.呼叫的方法是否被子類覆寫,如果被覆寫,呼叫的一定是被覆寫後的方法。
- private < default—包訪問許可權<protected < public
- 方法覆寫不能出現private關鍵字(隱式繼承影響)
練習:
解釋方法過載與方法重寫的區別
a.概念上:
方法過載:方法名相同,引數列表不同,與返回值無關。
方法重寫:子類定義了和父類方法名稱、引數列表、返回值完全相同的方法,只是許可權不同。
b.範圍上:
方法過載:在同一個類中
方法重寫:在有繼承關係的類之間
c.許可權要求:
方法過載:沒有許可權要求
方法重寫:被覆寫的方法不能擁有比父類更為嚴格的訪問控制權限
-
2.2 屬性的覆寫(瞭解)
當子類定義了和父類屬性名稱完全相同的屬性的時候,就稱為屬性的覆寫。
-
屬性覆寫不對訪問控制權限有要求,因為屬性是通過內部來進行訪問。
//方法覆寫
public class Test11{
public static void main(String [] args){
Person person=new Person();
person.print();
Student student=new Student();
//就近原則
System.out.println(student.getName());
}
}
class Person{
public String name="Jack";
//成員方法
public void print(){
System.out.println("這是Person的print方法");
}
private void hello(){
System.out.println("這是Person的hello方法");
}
}
class Student extends Person{
//屬性覆寫
private String name="Tom";
//default——包私有
//public>protected>[default]>private
public void print(){
System.out.println("這是Student的print方法");
}
//此時,子類的hello方法並不是方法覆寫
//因為父類中的hello方法是私有的,子類已經看不見父類的hello方法
//因此,這只是子類的普通方法,與父類的方法沒有任何關係
public void hello(){
System.out.println("這是Student的hello方法");
}
public String getName(){
return name;
}
}
-
2.3 super關鍵字
-
2.3.1 super用於方法
1. 用於構造方法,表示呼叫父類構造方法 super(引數列表)
a.當子類呼叫父類無參構造時,super( )可寫可不寫,表示父類無參構造;
當子類呼叫父類有參構造時,super(引數列表)必須要寫,告訴編譯器當前呼叫的是哪個有參構造
b.子類構造方法中呼叫父類構造必須是第一行語句
c. this與super不能同時呼叫
2. 用於普通方法,super.方法名(引數)
用於在子類中,明確呼叫父類被覆寫的方法
-
2.3.2 super用於屬性(瞭解)
super.屬性名 表示呼叫父類中被覆寫的屬性(許可權不是private)
程式碼示例:
class Person{
public String str="daddy";
public void fun(){
System.out.println("父類");
}
}
class Student extends Person{
public String str="child";
public void fun(){
//明確呼叫父類被覆寫的fun方法
super.fun();
System.out.println("子類");
//明確呼叫父類被覆寫的屬性
System.out.println(super.str);
}
}
public class Test1{
public static void main(String [] args){
System.out.println(new Student().str);
new Student().fun();
}
}
3.final關鍵字——終結器
-
3.1 final修飾類(String類以及8大基本資料型別的包裝類,Integer)
-
被final修飾的類不能被繼承(編譯無法通過)
-
final修飾的屬性、變數(引用)、引數,一旦初始化後就不能再賦值了
-
一旦一個類被final修飾,該類的所有方法都會預設加上final(成員變數不會加final)
-
3.2 final修飾方法
- 被final修飾的方法,不允許被覆寫
- 被private修飾的方法,相當於加了一個final關鍵字
-
3.3 final修飾屬性——常量
- 3.3.1 修飾普通資料型別的成員變數(最主要用途)
-
被final修飾的成員變數必須在宣告時初始化(構造塊或構造方法中初始化),並且在初始化後值無法修改。
- 被final修飾的全域性變數必須在宣告時初始化(靜態塊初始化),並且在初始化後值無法修改。
練習:
a和b的區別:
class Person{
public final int a=5;
public static final int B=10;
}
a final變數——常量(值不能改變,每個物件都有自己的final變數,在物件產生時初始化)(物件)
b static final變數——全域性常量(所有物件共享此變數,類載入時初始化,效率較高,通過類名呼叫),儲存與全域性資料區。字母全大寫,下劃線分隔。(類)
- 3.3.2 修飾引用資料型別的成員變數(值不能改變)
程式碼示例:
class Person{
public String name="daddy";
public int age=20;
}
public class Test1{
public static void main(String [] args){
final Person p=new Person();
//p的地址不變
//p=new Person(); //error
//內容可以改變
p.name="hello";
p.age=30;
System.out.println(p.name);
System.out.println(p.age);
}
}
- 兩個運算元都為:byte、 short、int 、char,兩個數都會被轉換成int型別,但是final修飾的型別不會發生變化。
練習:
public class Test1{
byte b1=2,b2=2,b3,b4;
final byte b5=4,b6=5,b7=8;
public void test(){
//假設下列語句沒有關聯性
//b3是byte型,b1+b2的結果是int型,需強轉
b3=b1+b2; //error
//b4是byte型,b5、b6有final修飾,相加結果仍為byte型
b4=b5+b6;
//b5被final修飾,值不可修改
b5=b1+b3; //error
//b3是byte型,b4是int型,b4+b5結果為int型,需強轉
b3=b5+b4; //error
}
}
4.多型
- 方法多型性:1.方法過載 2.方法覆寫
- 物件多型性的核心本質:方法的覆寫
- 向上轉型(自動):用於引數統一化
父類 父類引用 = new 子類( );
-
向下轉型(強制):當父類引用需要呼叫子類擴充方法時,才需要向下轉型
子類 子類引用 = (子類)父類例項;
- 要發生向下轉型,必須先發生向上轉型,否則在轉型時會出現執行時異常:ClassCastException (型別轉換異常)
public class Test1{
public static void main(String[] args){
//向上轉型
//父類 父類引用 = new 子類( );
Person per=new Student();
per.fun();
System.out.println(per.getPersonInfo());
//Student給的是子類物件,但變數型別是Person
//使用時,將一個Student變數當Person來用
//我們所看到的屬性、方法,是Person中定義的
//Person中沒有定義getStudentInfo(),因此下行程式碼錯誤
//System.out.println(per.getStudentInfo()); //error
//向下轉型
//子類 子類引用 = (子類)父類例項;
Student stu=(Student)per;
//Student stu=(Student)new Person(); //編譯通過,無法執行(CCE)
stu.fun();
System.out.println(stu.getPersonInfo());
System.out.println(stu.getStudentInfo());
}
}
class Person{
public void fun(){
System.out.println("父類");
}
public String getPersonInfo(){
return "Person info";
}
}
class Student extends Person{
public void fun(){
System.out.println("子類");
}
public String getStudentInfo(){
return "Student info";
}
}
- 引用名 instanceof 類:表示該引用是否能表示該類例項
public class Test6{
public static void main(String[] args){
Person per=new Person();
//per可以由Person類建立,返回true
System.out.println(per instanceof Person);
//如果per不能由Student建立
if(!(per instanceof Student)){
per=new Student();
System.out.println(per instanceof Student);
}
}
}
class Person{
public void fun(){
System.out.println("父類");
}
}
class Student extends Person{
public void fun(){
System.out.println("子類");
}
}
- 向上轉型作用:引數統一化
class Person{
public void fun(){
System.out.println("人類");
}
}
class Student extends Person{
public void fun(){
System.out.println("學生");
}
}
class Worker extends Person{
public void fun(){
System.out.println("工人");
}
}
public class Test1{
public static void main(String[] args){
Test1(new Person());
//向上轉型
Test1(new Student());
//向上轉型
Test1(new Worker());
}
public static void Test1(Person per){
per.fun();
}
}