JAVA物件導向基礎--封裝 繼承 多型

月亮警察發表於2024-03-11

一、封裝

  • 該顯示的顯示,該隱藏的隱藏
  • 程式設計追求“高內聚,低耦合”
    • 高內聚:類的內部資料操作細節自己完成,不允許外部干涉
    • 低耦合:僅暴露少量的方法給外部使用
  • 封裝即資料的隱藏,通常應禁止直接訪問一個物件中資料的實際表示,而透過操作介面來訪問,這稱為資訊隱藏
  • 屬性私有,透過get/set方法運算元據

1、封裝的特點

  1. 提高程式的安全性,保護資料
  2. 隱藏程式碼的實現細節
  3. 統一介面
  4. 增加系統的可維護性

2、舉例

  1. 建立一個Student類
//private : 私有
public class Student02 {
    //屬性私有
    private String name;//姓名
    private int age;//年齡
    private char sex;//性別

    //提供一些可以操作這些屬性的方法
    //get/set方法

    //get方法:獲取這個資料
    public String getName(){
       return this.name;
}
    //set方法:給這個資料設定值
    public void setName(String name){
        this.name = name;
    }
}
  1. 透過呼叫get,set方法賦值獲取
public class Appliance {
    public static void main(String[] args) {
        Student02 s1 = new Student02();
        s1.setName("張三");
        System.out.println(s1.getName());//輸出張三
    }
}

注意:當屬性前新增了private時,在main方法中直接使用s1.name,如圖:

注意:快捷鍵Alt+Insert(Fn+Alt+Insert),可以自動生成get set方法,步驟如下:

  • 按下Alt+Insert-->選擇Getter and Setter

  • 選擇需要的屬性

3、封裝的好處--判斷輸入合法性

//private : 私有
public class Student02 {
    //屬性私有
    private int age;//年齡

    //get方法:獲取這個資料
    public int getAge() {
        return age;
    }
    //set方法:設定這個資料的值
    public void setAge(int age) {
        //進行合法性判斷
        if(age<120 && age>0){
            this.age = age;
        }else {
            this.age = 3;
        }
    }
}
public class Appliance {
    public static void main(String[] args) {
        Student02 s1 = new Student02();
        //s1.setAge(130);//錯誤資料
        //System.out.println(s1.getAge());//輸出3
        s1.setAge(18);//有效資料
        System.out.println(s1.getAge());//輸出18
    }
}

二、繼承

1、什麼是繼承?

  • 繼承的本質是對某一批類的抽象,從而實現對現實世界更好的建模

  • 繼承關係的兩個類,一個為子類(派生類),一個為父類(基類)。

    • 子類繼承父類,使用關鍵字extends來表示
    • extends的意思是“擴充套件”,子類是父類的擴充套件
    • 子類和父類之間,從意義上來講具有”is a“的關係
  • 繼承是類和類之間的一種關係。除此之外,類和類之間的關係還有依賴、組合、聚合等

  • Java中類只有單繼承,沒有多繼承(即子類只能有一個父類,一個父類可以有多個子類)

例項如下:

  1. 先建立一個Person類作為父類
//父類
public class Person {
    //私有屬性
    private String name;
    //方法
    public void say(){
        System.out.println("在說話!");
    }

    //get set方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

(注意:get/set方法請檢視封裝介紹)

  1. 建立一個Student類和Teacher類作為子類
//Person的子類:子類繼承父類的屬性和方法
public class Student extends Person {

}
//Person的子類:子類繼承父類的屬性和方法
public class Teacher extends Person {
    
}
  1. 再建立一個帶有main方法的Application類

Student類中什麼也沒有,但是繼承了父類中的name屬性和方法,可以使用

public class Application {
    public static void main(String[] args) {
        Student student = new Student();
        student.setName("張三");//獲取父類中的屬性name
        System.out.print(student.getName());//輸出張三
        student.say();//呼叫父類中的say()
    }
}

2、Object類

  • 快捷鍵Ctrl+H:檢視類之間的父子類關係

