1. 程式人生 > 其它 >第五章 面向物件

第五章 面向物件

第五章 面向物件

1 面向物件概念

  • 面向物件程式設計(Objected-Oriented Programming)OOP

  • 面向物件程式設計的本質就是:以類的方式組織程式碼,以物件的形式組織(封裝)資料

  • 抽象

  • 三大特性:

    1. 封裝
    2. 繼承
    3. 多型
  • 從認識論角度考慮是先有物件後有類,物件是具體的事物,類是抽象的,是對物件的抽象

  • 從程式碼執行角度考慮是先有類後有物件,類是物件的模板

2 方法的呼叫

2.1 靜態方法與非靜態方法

eg.:現有類名Student,該類含有靜態方法public static void a(){}和非靜態方法public void b(){}

靜態方法與非靜態方法區別在於:

  • 呼叫靜態方法:可以直接Student.a();來呼叫靜態方法
  • 呼叫非靜態方法:必須先new一個物件再呼叫非靜態方法,eg.:
    • Student student1 = new Student(); student1.b();
    • 或者new Student().b();

2.1.1 方法與方法之間的呼叫

若兩個方法都是非靜態方法,則可以互相呼叫

若兩個方法都是靜態方法,則可以互相呼叫

若一個是靜態方法,一個是非靜態方法,則靜態方法不可呼叫非靜態方法,非靜態方法可以呼叫靜態方法

  • 因為非靜態方法是建立物件後才存在,所以呼叫非靜態方法需要先new一個物件(即例項化)

2.2 值傳遞和引用傳遞

值傳遞術後

package com.oop.demo01;

public class Demo02 {
    public static void main(String[] args) {
        int a = 1;
        System.out.println(a);//輸出為1

        Demo02.change(a);
        System.out.println(a);//輸出為1
    }

    public  static void change(int a){
        a = 10;//無返回值,不會對實參產生影響
    }
}

值傳遞不會改變變數的值

引用傳遞

物件是通過引用來操作的:物件是在堆裡的

package com.oop.demo01;

public class Demo03 {
    public static void main(String[] args) {
        Student student = new Student();
        System.out.println(student.name);//輸出為null

        change(student);
        System.out.println(student.name);//輸出為Liam
    }

    public static void change(Student student){
        student.name= "Liam";
    }
}

//定義了一個Student類,有一個屬性:name
class Student{
    String name;//null
}

引用傳遞會改變變數的值

3 類與物件的關係

3.1 this關鍵字

this關鍵字指代當前類,編寫類的時候可以用this打點來呼叫當前類的屬性和方法

3.2 類與類之間的互動呼叫

  • 一個專案會有一個專門放main方法的Application類,其餘類不會放main方法
  • 一個類有兩個部分:屬性(即欄位),方法

eg.:

Student類:

package com.oop.demo02;

public class Student {
    //屬性:欄位
    String name;
    int age;

    //方法
    public void study(){
        System.out.println(this.name+"在學習");//this代表當前類
    }
}

Application類:

package com.oop.demo02;

public class Application {
    public static void main(String[] args) {
        Student xiaoming = new Student();
        xiaoming.name = "小明";
        xiaoming.age = 23;
        System.out.println(xiaoming.name);
        System.out.println(xiaoming.age);

        Student xiaohong = new Student();
        xiaohong.name = "小紅";
        xiaohong.age = 22;
        System.out.println(xiaohong.name);
        System.out.println(xiaohong.age);
    }
}

4 ※ 構造器 ※

  • 構造器作用:可以初始化例項物件的屬性的值

  • 使用new關鍵字本質是在呼叫構造器

  • 建立物件時會呼叫構造方法,呼叫完成時才建立完成一個物件

  • 使用new關鍵字建立物件時,除了分配記憶體空間之外,還會給建立好的物件進行預設的初始化以及對類中構造器的呼叫

  • 構造器格式:public 類名(){}構造器的名字必須跟類的名字相同,必須沒有返回型別,也不能有void

  • 構造器分為:有參構造器無參構造器

new一個物件時會跳轉到類的預設的無參構造器,這是預設會有的構造器,即使什麼構造器都不寫在相應的class檔案中也會有一個public 類名(){}

Student.java檔案如下圖:

Student.class檔案如下圖:

