Java繼承和多態(tài)
繼承
定義
繼承就是利用現(xiàn)有類創(chuàng)建新類的過程,現(xiàn)有的類稱為 父類(基類),新類稱為 子類(派生類)。比如現(xiàn)實中:兒子繼承了父親遺產(chǎn)一樣。面向?qū)ο蟪绦蛟O(shè)計中的繼承,就是代碼重用。
Java中所有的類都繼承自 Object 這個父類。
實現(xiàn)
在Java中實現(xiàn)繼承需要使用到extends關(guān)鍵字;
[訪問修飾符] class 派生類名 extends 基類名 {
成員列表
}
如:
class Student extends Person
{
……
}
訪問修飾符 請參看之前章節(jié)的內(nèi)容。
例子
例:
定義抽象類:Person。
public abstract class Person { // 抽象類
private String name; // 私有變量
public String getName() { // Getter方法
return name;
}
public void setName(String name) { //Setter方法
this.name = name;
}
public Person(String name) { // 構(gòu)造函數(shù),用于初始化name
super();
this.name = name;
}
public abstract String getDesc(); // 抽象類中的抽象方法。 只有聲明,沒有具體實現(xiàn)。
public String toString(){ // toString方法覆蓋了Object類的toString方法
return name + getDesc();
}
}
public class Student extends Person { // 繼承類
private String major; // 新增加的數(shù)據(jù)
public String getMajor() {
return major;
}
public void setMajor(String major) {
this.major = major;
}
public Student(String name,String major) { // 構(gòu)造函數(shù)用于初始化
super(name); // 調(diào)用超類構(gòu)造函數(shù)
this.major = major;
}
@Override
public String getDesc() { // 必須實現(xiàn)超類中的抽象方法
// TODO Auto-generated method stub
return " : a student, major is " + major;
}
繼承的作用
- 當今軟件設(shè)計的特征
軟件規(guī)模越來越大
軟件設(shè)計者越來越多
軟件設(shè)計分工越來越細 - 引入繼承,實現(xiàn)了代碼重用;
- 引入繼承,實現(xiàn)了遞增式的程序設(shè)計。
- 繼承是能自動傳播代碼和重用代碼的有力工具;
- 繼承能夠在某些比較一般的類的基礎(chǔ)上建造、建立和擴充新類;
- 能減少代碼和數(shù)據(jù)的重復(fù)冗余度,并通過增強一致性來減少模塊間的接口和界面,從而增強了程序的可維護性;
- 能清晰地體現(xiàn)出類與類之間的層次結(jié)構(gòu)關(guān)系。
注意事項
- 繼承是單方向的,即派生類可以繼承和訪問基類中的成員,但基類則無法訪問派生類中的成員; 父類不能訪問子類方法
- 在Java中只允許 單一繼承 方式,即一個派生類只能繼承于一個基類,而不能象C++中派生類繼承于多個基類的多重繼承方式。
繼承中的構(gòu)造方法
- 父類中的構(gòu)造方法可以被重載,但不能被子類繼承,即便它是public的;
- 父類的構(gòu)造方法負責初始化屬于它的成員變量,而子類的構(gòu)造方法則只需考慮屬于自己的成員變量,不必去關(guān)注父類的情況。
- 當實例化子類的對象時,必須先執(zhí)行父類的構(gòu)造方法,然后再執(zhí)行子類的構(gòu)造方法;
- 如果父類還有更上級的父類,就會先調(diào)用最高父類的構(gòu)造方法,再逐個依次地將所有繼承關(guān)系的父類構(gòu)造方法全部執(zhí)行;
- 如果父類的構(gòu)造方法執(zhí)行失敗,那么子類的對象也將無法實例化。
如
class ParentClass { //定義父類
public ParentClass() { //構(gòu)造方法
System.out.println("這是父類的構(gòu)造方法。");
}
}
class ChildClass extends ParentClass { //子類繼承于父類
public ChildClass() { //構(gòu)造方法
System.out.println("這是子類的構(gòu)造方法。");
}
}
public class ConstructorTest { //該類用于容納main方法
public static void main(String[] args) {
ChildClass cc = new ChildClass(); //實例化子類對象
}
}
super
如果子類需要調(diào)用父類的方法或者屬性怎么辦?
- 在子類的構(gòu)造方法中,super關(guān)鍵字可以顯式地調(diào)用父類的構(gòu)造方法,用于將參數(shù)傳遞給它;需要注意的是: 該語句必須是子類構(gòu)造方法的第一條語句
如:
class Point //定義"點"類
{
protected float mX, mY; //x軸坐標和y軸坐標
public Point(float x, float y) //構(gòu)造方法
{
mX = x;
mY = y;
}
……
}
class Circle extends Point //定義"圓"類繼承于"點"類
{
protected float mRadius; //半徑
public Circle(float x, float y, float r) //構(gòu)造方法
{
super(x, y); //顯式調(diào)用父類構(gòu)造方法,必須是第一條語句
mRadius = r;
}
……
}
- 如果父類和子類中有同名成員,在子類中默認訪問是屬于自己的那一個成員;super關(guān)鍵字可以明確地指定要訪問父類中的成員;但前提條件是:父類中的該成員不是private的
多態(tài)
方法覆蓋
定義
- 在類的繼承體系結(jié)構(gòu)中,如果子類中出現(xiàn)了與父類中有同原型的方法,那么認為子類中的方法覆蓋了父類中的方法(也稱為方法重寫);
- 通過子類的實例調(diào)用被覆蓋的方法時,將總是調(diào)用子類中的方法,而父類中的方法將被隱藏。
如:
/*如果不但名稱相同,而且連方法原型也完全相同的話,則構(gòu)成方法覆蓋*/
class ParentClass { //定義父類
public void fun() {
System.out.println("這是父類中的方法。");
}
}
class ChildClass extends ParentClass {//子類繼承于父類
public void fun() { //子類覆蓋父類中的方法
System.out.println("這是子類中的方法。");
}
}
class OverriddenTest { //用于容納main方法
public static void main(String[] args) {
ParentClass parObj = new ParentClass();
parObj.fun(); //父類的實例調(diào)用此方法
ChildClass chiObj = new ChildClass();
chiObj.fun(); //子類的實例調(diào)用此方法
}
}
方法覆蓋的注意事項
- 子類中重寫的方法,其訪問權(quán)限不能比父類中被重寫方法的訪問權(quán)限更低
- 在子類中重寫方法時要保持方法的簽名與父類中方法的簽名一致
引用轉(zhuǎn)型
- 基類(父類)的引用可以指向派生類(子類)的對象。
如:
BaseClass obj = new DerivedClass();
- 如果存在方法覆蓋,那么將會調(diào)用其派生類中的方法
如:
class Shapes { //基本形狀類
public void draw() { //繪圖的方法
System.out.println("繪制了一個基本形狀。");
}
}
class Circle extends Shapes { //圓形類繼承于基本形狀類
public void draw() { //覆蓋父類的繪圖方法
System.out.println("繪制了一個圓形。");
}
}
class Square extends Shapes { //正方形類繼承與基本形狀類
public void draw() { //覆蓋父類的繪圖方法
System.out.println("繪制了一個正方形。");
}
}
public class polymorphismDemo {
public static void main(String[] args) {
Shapes obj = new Shapes(); //父類的引用指向父類的實例
obj.draw(); //調(diào)用繪圖方法
obj = new Circle(); //父類的引用指向子類的實例
obj.draw(); //調(diào)用繪圖方法
obj = new Square(); //父類的引用指向子類的實例
obj.draw(); //調(diào)用繪圖方法
}
}
多態(tài)
在Java中,使用父類的引用,調(diào)用同一個方法,卻可以得到不同的調(diào)用結(jié)果,這就是多態(tài)。即: 同一函數(shù),多種形態(tài)。
實際上多態(tài)包括 動態(tài)多態(tài) 和 靜態(tài)多態(tài)。
靜態(tài)多態(tài)
- 靜態(tài)多態(tài)也稱為編譯時多態(tài),即在編譯時決定調(diào)用哪個方法;
- 靜態(tài)多態(tài)一般是指方法重載;
- 只要構(gòu)成了方法重載,就可以認為形成了靜態(tài)多態(tài)的條件;
- 靜態(tài)多態(tài)與是否發(fā)生繼承沒有必然聯(lián)系。
動態(tài)多態(tài)
動態(tài)多態(tài)也稱為運行時多態(tài),即在運行時才能確定調(diào)用哪個方法。
形成動態(tài)多態(tài)必須具體以下條件:
- 必須要有繼承的情況存在;
- 在繼承中必須要有方法覆蓋;
- 必須由基類的引用指向派生類的實例,并且通過基類的引用調(diào)用被覆蓋的方法;
由上述條件可以看出,繼承是實現(xiàn)動態(tài)多態(tài)的首要前提。
抽象
抽象方法
基類無法(或者沒有必要)提供被覆蓋方法的具體實現(xiàn),那么就可以將這個方法定義為 抽象方法。
[訪問權(quán)限] abstract 返回值類型 方法名(參數(shù)列表){
}
如:
public abstract void draw(){
}
抽象類
如果某個類包含抽象方法,那么這個類就必須定義成 抽象類。
[訪問權(quán)限] abstract class 類名{
成員列表
}
如:
public abstract class Shapes{
public abstract void draw();
}
抽象類的注意事項
- 抽象類不可以直接實例化,只可以用來繼承;
- 抽象類的派生子類應(yīng)該提供對其所有抽象方法的具體實現(xiàn);
- 如果抽象類的派生子類沒有實現(xiàn)其中的所有抽象方法,那么該派生子類仍然是抽象類,只能用于繼承,而不能實例化;
- 抽象類中也可以包含有非抽象的方法;
- 構(gòu)造方法和靜態(tài)方法不可以修飾為abstract。
如:
abstract class Shapes { //基本形狀類,抽象類
public abstract void draw(); //繪圖方法,抽象方法
}
class Circle extends Shapes { //圓形類繼承于基本形狀類
public void draw() { //實現(xiàn)抽象父類的抽象繪圖方法
System.out.println("繪制了一個圓形。");
}
}
class Square extends Shapes { //正方形類繼承與基本形狀類
public void draw() { //實現(xiàn)抽象父類的抽象繪圖方法
System.out.println("繪制了一個正方形。");
}
}
public class abstractDemo { //該類用于容納main方法
public static void main(String[] args) {
Shapes obj;
obj = new Circle(); //父類的引用指向子類的實例
obj.draw(); //調(diào)用繪圖方法
obj = new Square(); //父類的引用指向子類的實例
obj.draw(); //調(diào)用繪圖方法
}
}
接口
接口定義
如果某個類中所有的方法都是 抽象方法,那么可以考慮將該類定義為接口。
[訪問權(quán)限] interface 接口名{
成員列表
}
如:
public interface IMyInterface{
public void doIt();
}
實現(xiàn)接口
接口只能用于實現(xiàn),不能實例化(new)。
[訪問權(quán)限] class 類名 implements 接口名 {
成員列表
}
如:
public class MyClass implements IMyInterface {
public void doIt(){
}
}
接口的注意事項
- 接口中不能定義非抽象方法,也就是說接口中不能包含有函數(shù)實體。
接口中的所有方法都默認為抽象方法,無需在每個方法前加abstract關(guān)鍵字。 - 接口的實現(xiàn)類應(yīng)該提供對接口中所有抽象方法的具體實現(xiàn),否則將成為抽象類。
與抽象類和它的繼承類相似,也可以使用接口的引用指向其實現(xiàn)類的對象,從而達到動態(tài)多態(tài)的效果。 - Java只支持單繼承,而不能象C++那樣可以多重繼承,接口正是為了彌補這一點。
- Java中還允許一個接口繼承于另一個接口,即由父接口派生出子接口。
如:
public interface 子接口名 extends 父接口名 {
成員列表
}
final關(guān)鍵字
final修飾變量
如果將某個變量修飾為final,那么該變量就成為 常量
如:
final double PI = 3.14159;
☆ 常量在聲明時必須初始化。
final修飾方法
如果將某個成員方法修飾為final,則意味著該方法 不能被子類覆蓋
如:
public final void fun() {
……
}
//如果在派生類中出現(xiàn)同原型的方法,將會報錯。
final修飾類
如果將某個類修飾為final,則說明該類 無法被繼承
如:
public final class MyClass {
……
}
//任何類想繼承于MyClass類都將報錯。
類和類之間的關(guān)系
有——>has
汽車有輪子。一般來說這種關(guān)系對應(yīng)的是 屬性。是——>is
圓形是個形狀。一般來說這種關(guān)系對應(yīng)的是 方法。