  • 注意:需把滑鼠停留在某個類中,再按快捷鍵

  • 圖例如下:

注意:我們發現最上層有一個Object類,是Person類的父類

  • 每一個類預設直接或間接的以Object類作為父類!!!!

例如:

public class Person /*extends Object*/ {
    //什麼也沒寫
}
public class Application {
    public static void main(String[] args) {
        Person person = new Person();
        //person物件還是可以點出很多方法,這些方法都是Object類的方法
        person.toString();
        person.equals();
        person.getClass();
    }
}

以下均為Object類中的方法:

3、super

用於呼叫父類中的方法和屬性

例項:

  1. Person類(父類)
public class Person /*extends Object*/ {
    //protected:受保護的
    protected String name = "張三";

    //print方法
    public void print(){
        System.out.println("Person");
    }
}
  1. Student類(子類)
//Person的子類
public class Student extends Person {
    private String name = "李四";

    //test方法
    public void test(String name){
        //this.name中的name表示Student類中的name屬性
        //等號右邊的name表示test方法的引數name
        this.name = name;
        //super.name中的name表示父類Person類中的name屬性
        super.name = name;
        System.out.println(this.name);
        System.out.println(super.name);
    }

    //print方法
    public void print(){
        System.out.println("Student");
    }
    //方法呼叫也是一樣
    public void test_2(){
        print();//呼叫本類的print方法,輸出Student
        this.print();//呼叫本類的print方法,輸出Student
        super.print();//呼叫父類的print方法,輸出Person
    }
}
  1. Application類,包含main方法
public class Application {
    public static void main(String[] args) {
        Student student = new Student();
        student.test("王五");
        System.out.println("================");
        student.test_2();
    }
}

執行結果如下圖:

注意點:

  1. super呼叫父類的構造方法,必須在構造方法的第一個
  2. super必須且只能出現在子類的方法或構造方法中
public class Person {
    
    public Person() {
        System.out.println("Person類的無參執行了!");
    }
}
public class Student extends Person {
    //無參構造
    public Student() {
        System.out.println("Student類的無參執行了!");
    }
}
public class Application {
    public static void main(String[] args) {
        Student student = new Student();//只new了一個物件,其他操作不做
    }
}

執行結果如下:Person類(父類)的構造方法先執行了,之後才執行了Student類(子類)的構造方法!!!!

原因如下:Student類中的無參構造有一句隱藏程式碼super(),呼叫了父類的無參構造

注意:super();必須放在構造器中的第一行位置,其他位置會報錯無法呼叫

public class Student extends Person {
    //無參構造
    public Student() {
        //隱藏程式碼:呼叫了父類的無參構造(程式碼如下:)
        super();//呼叫父類的構造器必須放在子類構造器的第一行
        System.out.println("Student類的無參執行了!");
    }
}
  1. super和this不能同時呼叫構造方法

Student類修改如下:

public class Student extends Person {
    //無參構造
    public Student() {
        //隱藏程式碼:呼叫了父類的無參構造(程式碼如下:)
        super();//呼叫父類的構造器必須放在子類構造器的第一行
        
        this();//呼叫本類的有參構造 //報錯!!!
        
        System.out.println("Student類的無參執行了!");
    }
    //有參構造
    public Student(String name) {
    }
}

報錯:報錯內容即‘this()'必須放在構造器的第一行,即super和this無法同時呼叫

  1. 當父類只有有參構造器,沒有無參構造器時,子類的無參構造中必須呼叫父類的有參構造,否則子類無法定義無參構造

注意:當父類的無參構造器顯示定義時即有無參,super()不會報錯!!

public class Person {
    //有參構造
    public Person(String name) {
        System.out.println("Person類的無參執行了!");
    }
}
public class Student extends Person {
    //無參構造
    public Student() {
        //隱藏程式碼:呼叫了父類的無參構造(程式碼如下:)
        super();//報錯!!!!!
        //原因:父類沒有無參構造
        System.out.println("Student類的無參執行了!");
    }
}
  • 報錯原因:父類沒有無參構造