4.1 構造方法(即構造器)的過載

  • 構造方法(即構造器)也是有過載的,建立物件時,不傳引數就呼叫無參構造方法,傳一個引數就呼叫含有一個形參的構造方法,傳兩個引數就呼叫含有兩個形參的構造方法,以此類推

  • 建立構造器的快捷鍵:alt+insert再選中constructor,選擇要建立構造器包含的屬性

構造器過載示例

類部分:

package com.oop.demo02;

public class Person {

    String name;
    int age;
    char gender;

    public Person() {
    }

    public Person(String name){
        this.name = name;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name, char gender) {
        this.name = name;
        this.gender = gender;
    }

    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
}

main部分:

package com.oop.demo02;

public class Application {
    public static void main(String[] args) {
        
        Person person1 = new Person();
        Person person2 = new Person("liam");
        Person person3 = new Person("liam",23);
        Person person4 = new Person("liam",'男');
        Person person5 = new Person("liam",23,'男');
    }
}

【注意】即使在java檔案中什麼都不寫,也會預設有一個不顯示的無內容的無參構造器,但是一旦定義了有參構造,再想使用無參構造,無參構造就必須顯示定義

package com.oop.demo02;

public class Person {

    String name;

    //構造器作用:可以對例項物件的初始預設值進行設定
    //無參構造器:
    public Person(){
        
    }

    //有參構造器:一旦定義了有參構造,再想使用無參構造,無參構造就必須顯示定義
    public Person(String name){
        this.name = name;
    }
}

5 物件建立過程中的記憶體分析

現有Pet類部分:

package com.oop.demo02;

public class Pet {
    String name;
    int age;

    public void shout(){
        System.out.println("叫了一聲");
    }
}

main方法部分:

package com.oop.demo02;

public class Application {
    public static void main(String[] args) {
        Pet dog = new Pet();
        dog.name = "旺財";
        dog.age = 3;
        dog.shout();

        Pet cat = new Pet();
    }
}

記憶體分析如下圖所示:

6 封裝

封裝(資料的隱藏)

  • 通常應禁止直接訪問一個物件中的資料,而應該通過操作介面來訪問,這也成為資訊隱藏
  • 程式的設計應該追求“高內聚,低耦合”
    • 高內聚:類的內部資料操作細節自己完成,不允許外部干涉
    • 低耦合:僅暴露少量的方法給外部使用

封裝的意義

可以給私有屬性一些條件,用以約束屬性的範圍

6.1 private關鍵字

eg.現有Student類:

public class Student {

    private String name;
    private int id;
    private char gender;
}

通過private關鍵字讓屬性私有化,這使得不能直接用物件名打點來對屬性進行賦值操作,與之對應的是public關鍵字

6.2 私有屬性的設定set與獲取get

  • 為了能夠使用與操作私有化的屬性,需要一個獲得這個屬性的方法getName()以及設定這個屬性的方法setName()
  • 快捷鍵:alt+insert再選中Getter and Setter,選擇要建立set和get方法的屬性

eg.類部分:

package com.oop.demo03;

public class Student {

    private String name;
    private int id;
    private char gender;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if(age > 120|age < 0){
            System.out.println("ERROR");
        }else {
            this.age = age;
        }
    }//給age屬性設定約束,以保證不能隨心所欲的設定age的值
}

main部分:

package com.oop;

import com.oop.demo03.Student;

public class Application {
    public static void main(String[] args) {
        Student s1 = new Student();
        s1.setAge(130);
        System.out.println(s1.getAge());
    }
}

最後main部分輸出結果:

因為age設定了超過設定範圍所以輸出了ERROR,age沒有被成功賦值,還是保持了預設的0

7 繼承

  • 繼承的本質是對某一批類的抽象,從而實現對現實世界更好的建模
  • 繼承關係的兩個類,一個為子類(派生類),一個為父類(基類),子類繼承父類,使用關鍵字extends來表示
  • extends的意思是“擴充套件”,子類是父類的擴充套件
  • JAVA中類只有單繼承,沒有多繼承:就是一個兒子只能有一個父親,但是一個父親可以有多個兒子
  • 繼承是類和類之間的一種關係,除此之外,類和類之間的關係還有依賴組合聚合
  • 子類和父類之間,從意義上講應該具有父類包含子類的關係

子類會繼承父類的全部方法和public屬性,並不會繼承父類的private屬性以及private方法

父類部分:

package com.oop.demo05;

public class Person {

    private int money = 10_0000_0000;

