1. 程式人生 > >Java繼承、覆寫與多型

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();
	}
}