  • **解決辦法:利用super("張三");呼叫父類的有參構造器如下:

    super("張三");
    

super 與 this

  1. 代表的物件不同:
  • this:本身呼叫者這個物件
  • super:代表父類物件的應用
  1. 使用前提:
  • this:沒有繼承也可以使用
  • super:只有在繼承條件下才可以使用
  1. 構造方法:
  • this():本類的構造
  • super():父類的構造

三、重寫

重寫都是對方法的重寫,與屬性無關

靜態方法和非靜態方法的差別很大!!!

注意:父子類需是同一種方法,即均為靜態或均為非靜態

1、靜態方法:

  • 方法的呼叫只和左邊定義的資料型別有關
//父類
public class B {
    //靜態方法
    public static void test(){
        System.out.println("B=>test()");
    }
}
//子類
public class A extends B {
    //靜態方法
    public static void test(){
        System.out.println("A=>test()");
    }
}
public class Application {
    public static void main(String[] args) {
        A a = new A();
        a.test();//輸出A=>test();
        
        B b = new A();//父類的引用(b)指向了A類
        b.test();//輸出B=>test();!!!!
    }
}

2、非靜態方法:

  • 重寫

注意:重寫的方法不能是私有的方法(即private的方法)

//父類
public class B {
    //非靜態方法
    public  void test(){
        System.out.println("B=>test()");
    }
}
//子類
public class A extends B {
    //非靜態方法
    //子類重寫了B(父類)的方法,那麼就是執行子類的方法
    public  void test(){
        System.out.println("A=>test()");
    }
}
public class Application {
    public static void main(String[] args) {
        A a = new A();
        a.test();//輸出A=>test();
        B b = new A();
        b.test();//輸出A=>test();!!!!!
    }
}

快速重寫父類方法:

  1. 在子類中按快捷鍵Alt+Insert(Fn+Alt+Insert),選擇Override Methods...

  1. 點選後會自動生成以下程式碼
//@Override:重寫
@Override  //註解:有功能的註釋
//子類重寫了B(父類)的方法,那麼就是執行子類的方法
//沒有重寫則呼叫父類的方法
public void test() {
    //內容可自行修改
    System.out.println("A=>test()");
    //super.test();//自動生成,super用於呼叫父類的方法
}

3、小結:

重寫需要有繼承關係,子類重寫父類的方法!

  • 方法名必須相同
  • 引數列表必須相同
  • 修飾符:範圍可以擴大但不能縮小
    • public>Protected>Default>private
  • 丟擲的異常:範圍可以被縮小但不能擴大
    • 例如:ClassNotFoundException(小) --->Exception(大)
  • 子類的方法和父類的方法必須一致,但方法體不同
  • 方法重寫快捷鍵:Alt+Insert

為什麼要重寫?

  • 父類的功能,子類不一定需要或者不一定滿足

四、多型

多型:即同一方法可以根據傳送物件的不同而採用多種不同的行為方式

  • 一個物件的實際型別是確定的,但可以指向物件的引用型別有很多(父類,有關係的類)

1、情況一:無重寫

//父類
public class Person {
    public void run(){
        System.out.println("run");
    }
}
//父類
public class Person {
    public void run(){
        System.out.println("run");
    }
}
//子類
public class Student extends Person {
    public void say(){
        System.out.println("say");
    }
}
//測試類
public class Applicaton {
    public static void main(String[] args) {
        //一個物件的實際型別是確定的
        //new Student(); 或者 new Person();
        //但一個物件可以指向的引用型別是不確定的:父類的引用可以指向子類

        //Studnet子類:可以呼叫自己的方法或者繼承父類的方法
        Student s1 = new Student();

        //Person父類:引用物件可以指向子類,但無法呼叫子類獨有的方法
        Person s2 = new Student();
        Object s3 = new Student();

        //物件可以執行哪些方法,主要是看物件左邊的型別,和右邊關係不大!!
        s1.run();//子類呼叫父類的方法,輸出run
        s2.run();//輸出run
        
        //s2.say();//錯誤!!!父類引用物件無法呼叫子類獨有的方法
        //((Student)s2).say();//強制型別轉換即可呼叫
    }
}

2、情況二:重寫

Student類重寫了父類的say()方法:

//子類
public class Student extends Person {