    public void talk(){
        System.out.println("說了一句話");
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }
}

子類部分:

package com.oop.demo05;

//Student也是Person
public class Student extends Person{
}

mian部分:

package com.oop;

import com.oop.demo05.Student;

public class Application {
    public static void main(String[] args) {
        Student student = new Student();
        student.talk();//輸出“說了一句話”
        System.out.println(student.getMoney());//輸出“1000000000”
    }
}

在建立student物件後可以發現,Student類沒有任何方法,但是student物件卻能打點呼叫父類Person裡的talk()方法

在Java中,所有的類,都預設直接或者間接的繼承Object類

快捷鍵:alt+h可以檢視類之間的層次關係:

會發現所有類都直接或間接繼承Object類

7.1 super關鍵字

作用

  • 在Java類中使用super來呼叫父類中的指定操作:
    • super可用於訪問父類中定義的屬性
    • super可用於呼叫父類中定義的成員方法
    • super可用於在子類構造方法中呼叫父類的構造器

【注意】

  • 尤其當子父類出現同名成員時,可以用super進行區分
  • super的追溯不僅限於直接父類,super還可以呼叫父類的父類的屬性方法
  • super和this的用法相像,this代表本類物件的引用,super代表父類的記憶體空間的標識

7.1.1 用super訪問父類的屬性

Person父類部分:

package com.oop.demo05;

public class Person{
    protected String name = "liam";
}

Student子類部分:

package com.oop.demo05;

public class Student extends Person{
    private String name = "sprite";

    public void test(String name){
        System.out.println(name);
        System.out.println(this.name);
        System.out.println(super.name);
    }
}

main部分:

package com.oop;

import com.oop.demo05.Person;
import com.oop.demo05.Student;

public class Application {
    public static void main(String[] args) {
        Student student = new Student();
        student.test("LIAM");
    }
}

輸出結果:

7.1.2 用super呼叫父類的方法

Person父類部分:

package com.oop.demo05;

public class Person{

    private String name;

    public Person() {
        System.out.println("我是父類的無參構造");
    }

    public Person(String name) {
        System.out.println("我是父類的有參構造");
        this.name = name;
    }
}


Student子類部分:

package com.oop.demo05;

public class Student extends Person{

    private String name;

    public Student() {
        super();//可以省略
        System.out.println("我是子類的無參構造");
    }

    public Student(String name) {
        //super(name);   //-------(3)
        super();//可以省略
        System.out.println("我是子類的有參構造");
        this.name = name;
    }
}

main部分:

package com.oop;

import com.oop.demo05.Person;
import com.oop.demo05.Student;

public class Application {
    public static void main(String[] args) {
        Student student1 = new Student();   //-------(1)
        System.out.println("==================");
        Student student2 = new Student("LX");   //-------(2)
    }
}

輸出結果:

(1)語句執行的是子類的無引數構造方法,內部預設有super(),代表執行父類無引數構造方法,因此輸出父類無引數構造方法中的語句和子類無引數構造方法中的語句;

(2)語句執行的是子類有引數構造方法,內部也是預設有super(),代表執行父類無引數構造方法,,輸出語句是父類無引數構造方法中的語句和子類有引數構造方法中的語句;

若將(3)語句解除遮蔽,則子類有參構造方法中執行super("LX")表示執行父類有參構造方法Person("LX"),修改後Student子類:

package com.oop.demo05;

public class Student extends Person{

    private String name;

    public Student() {
        super();//可以省略
        System.out.println("我是子類的無參構造");
    }

    public Student(String name) {
        super(name);   //-------(3)
        //super();
        System.out.println("我是子類的有參構造");
        this.name = name;
    }
}

輸出結果:

對比可得:子類構造器內預設呼叫父類的無參構造器,只有用super("LX")主動呼叫父類有參構造才會呼叫父類有參構造

7.1.3 super()代表子類呼叫父類的構造方法(在main方法建立一個物件是用new關鍵字來呼叫類的構造方法)

Person父類部分:

package com.oop.demo05;

public class Person{

    public Person() {
        System.out.println("Person無參執行了");
    }
}

Student子類部分:

package com.oop.demo05;

public class Student extends Person{

    public Student() {
        super();//隱藏的程式碼,不寫也可以,代表呼叫了父類的無參構造
        System.out.println("Student無參執行了");
    }
}

main部分:

package com.oop;

import com.oop.demo05.Person;
import com.oop.demo05.Student;

public class Application {
    public static void main(String[] args) {
        Student student = new Student();

    }
}

輸出結果:

結果表明:在new了student物件時,會先呼叫父類Person類的構造器,這表示有一個預設的super()被呼叫了

【注意】

  1. super呼叫父類的構造方法必須在構造方法的第一行,不然會報錯,如圖所示
  1. super只能出現在子類的方法或者構造方法中
  2. this()表示呼叫本類的構造方法,super()和this()不能同時呼叫構造方法,因為this()和super()都要在第一行,如圖所示

8 方法重寫

8.1 重寫的作用

在子類中建立一個與父類中相同名稱、相同返回值型別、相同引數列表的方法,只是方法體中的實現不同,以實現不同於父類的功能,這

種方式被稱為方法重寫(override),又稱為方法覆蓋。當父類中的方法無法滿足子類需求或子類具有特有功能的時候,需要方法重寫。

子類可以根據需要,定義特定於自己的行為。既沿襲了父類的功能名稱,又根據子類的需要重新實現父類方法,從而進行擴充套件增強。

建立重寫方法的快捷鍵:alt+insert選中override再選中要重寫的方法即可

在重寫方法時,需要遵循下面的規則:

  • 重寫都是方法的重寫,與屬性無關
  • 引數列表必須完全與被重寫的方法引數列表相同
  • 返回的型別必須與被重寫的方法的返回型別相同(Java1.5 版本之前返回值型別必須一樣,之後的 Java 版本放寬了限制,返回值型別必須小於或者等於父類方法的返回值型別)
  • 訪問許可權不能比父類中被重寫方法的訪問許可權更低(public > protected > default > private)
  • 重寫方法一定不能丟擲新的檢查異常或者比被重寫方法宣告更加寬泛的檢查型異常。例如,父類的一個方法聲明瞭一個檢查異常IOException,在重寫這個方法時就不能丟擲 Exception,只能丟擲 IOException 的子類異常,可以丟擲非檢査異常。

另外還需要注意以下幾條

  • 重寫的方法可以使用@override註解來標識
  • 父類的成員方法只能被它的子類重寫
  • 宣告為final的方法不能被重寫
  • 宣告為static的方法不能被重寫,但是能再次宣告
  • 構造方法不能被重寫
  • 子類和父類在同一個包中時,子類可以重寫父類的所有方法,除了宣告為privae和final的方法
  • 子類和父類不在同一個包中時,子類只能重寫父類的宣告為public和protected的非final方法
  • 如果不能繼承一個方法,則不能重寫這個方法

8.2 重寫案例

父類Animal類:

package com.oop.demo05;

public class Animal {

    public String name;
    public int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void getInfo(){
        System.out.println("寵物名字是"+name+",年齡是"+age);
    }
}

子類Cat類:

package com.oop.demo05;

public class Cat extends Animal{

    private String hobby;

    public Cat(String name, int age, String hobby) {
        super(name, age);
        this.hobby = hobby;
    }

    @Override//註解作用,起到功能註釋的作用
    public void getInfo() {
        System.out.println("貓貓的名字是"+name+",年齡是"+age+",愛好是"+hobby);
    }
}

main方法:

package com.oop;

import com.oop.demo05.Animal;
import com.oop.demo05.Cat;

public class Application {
    public static void main(String[] args) {
        Animal animal = new Cat("富貴",3,"吃魚");
        animal.getInfo();
    }
}

輸出結果:

  • 上述程式碼,在 Animal 類中定義了一個getInfo() 的方法,而 Cat 類繼承自該類,因此 Cat 類同樣含有與 Animal 類中相同的 getInfo() 方法。但是我們在 Cat 類中又重新定義了一個 getInfo() 方法,即重寫了父類中的 getInfo() 方法。子類重寫了父類的方法,執行子類的方法。

  • 如果子類Cat中有一個方法eat(),父類沒有此方法,那麼Animal animal = new Cat("富貴",3,"吃魚");中的animal無法呼叫eat()方法。物件能執行哪些方法,主要看物件左邊的型別,和右邊型別不大。

9 多型