    //重寫父類方法:會執行子類的方法
    @Override
    public void run() {
        System.out.println("run_02");
    }

    public void say(){
        System.out.println("say");
    }
}

測試類和Person類不變:

//父類
public class Person {
    public void run(){
        System.out.println("run");
    }
}
//測試類
public class Applicaton {
    public static void main(String[] args) {
        
        //Studnet子類:可以呼叫自己的方法或者繼承父類的方法
        Student s1 = new Student();

        //Person父類:引用物件可以指向子類,但無法呼叫子類獨有的方法
        Person s2 = new Student();
        Object s3 = new Student();

        s1.run();//輸出run_02
        s2.run();//重寫父類方法:會執行子類的方法  輸出run_02!!!!
    }
}

3、注意事項:

  1. 多型是方法的多型,屬性沒有多型
  2. 多型的使用需要類之間有父子類關係
  3. 型別轉換異常會報錯:ClasCastException
  4. 多型存在條件:
  • 需要有父子類關係(繼承關係)
  • 方法需要重寫
  • 父類的引用指向子類物件
    • Father f1 = new Son();
  • 無法被重寫的方法有:
    • static方法:靜態方法屬於類和類一起載入,不屬於例項,無法被重寫
    • final 常量
    • private 方法

五、instanceof

判斷兩個類是否相似,是否可以比較轉換(型別轉換),相似返回true,不相似返回false,語法如下:

注意:有關係的兩個類才能進行比較

System.out.println(X instanceof Y);
public class Application {
    public static void main(String[] args) {

        //instanceof 二者有關係才能進行判斷

        //Object > String
        //Object > Person > Student
        //Object > Person > Teacher
        Object obj = new Student();
        System.out.println(obj instanceof Person);//True
        System.out.println(obj instanceof Student);//True
        System.out.println(obj instanceof Object);//True
        System.out.println(obj instanceof Teacher);//False; //Student類和Teacher類是同級
        System.out.println(obj instanceof String);//False;  //String是Object的子類
        System.out.println("==============================");

        Person person = new Student();
        System.out.println(person instanceof Person);//True
        System.out.println(person instanceof Student);//True
        System.out.println(person instanceof Object);//True
        System.out.println(person instanceof Teacher);//False;
        //System.out.println(person instanceof String);//編譯報錯 //Person與String沒有聯絡無法比較
        System.out.println("==============================");

        Student student = new Student();
        System.out.println(student instanceof Person);//True
        System.out.println(student instanceof Student);//True
        System.out.println(student instanceof Object);//True
        //System.out.println(student instanceof Teacher);//編譯報錯 //Student類和Teacher類沒有聯絡無法比較
        //System.out.println(person instanceof String);//編譯報錯

    }
}

1、強制型別轉換

//父類
public class Person {
    public void to(){
        System.out.println("to");
    }
}
//子類
public class Student extends Person {
    public void go(){
        System.out.println("go");
    }
}
//測試類
public class Application {
    public static void main(String[] args) {
        //型別轉換:父類  子類

        Student s1 = new Student();
        s1.go();//子類方法
        //子類轉為父類,可能會丟失自己本來的一些方法
        //子類可以直接轉為父類
        Person s2 = s1;
        s2.to();//父類方法

        //父類需強制轉換為子類
        Person p1 = new Person();
        //p1.go();//報錯,父類無法使用子類的方法,需要強制轉換
        //強制轉換
        Student p2 = (Student) p1;
        p2.go();//子類方法
        //以上兩句合為一句如下:
        ((Student)p1).go();
    }
}

2、小結:

  1. 父類引用指向子類的物件‘
  2. 向上轉型:把子類轉為父類
  3. 向下轉型:把父類轉為子類(強制轉換)
  4. 強制轉換的作用:
    • 方便方法的呼叫,減少重複程式碼

相關文章