9.1 多型概念

  • 多型是面向物件程式設計的一個重要特徵,指同一個實體同時具有多種形式,即同一個物件,在不同時刻,代表的物件不一樣,指的是物件的多種形態。

  • 可以把不同的子類物件都當作父類來看,進而遮蔽不同子類物件之間的差異,寫出通用的程式碼,做出通用的程式設計,統一呼叫標準。

  • 比如,你的女盆友讓你買點水果回來,不管買回來的是蘋果還是西瓜,只要是水果就行,這個就是生活中多型的體現

  • 再比如,小貓、小狗、小豬我們可以把他們都歸納成小動物,每種小動物都需要吃東西,所以我們可以統一設定他們都必須吃,但是每種小動物的習性不一樣,那這個就可以設定成小動物自己特有的功能,多型物件只能呼叫父類中定義子類中重寫的功能,並不能呼叫子類的特有功能,這樣就實現了程式碼的統一

9.2 多型特點

  1. 多型的前提1:是繼承
  2. 多型的前提2:要有方法的重寫
  3. 父類引用指向子類物件,如:Animal a = new Cat();//建立一個Cat例項物件a,但a是Animal型別
  4. 多型中,編譯看左邊,執行看右邊

9.3 多型案例

父類Animal類:

package com.oop.demo05;

public class Animal {

    //建立父類普通方法
    public void eat(){
        System.out.println("動物吃啥都行");
    }
}

子類Cat類:

package com.oop.demo05;

public class Cat extends Animal{

    //新增重寫方法
    @Override
    public void eat() {
        System.out.println("貓貓愛吃魚");
    }

    //新增子類特有方法
    public void jump(){
        System.out.println("貓貓跳的老高了");
    }
}

子類Dog類:

package com.oop.demo05;

public class Dog extends Animal{

    //新增重寫方法
    @Override
    public void eat() {
        System.out.println("狗狗愛吃肉");
    }

    //新增子類特有方法
    public void run(){
        System.out.println("狗狗跑的老快了");
    }
}

main方法:

  1. 建立”純純的“物件用於測試
package com.oop;

import com.oop.demo05.Animal;
import com.oop.demo05.Cat;
import com.oop.demo05.Dog;

public class Application {
    public static void main(String[] args) {
        //1.建立”純純的“物件用於測試
        Animal a = new Animal();
        Cat c = new Cat();
        Dog d = new Dog();

        a.eat();//動物吃啥都行,呼叫的是父類自己的功能
        c.eat();//貓貓愛吃魚,呼叫的是子類重寫後的功能
        d.eat();//狗狗愛吃肉,呼叫的是子類重寫後的功能
        //a.jump();//報錯,Animal類裡並沒有這個方法
        //a.run();//報錯,Animal類裡並沒有這個方法
        c.jump();//貓貓跳的老高了,子類可以呼叫自己的功能
        d.run();//狗狗跑的老快了,子類可以呼叫自己的功能
    }
}
  1. 建立多型物件用於測試
package com.oop;

import com.oop.demo05.Animal;
import com.oop.demo05.Cat;
import com.oop.demo05.Dog;

public class Application {
    public static void main(String[] args) {
        //2.建立多型物件進行測試
        /*口訣1:父類引用指向子類物件
         * 解釋:創建出來的子類物件的地址值,交給父類型別的引用型別變數來儲存*/
        Animal a2 = new Cat();//Cat類物件的地址值交給父型別變數a2來儲存
        Animal a3 = new Dog();//Dog類物件的地址值交給父型別變數a3來儲存
        /*口訣2:編譯看左邊,執行看右邊
         * 解釋:必須要在父類定義這個方法,才能通過編譯,把多型物件看作是父類型別
         *      必須要在子類重寫這個方法,才能滿足多型,實際幹活的是子類*/
        a2.eat();//貓貓愛吃魚,多型物件使用的是父類的定義,子類的方法體
        //a2.jump();//無法呼叫子類Cat的jump方法
    }
}

9.4 多型的好處

  1. 多型可以讓我們不用關心某個物件到底具體是什麼型別,就可以使用該物件的某些方法
  2. 提高了程式的可擴充套件性和可維護性

9.5 多型的使用

前提:多型物件把自己看做是父類型別

  1. 成員變數:使用的是父類的
  2. 成員方法:由於存在重寫現象,所以使用的是子類的
  3. 靜態成員:隨著類的載入而載入,誰呼叫就返回誰的,靜態方法屬於類資源,不存在重寫現象,靜態方法在哪個類定義,就作為哪個類的資源使用(final和private也不存在重寫現象)
  • 口訣1:父類引用指向子類物件
  • 口訣2:編譯儲存(指各個屬性引數)看左邊,執行效果(指方法)看右邊,意思就是成員變數用的是父類的,方法用的是子類的

9.6 多型成員使用測試

父類Animal類:

package com.oop.demo05;

public class Animal {
    //建立父類成員變數
    public int sum =10;

    //建立父類普通方法
    public void eat(){
        System.out.println("吃啥都行");
    }

    //定義父類的靜態方法play
    public static void play(){
        System.out.println("玩啥都行");
    }
}

子類Dog類:

package com.oop.demo05;

public class Dog extends Animal{
    //建立子類的成員變數
    public int sum = 20;

    //重寫父類方法
    @Override
    public void eat() {
        System.out.println("狗狗愛吃肉");
    }

    //建立子類靜態方法play
    /*這不是一個重寫的方法,只是恰巧在兩個類中出現了一模一樣的兩個靜態方法
    靜態方法屬於類資源,只有一份,不存在重寫的現象
    在哪個類定義,就作為哪個類的資源使用
    */
    public static void play(){
        System.out.println("狗狗愛玩球");
    }
}

main方法:

  1. 建立”純純的“物件用於測試
package com.oop;

import com.oop.demo05.Animal;
import com.oop.demo05.Dog;

public class Application {
    public static void main(String[] args) {
        //建立”純純的“子類物件
        Dog dog = new Dog();
        System.out.println(dog.sum);//20,子類自己的屬性
        dog.eat();//狗狗愛吃肉,子類自己的方法
        dog.play();//狗狗愛玩球,並沒有重寫父類靜態方法
    }
}
  1. 建立多型物件用於測試
package com.oop;

import com.oop.demo05.Animal;
import com.oop.demo05.Dog;

public class Application {
    public static void main(String[] args) {
        /*口訣1:父類引用指向子類物件*/
        /*口訣2:編譯(儲存)看左邊,執行(效果)看右邊*/
        Animal a = new Dog();
        System.out.println(a.sum);//10,多型中,方法的宣告使用的是父類的,方法體使用的是子類的
        a.eat();//狗狗愛吃肉
        /*多型中,呼叫的靜態方法是父類的,因為多型物件把自己看作是父類型別
         * 直接使用父類中的靜態資源*/
        a.play();//玩啥都行
        Animal.play();//玩啥都行
    }
}

9.7 instanceof關鍵字

用途

Java 中的instanceof運算子是用來指出物件是否是特定類的一個例項,返回值是Boolean。

public class Application {
    public static void main(String[] args) {
        Animal a = new Dog();//建立一個Dog例項物件a,但a是Animal型別
        System.out.println(a instanceof Animal);//true,a是Dog例項物件,屬於Animal類
        System.out.println(a instanceof Dog);//true,a是Dog例項物件,屬於Dog類
        System.out.println(a instanceof Object);//true,a是Dog例項物件,屬於Object類
    }
}

9.8 向上轉型和向下轉型

​ 在JAVA中,繼承是一個重要的特徵,通過extends關鍵字,子類可以複用父類的功能,如果父類不能滿足當前子類的需求,則子類可以重寫父類中的方法來加以擴充套件。

那麼在這個過程中就存在著多型的應用。存在著兩種轉型方式,分別是:向上轉型和向下轉型。

  1. 向上轉型,即低轉高:可以把不同的子類物件都當作父類來看,進而遮蔽不同子類物件之間的差異,寫出通用的程式碼,做出通用的程式設計,統一呼叫標準。
  • 比如:父類Person,有walk方法,子類Student,有study方法

    父類的引用指向子類物件,將低一級的Student型別轉化為高一級的Person型別,不用強轉Person p=new Student();//建立一個Student例項物件p,但p是Person型別

    說明:向上轉型時,子類物件當成父類物件,只能呼叫父類的功能,如果子類重寫了父類中宣告過的方法,方法體執行的就是子類重過後的功能。但是此時物件是把自己看做是父類型別的,所以其他資源使用的還是父型別的。子類轉換為父類可能會丟失自己本來的一些方法。

    public class Application {
        public static void main(String[] args) {
            /*
            把Person p = new Student分為Student s = new Student();和Person p = s;兩句更容易理解低轉高
            * */
            Student s = new Student();
            //s.walk();//轉化前可以執行
            //s.study();//轉化前可以執行
    
            Person p = s;
            p.walk();//轉化後可以執行
            //p.study//轉化後不可執行
        }
    }
    

    比如:花木蘭替父從軍,大家都把花木蘭看做她爸,但是實際從軍的是花木蘭,而且,花木蘭只能做她爸能做的事,在軍營裡是不可以化妝的。

    花木蘭只能呼叫父類方法(從軍),或者呼叫重寫了父類中宣告過的方法(替他爸從軍),無法呼叫自己獨特的方法(貼花黃)

  1. 向下轉型,即高轉低(較少):子類的引用的指向子類物件,過程中必須要採取到強制轉型。這個是之前向上造型過的子類物件仍然想執行子類的特有功能,所以需要重新恢復成子類物件
  • Person p=new Student();//向上轉型,此時p是Person型別

    Student s = (Student)p//此時把高一級的Person型別p轉成低一級的Student型別

    就相當於建立了一個子類物件一樣,可以呼叫父類方法也能呼叫自己的方法

    說明:向下轉型是為了方便呼叫子類的專有方法,可以減少重複的程式碼,不用為了呼叫子類方法而再重新建立另一個子類物件來呼叫。例:Person p=new Student();,此時想呼叫子類Student的專有方法study(),就不用建立另一個子類的引用指向子類物件Student s = new Student();,直接Student s = (Student)p;強轉一下就可以使用子類的專有方法了。

    比如:花木蘭打仗結束後,就不需要再看作是她爸了,就可以”對鏡貼花黃“了

10 static總結

10.1 static修飾屬性

靜態變數和例項變數的區別:

  • 在語法定義上的區別:靜態變數前要加static關鍵字,而例項變數前則不加。

  • 在程式執行時的區別:

    1. 例項變數屬於某個物件的屬性,必須建立了例項物件,其中的例項變數才會被分配空間,才能使用這個例項變數。

    2. 靜態變數不屬於某個例項物件,而是屬於類,所以也稱為類變數,只要程式載入了類的位元組碼,不用建立任何例項物件,靜態變數就會被分配空間,靜態變數就可以被使用了。

    3. 總之,例項變數從屬於物件,靜態變數從屬於類靜態變數能夠被所有的例項物件共享,例項變數必須建立物件後才可以通過這個物件來使用,靜態變數則可以直接使用類名來引用。

10.2 static修飾方法

靜態方法跟類一起載入,普通方法跟物件一起載入,與static修飾屬性的情況一樣。

如Student類有普通方法a()和靜態方法b(),a能直接呼叫b,但b不能直接呼叫a,要想再b呼叫a,需要先建立一個Student例項物件,再用物件呼叫a。

10.3 static修飾程式碼塊

package com.oop.demo06;

public class Student {

    //程式碼塊可以賦初始值
    //第二個執行
    {
        System.out.println("匿名程式碼塊");
    }

    //靜態程式碼塊只執行一次
    //第一個執行
    static {
        System.out.println("靜態程式碼塊");
    }

    //第三個執行
    public Student() {
        System.out.println("構造方法");
    }

    public static void main(String[] args) {
        Student student = new Student();
        //輸出結果:
        //靜態程式碼塊
        //匿名程式碼塊
        //構造方法
    }
}

10.4 通過static靜態匯入包

package com.oop.demo06;

//靜態匯入包
import static java.lang.Math.random;

public class Student {

    public static void main(String[] args) {
        System.out.println(random());//靜態匯入Math.random後可以不用Math.random(),可以省略Math
    }
}

11 抽象類

抽象類是單繼承,介面可以多繼承(比如插座,約束了可以插什麼頭,但是沒約束可以用什麼電器)

父類抽象類:

package com.oop.demo06;

public abstract class Action {

    public abstract void doSomething();//抽象方法,只有方法名字,沒有方法的實現

}

子類:

package com.oop.demo06;

//繼承了抽象類的子類方法必須要重寫父類的方法來實現功能
public class A extends Action{
    @Override
    public void doSomething() {
        System.out.println("do something");
    }
}

抽象的特點

  • 抽象類不能new出來,只能靠new抽象類的子類來呼叫方法
  • 抽象類可以有抽象方法,這些抽象方法要此抽象類的子類來實現;抽象類也可以有一些正常的方法
  • 抽象方法必須在抽象類裡,抽象方法裡可以有抽象方法也可以有普通方法

抽象的意義

比如遊戲建立新角色,建立新角色的髮色、穿著可以重寫子類方法即可;可以節省程式碼,說白了還是為了提高開發效率。

12 介面

12.1 介面的概念

  • 官方解釋:Java介面是一系列方法的宣告,是一些方法特徵的集合,一個介面只有方法的特徵沒有方法的實現,因此這些方法可以在不同的地方被不同的類實現,而這些實現可以具有不同的行為(功能)
  • 通俗解釋:介面可以理解為一種特殊的類,裡面全部是由*全域性常量*公共的抽象方法所組成。介面是解決*Java無法使用多繼承*的一種手段,但是介面在實際中更多的作用是*制定標準*的。或者我們可以直接把介面理解為*100%的抽象類*,既介面中的方法*必須全部*是抽象方法。(JDK1.8之前可以這樣理解)
  • 普通類只有具體實現,抽象類具體實現和規範(即抽象方法)都有,介面只有規範(即抽象方法)

12.2 介面的特點

就像一個類一樣,一個介面也能夠擁有方法和屬性,但是在介面中宣告的方法預設是抽象的。(*即只有方法識別符號,而沒有方法體*)。

eg.

UserService介面:

package com.oop.demo07;

//interface定義的關鍵字,介面都需要有實現類,實現類名字一般字尾加Impl
public interface UserService {
    //介面中所有定義都是抽象的public

    int AGE = 99;//常量預設有public static final

    void add();//省略了public abstract,預設是有public abstract的
    void delete();
    void update();
    void query();
}

Timer介面:

package com.oop.demo07;

public interface Timer {
    void timer();
}

UserServiceImpl實現類(實現類一般字尾加Impl):

package com.oop.demo07;

//利用imlements關鍵字實現介面具體功能,實現類可以實現多個介面的功能比如下面還實現了Timer的方法
public class UserServiceImpl implements UserService,Timer{
    //實現類必須要重寫介面中的方法,以此實現具體功能
    @Override
    public void add() {
        //具體實現方法
    }

    @Override
    public void delete() {
        //具體實現方法
    }

    @Override
    public void update() {
        //具體實現方法
    }

    @Override
    public void query() {
        //具體實現方法
    }

    @Override
    public void timer() {
        //具體實現方法
    }
}

13 內部類

內部類就是在一個類的內部在定義一個類,比如,A類中定義一個B類,那麼B類相對A類來說就稱為內部類,而A類相對B類來說就是外部類了。

13.1 內部類的分類

  • 成員內部類
  • 靜態內部類
  • 區域性內部類
  • 匿名內部類

13.2 成員內部類

內部類可以獲取外部類的私有屬性和私有方法,這是普通類做不到的。

外部類Outer和內部類Inner:

package com.oop.demo08;

public class Outer {

    private int id = 10;

    public void out(){
        System.out.println("這是外部類的方法");
    }

    //內部類Inner
    public class Inner{
        public void in(){
            System.out.println("這是內部類的方法");
        }

        //內部類可以獲取外部類的私有屬性
        public void getID(){
            System.out.println(id);
        }

    }

}

//普通類
class method{
    public void getID(){
        //System.out.println(id);//報錯,普通類無法獲取其他普通類的私有屬性
    }
}

呼叫內部類的方法:

main方法部分:

package com.oop;

import com.oop.demo08.Outer;

public class Application {

    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.in();//輸出“這是內部類的方法”
    }

}

13.3 靜態內部類

靜態內部類無法獲取外部類的屬性,除非外部類的屬性也是靜態屬性。因為靜態成員是隨類最先一起載入的。

package com.oop.demo08;

public class Outer {

    private int id = 10;

    public void out(){
        System.out.println("這是外部類的方法");
    }

    public static class Inner{

        public void getID(){
            //System.out.println(id);//報錯,靜態內部類無法獲取外部類的屬性,除非外部類的屬性也是靜態屬性
        }

    }

}

13.4 區域性內部類

定義在方法中的內部類

package com.oop.demo08;

public class Outer {

    public void method(){

        //區域性內部類
        class Inner{
            public void in(){

            }
        }

    }

}

13.5 匿名內部類

package com.oop.demo08;

public class Test {
    public static void main(String[] args) {
        //沒有名字的初始化類,不用將例項儲存到變數中
        new Apple().eat();
    }
}

class Apple{
    public void eat(){
        System.out.println("吃蘋果");
    }
}