Java面向對象基礎(下)

  • 理解動態(tài)多態(tài)性的表現(xiàn)
  • 理解向上轉型與向下轉型的概念
  • 掌握instanceof關鍵字的使用
  • 掌握匿名內部類的實現(xiàn)方式
  • 理解靜態(tài)內部類與非靜態(tài)內部類的區(qū)別
  • 了解局部內部類形式
  • 掌握枚舉類的聲明和使用
  • 了解記錄類的特點和聲明方式
  • 了解密封類的特點和聲明方式
  • 了解代碼塊的作用和執(zhí)行特點

7.1 多態(tài)

7.1.1 什么是多態(tài)

多態(tài)是繼封裝、繼承之后,面向對象的第三大特性。

多態(tài)的本意就是多種形態(tài),在編程中通常是指方法的多種形態(tài)。多態(tài)有兩種形式:靜態(tài)多態(tài)(也稱為編譯時多態(tài),如方法重載)和動態(tài)多態(tài)(也稱為運行時多態(tài),如方法重寫)。動態(tài)多態(tài)是通過繼承和接口實現(xiàn)的,允許我們使用父類或接口類型的引用指向子類對象,然后調用方法時,會根據對象的實際類型來決定調用哪個方法的實現(xiàn)。這樣可以編寫出更加靈活和可擴展的代碼。

7.1.2 動態(tài)多態(tài)性

想要呈現(xiàn)動態(tài)多態(tài)性,需要3個必要條件:

  • 子類繼承父類或實現(xiàn)接口
  • 方法重寫
  • 多態(tài)引用

1、多態(tài)引用的語法

Java規(guī)定父類型的變量可以接收子類類型的對象,這一點從邏輯上也是說得通的。

父類類型 變量名 = 子類對象; 
父接口類型 變量名 = 實現(xiàn)類對象; 

2、動態(tài)多態(tài)性的表現(xiàn)

多態(tài)引用后調用方法的表現(xiàn)是:編譯時看左邊,運行時看右邊。這就是Java虛方法的動態(tài)綁定機制。所謂虛方法,就是可以被子類/實現(xiàn)類重寫的方法。

package com.atguigu.polymorphism.grammar;
public class Pet {
    public void eat(){
        System.out.println("吃東西");
    }
}
package com.atguigu.polymorphism.grammar;
public class Dog extends Pet {
    public void watchHouse(){
        System.out.println("看家");
    }
    @Override
    public void eat(){
        System.out.println("狗狗啃骨頭");
    }
}
package com.atguigu.polymorphism.grammar;
public class Cat extends Pet{
    public void catchMouse(){
        System.out.println("抓老鼠");
    }
    @Override
    public void eat(){
        System.out.println("貓咪吃魚仔");
    }
}
package com.atguigu.polymorphism.grammar;
public class Pig extends Pet {
    
}
package com.atguigu.polymorphism.grammar;
public class TestPolymorphismGood {
    public static void main(String[] args) {
        Pet p = new Dog();
        p.eat();
       // pet.watchHouse();//不能調用子類擴展的方法
        p = new Cat();
        p.eat();
        p = new Pig();
        p.eat();
    }
}
運行結果:
狗狗啃骨頭
貓咪吃魚仔
吃東西

3、如何獲取對象的運行時類型呢?

java.lang.Object類中有一個方法可以獲取對象的運行時類型:

public final Class getClass()返回此 Object 的運行時類。
package com.atguigu.polymorphism.grammar;
public class TestPolymorphism {
    public static void main(String[] args) {
        Pet p = new Dog();//p變量是Pet類型,但它指向Dog對象
        System.out.println("p變量的運行時類型:" + p.getClass());
        
        p = new Cat();//p變量還可以指向Cat對象。
        System.out.println("p變量的運行時類型:" + p.getClass());
    }
}
運行結果:
p變量的運行時類型:class com.atguigu.polymorphism.grammar.Dog
p變量的運行時類型:class com.atguigu.polymorphism.grammar.Cat

7.1.3 多態(tài)引用的應用場景

1、聲明變量是父類類型,變量賦值子類對象

- 方法的形參是父類類型,調用方法的實參是子類對象

- 成員變量聲明父類類型,實際存儲的是子類對象

package com.atguigu.polymorphism.grammar;
public class Pet {
    private String nickname;
    
    public Pet(String nickname){
        this.nickname = nickname;
    }
    public String getNickname() {
        return nickname;
    }
    public void setNickname(String nickname) {
        this.nickname = nickname;
    }
    
    public void eat(){
        System.out.println("吃東西");
    }
}
package com.atguigu.polymorphism.grammar;
public class Dog extends Pet {
    public Dog(String nickname){
        this.nickname = nickname;
    }
    
    public void watchHouse(){
        System.out.println("看家");
    }
    @Override
    public void eat(){
        System.out.println("狗狗啃骨頭");
    }
    @Override
    public String toString() {
        return "Dog{"+getNickname()+"}";
    }
}
package com.atguigu.polymorphism.grammar;
public class Cat extends Pet{
    public Cat(String nickname){
        this.nickname = nickname;
    }
    
    public void catchMouse(){
        System.out.println("抓老鼠");
    }
    @Override
    public void eat(){
        System.out.println("貓咪吃魚仔");
    }
    @Override
    public String toString() {
        return "Cat{"+getNickname()+"}";
    }
}
package com.atguigu.polymorphism.grammar;
public class Owner {
    private String name;
    private Pet pet;//成員變量聲明為父類類型,實際賦值的是子類對象
    
    public Owner(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Pet getPet() {
        return pet;
    }
    public void setPet(Pet pet) {//方法形參聲明為父類類型,實參是子類對象
        this.pet = pet;
    }
    //feed:喂食
    public void feed() {
        if (pet!= null) {
            //這里eat()執(zhí)行哪個類的eat()方法,是根據pet對象的運行時類型動態(tài)綁定的
            pet.eat();
        }
    }
    @Override
    public String toString() {
        return "Owner{" +
                "name='" + name + '\'' +
                ", pet=" + pet +
                //這里+pet,會自動調用pet.toString(),至于執(zhí)行哪個類的toString方法,也是根據pet對象的運行時類型動態(tài)綁定的
                '}';
    }
}
package com.atguigu.polymorphism.grammar;
public class TestPersonPet {
    public static void main(String[] args) {
        Owner zhang = new Owner("張三");
        zhang.setPet(new Dog("旺財"));
        zhang.feed();
        System.out.println(zhang);
        
        
        Owner li = new Owner("李四");      
        li.setPet(new Cat("雪球"));
        li.feed();
        System.out.println(li);
    }
}
運行結果:
狗狗啃骨頭
Owner{name='張三', pet=Dog{旺財}}
貓咪吃魚仔
Owner{name='李四', pet=Cat{雪球}}

2、數組元素是父類類型,元素對象是子類對象

package com.atguigu.polymorphism.grammar;
public class Person {
    private String name;
    private Pet[] pets;
    
    public Person(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Pet[] getPets() {
        return pets;
    }
    public void setPets(Pet[] pets) {
        this.pets = pets;
    }
    public void feed() {
        for(int i=0; i
package com.atguigu.polymorphism.grammar;
public class TestPersonPets {
    public static void main(String[] args) {
        Person p = new Person("王五");
        Pet[] pets = new Pet[2];
        //數組的元素類型聲明為父類類型,實際存儲的是子類對象
        pets[0] = new Dog("旺財");
        pets[1] = new Cat("雪球");
        p.setPets(pets);
        p.feed();
        System.out.println(p);
    }
}

3、方法返回值類型聲明為父類類型,實際返回的是子類對象

package com.atguigu.polymorphism.grammar;
public class PetShop {
    //返回值類型是父類類型,實際返回的是子類對象
    public Pet sale(String type){
        switch (type){
            case "Dog":
                return new Dog();
            case "Cat":
                return new Cat();
        }
        return null;
    }
}
package com.atguigu.polymorphism.grammar;
public class TestPetShop {
    public static void main(String[] args) {
        PetShop shop = new PetShop();
        Pet p1 = shop.sale("Dog");
        /*
        p1的編譯時類型是Pet,運行時類型是Dog
         */
        p1.setNickname("小白");
        System.out.println(p1);//自動調用Dog類的toString方法
        Pet p2 = shop.sale("Cat");
        /*
        p2的編譯時類型是Pet,運行時類型是Cat
         */
        p2.setNickname("雪球");
        System.out.println(p2);//自動調用Cat類的toString方法
    }
}

7.1.4 向上轉型與向下轉型

1、向上轉型

(1)什么是向上轉型?

- 讓一個子類對象在**在編譯期間**,以父類的類型呈現(xiàn),就是向上轉型。

(2)如何實現(xiàn)向上轉型呢?

- 當把子類對象賦值給父類的變量時,此時通過這個父類變量引用它時,這個子類對象就呈現(xiàn)為“父類的類型”,

Pet p = new Dog();  
//接下來,通過p引用Dog對象,編譯期間就呈現(xiàn)為Pet類型

(3)為什么要向上轉型呢?

父類類型 > 子類類型。很多地方不得不使用父類類型代替子類類型聲明變量。

2、向下轉型

(1)什么是向下轉型?

- 讓一個父類的變量在**在編譯期間**,以子類的類型呈現(xiàn),就是向下轉型。

(2)如何實現(xiàn)向下轉型呢?

- 必須使用強制類型轉換的語法

Pet p = new Dog();
Dog d = (Dog)p;
//接下來,通過d引用Dog對象,編譯期間就呈現(xiàn)為Dog類型

(3)為什么要向下轉型呢?

為了使用子類擴展的成員。

Pet p = new Dog();
Dog d = (Dog)p;
//接下來,通過d引用Dog對象,編譯期間就呈現(xiàn)為Dog類型
d.watchHouse();

3、注意問題

(1)無論向上還是向下,都只發(fā)生在編譯時,對象的運行時類型不會變

首先,一個對象在new的時候創(chuàng)建的是哪個類型的對象,它從頭至尾都不會變。即這個對象的運行時類型,本質的類型不會變。但是,把這個對象賦值給不同類型的變量時,這些變量的編譯時類型卻不同。

這個和基本數據類型的轉換是不同的?;緮祿愋褪前褦祿礳opy了一份,相當于有兩種數據類型的值。而對象的賦值不會產生兩個對象。

(2)向上轉型和向下轉型只支持父子類或父接口與實現(xiàn)類之間。

Dog dog = "hello";//錯誤,不能向上也不能向下,不是父子類之間

(3)向上轉型向下轉型操作編譯通過,運行時不一定通過

不是所有通過編譯的轉型轉換都是正確的,可能會發(fā)生ClassCastException

package com.atguigu.polymorphism.grammar;
public class ClassCastTest {
    public static void main(String[] args) {
        //沒有類型轉換
        Dog dog = new Dog();//dog的編譯時類型和運行時類型都是Dog
        //向上轉型
        Pet pet = new Dog("小白");//pet的編譯時類型是Pet,運行時類型是Dog
        pet.eat();//可以調用父類Pet有聲明的方法eat,但執(zhí)行的是子類重寫的eat方法體
//        pet.watchHouse();//不能調用父類沒有的方法watchHouse
        Dog d = (Dog) pet;
        d.eat();//可以調用eat方法
        d.watchHouse();//可以調用子類擴展的方法watchHouse
        Cat c = (Cat) pet;//編譯通過,因為從語法檢查來說,pet的編譯時類型是Pet,Cat是Pet的子類,所以向下轉型語法正確
        //這句代碼運行報錯ClassCastException,因為pet變量的運行時類型是Dog,Dog和Cat之間是沒有繼承關系的
    }
}

4、instanceof關鍵字

為了避免ClassCastException的發(fā)生,Java提供了 `instanceof` 關鍵字,用來判斷對象的類型關系,只要用instanceof判斷返回true的,那么強轉為該類型就一定是安全的,不會報ClassCastException異常。

變量 instanceof 數據類型 

那么,哪些instanceof判斷會返回true呢?

- 對象的編譯時類型 與  instanceof后面數據類型是 父子類關系才編譯通過

- 對象的運行時類型<= instanceof后面數據類型,運行結果才為true

package com.atguigu.polymorphism.grammar;
public class TestInstanceof {
    public static void main(String[] args) {
        Pet[] pets = new Pet[2];
        pets[0] = new Dog("小白");//多態(tài)引用
        pets[1] = new Cat("雪球");//多態(tài)引用
        for (int i = 0; i < pets.length; i++) {
            pets[i].eat();
            if(pets[i] instanceof Dog){
                Dog dog = (Dog) pets[i];
                dog.watchHouse();
            }else if(pets[i] instanceof Cat){
                Cat cat = (Cat                cat.catchMouse();
            }
        }
    }
}

5、新特性:instanceof模式匹配

instanceof的模式匹配在JDK14、15中預覽,在JDK16中轉正。有了它就不需要編寫先通過instanceof判斷再強制轉換的代碼。

package com.atguigu.polymorphism.grammar;
public class TestInstanceof {
    public static void main(String[] args) {
        Pet[] pets = new Pet[2];
        pets[0] = new Dog("小白");//多態(tài)引用
        pets[1] = new Cat("雪球");//多態(tài)引用
        for (int i = 0; i < pets.length; i++) {
            pets[i].eat();
            if(pets[i] instanceof Dog dog){//新特性
                dog.watchHouse();
            }else if(pets[i] instanceof Cat cat){//新特性
                cat.catchMouse();
            }
        }
    }
}

7.1.5 經典面試題

1、虛方法的靜態(tài)分配和動態(tài)綁定

在Java中虛方法是指在編譯階段和類加載階段都不能確定方法的調用入口地址,在運行階段才能確定的方法,即可能被重寫的方法。

當我們通過“對象.方法”的形式調用一個虛方法時,要如何確定它具體執(zhí)行哪個方法呢?

==注意:super.方法 和 對象.非虛方法(靜態(tài)方法,final修飾的方法等),不使用以下規(guī)則。==

(1)編譯時靜態(tài)分派:先看這個對象xx的編譯時類型,在這個對象的編譯時類型中找到能匹配的方法

匹配的原則:看實參的編譯時類型與方法形參的類型的匹配程度 A:找最匹配    實參的編譯時類型 = 方法形參的類型 B:找兼容      實參的編譯時類型 < 方法形參的類型

(2)運行時動態(tài)綁定:再看這個對象xx的運行時類型,如果這個對象xx的運行時類重寫了剛剛找到的那個匹配的方法,那么執(zhí)行重寫的,否則仍然執(zhí)行剛才編譯時類型中的那個匹配的方法

package com.atguigu.polymorphism.virtual;
    
class MyClass{
    public void method(Father f) {
        System.out.println("father");
    }
    public void method(Son s) {
        System.out.println("son");
    }
}
class MySub extends MyClass{
    public void method(Father d) {
        System.out.println("sub--father");
    }
    public void method(Daughter d) {
        System.out.println("daughter");
    }
}
class Father{
    
}
class Son extends Father{
    
}
class Daughter extends Father{
    
}
package com.atguigu.polymorphism.virtual;
public class TestVirtualMethod {
    public static void main(String[] args) {
        Father f = new Father();
        Son s = new Son();
        Daughter d = new Daughter();
        
        MyClass my = new MySub();
        my.method(f);//sub--father
            /*
            (1)靜態(tài)分派:看my的編譯時類型MyClass,在MyClass中找最匹配的
                匹配的原則:看實參的編譯時類型與方法形參的類型的匹配程度
                 A:找最匹配    實參的編譯時類型 = 方法形參的類型
                 B:找兼容      實參的編譯時類型 < 方法形參的類型
                 實參f的編譯時類型是Father,形參(Father f) 、(Son s)
                 最匹配的是public void method(Father f)
            (2)動態(tài)綁定:看my的運行時類型MySub,看在MySub中是否有對    public void method(Father f)進行重寫
                發(fā)現(xiàn)有重寫,如果有重寫,就執(zhí)行重寫的
                    public void method(Father d) {
                        System.out.println("sub--");
                    }
             */
        my.method(s);//son
            /*
            (1)靜態(tài)分派:看my的編譯時類型MyClass,在MyClass中找最匹配的
                匹配的原則:看實參的編譯時類型與方法形參的類型的匹配程度
                 A:找最匹配    實參的編譯時類型 = 方法形參的類型
                 B:找兼容      實參的編譯時類型 < 方法形參的類型
                 實參s的編譯時類型是Son,形參(Father f) 、(Son s)
                 最匹配的是public void method(Son s)
            (2)動態(tài)綁定:看my的運行時類型MySub,看在MySub中是否有對 public void method(Son s)進行重寫
                發(fā)現(xiàn)沒有重寫,如果沒有重寫,就執(zhí)行剛剛父類中找到的方法
             */
        my.method(d);//sub--father
             /*
            (1)靜態(tài)分派:看my的編譯時類型MyClass,在MyClass中找最匹配的
                匹配的原則:看實參的編譯時類型與方法形參的類型的匹配程度
                 A:找最匹配    實參的編譯時類型 = 方法形參的類型
                 B:找兼容      實參的編譯時類型 < 方法形參的類型
                 實參d的編譯時類型是Daughter,形參(Father f) 、(Son s)
                 最匹配的是public void method(Father f)
            (2)動態(tài)綁定:看my的運行時類型MySub,看在MySub中是否有對 public void method(Father f)進行重寫
                發(fā)現(xiàn)有重寫,如果有重寫,就執(zhí)行重寫的
                    public void method(Father d) {
                        System.out.println("sub--");
                    }
             */
    }
}

2、成員變量沒有多態(tài)一說

package com.atguigu.polymorphism.grammar;
public class Father {
    int a = 1;
    int c = 1;
}
package com.atguigu.polymorphism.grammar;
public class Son extends Father {
    int a = 2;
    int d = 2;
}
package com.atguigu.polymorphism.grammar;
public class TestField {
    public static void main(String[] args) {
        Father f = new Son();
        System.out.println("f.a = " + f.a);
        System.out.println("f.c = " + f.c);
//        System.out.println("f.d" + f.d);//錯誤
        System.out.println("((Son)f).a = " + ((Son)f).a );
        System.out.println("((Son)f).d = " + ((Son)f).d);
        System.out.println("-------------------");
        Son s =  new Son();
        System.out.println("s.a = " + s.a);
        System.out.println("s.c = " + s.c);
        System.out.println("s.d = " + s.d);
        System.out.println("((Father)s).a = " + ((Father)s).a);
        System.out.println("((Father)s).c = " + ((Father)s).c);
//        System.out.println("((Father)s).d = " + ((Father)s).d);//錯誤
    }
}


7.2 內部類

7.2.1 概述

1、什么是內部類?

將一個類A定義在另一個類B里面,里面的那個類A就稱為**內部類**,B則稱為**外部類**。

2、為什么要聲明內部類呢?

總的來說,遵循高內聚低耦合的面向對象開發(fā)總原則。便于代碼維護和擴展。

3、內部類都有哪些形式?

根據內部類聲明的位置(如同變量的分類),我們可以分為:

  • 成員內部類:        

    • 靜態(tài)成員內部類
    • 非靜態(tài)成員內部類

       

  • 局部內部類:        

    • 有名字的局部內部類
    • 匿名的內部類

       

7.2.2 成員內部類

如果成員內部類中不直接使用外部類的非靜態(tài)成員,那么通常將內部類聲明為靜態(tài)內部類,否則聲明為非靜態(tài)內部類。

語法格式:

【修飾符】 class 外部類{
    【其他修飾符】 【static】 class 內部類{
    }
}

1、靜態(tài)內部類

有static修飾的成員內部類叫做靜態(tài)內部類。它的特點:

  • 和其他類一樣,它只是定義在外部類中的另一個完整的類結構        

    • 可以繼承自己的想要繼承的父類,實現(xiàn)自己想要實現(xiàn)的父接口們,和外部類的父類和父接口無關
    • 可以在靜態(tài)內部類中聲明屬性、方法、構造器等結構,包括靜態(tài)成員
    • 編譯后有自己的獨立的字節(jié)碼文件:外部類名$靜態(tài)內部類名.class
    • 在外部類的外面使用靜態(tài)內部類名時需要使用”包名.外部類名.靜態(tài)內部類名“

       

  • 和外部類不同的是,它可以允許四種權限修飾符:public,protected,缺省,private
  • 在外部類的任意位置都可以直接使用靜態(tài)內部類,也可以訪問靜態(tài)內部類的所有成員,包括私有的        

    • 如果使用靜態(tài)內部類的靜態(tài)成員,就通過”靜態(tài)內部類名.靜態(tài)成員“的形式
    • 如果使用靜態(tài)內部類的非靜態(tài)成員,就先創(chuàng)建靜態(tài)內部類的對象,然后通過“靜態(tài)內部類對象.非靜態(tài)成員”的形式

       

  • ==只可以==在靜態(tài)內部類中使用外部類的靜態(tài)成員        

    • 在靜態(tài)內部類中不能直接使用外部類的非靜態(tài)成員哦
    • 如果在內部類中有變量與外部類的靜態(tài)變量同名,可以使用“外部類名.靜態(tài)變量"進行區(qū)別

       

  • 如果權限修飾符允許,靜態(tài)內部類也可以在外部類的外面使用。        

    • 如果使用靜態(tài)內部類的靜態(tài)成員,就通過”外部類名.靜態(tài)內部類名.靜態(tài)成員“的形式
    • 如果使用靜態(tài)內部類的非靜態(tài)成員,就先創(chuàng)建靜態(tài)內部類的對象,在外部類的外面不需要通過外部類的對象就可以創(chuàng)建靜態(tài)內部類的對象

       

其實嚴格的講(在James Gosling等人編著的《The Java Language Specification》書中)靜態(tài)內部類不是內部類,而是類似于C++的嵌套類的概念,外部類僅僅是靜態(tài)內部類的一種命名空間的限定名形式而已。所以接口中的內部類通常都不叫內部類,因為接口中的內部成員都是隱式是靜態(tài)的(即public static)。例如:Map.Entry。

package com.atguigu.inner.staticinner;
public class TestStaticInner {
    public static void main(String[] args) {
        Outer.outMethod();
        System.out.println("-----------------------");
        Outer out = new Outer();
        out.outFun();
        System.out.println("####################################");
        Outer.Inner.inMethod();
        System.out.println("------------------------");
        Outer.Inner inner = new Outer.Inner();
        inner.inFun();
    }
}
class Outer{
    private static String a = "外部類的靜態(tài)a";
    private static String b  = "外部類的靜態(tài)b";
    private String c = "外部類對象的非靜態(tài)c";
    private String d = "外部類對象的非靜態(tài)d";
    static class Inner{
        private static String a ="靜態(tài)內部類的靜態(tài)a";
        private String c = "靜態(tài)內部類對象的非靜態(tài)c";
        public static void inMethod(){
            System.out.println("Inner.inMethod");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("Outer.b = " + b);
//            System.out.println("Outer對象.c = " + Outer.c + "," + Outer.this.c);//不能訪問外部類的非靜態(tài)成員
//            System.out.println("Outer對象.d = " + Outer.d + "," + Outer.this.d);//不能訪問外部類的非靜態(tài)成員
            System.out.println("Inner.a = " + a);
            //System.out.println("Inner對象.c = " + c);//不能訪問自己的非靜態(tài)成員
        }
        public void inFun(){
            System.out.println("Inner對象.inFun");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("Outer.b = " + b);
//            System.out.println("Outer對象.c = " + Outer.c + "," + Outer.this.c);//不能訪問外部類的非靜態(tài)成員
//            System.out.println("Outer對象.d = " + Outer.d + "," + Outer.this.d);//不能訪問外部類的非靜態(tài)成員
            System.out.println("Inner.a = " + a);
            System.out.println("Inner對象c = " + c);
        }
    }
    public static void outMethod(){
        System.out.println("Outer.outMethod");
        System.out.println("Outer.a = " + a);
        System.out.println("Outer.b = " + b);
//        System.out.println("Outer對象.c = " + c);
//        System.out.println("Outer對象.d = " + d);
        System.out.println("Inner.a = " + Inner.a);
        Inner in = new Inner();
        System.out.println("Inner對象.c = " + in.c);
    }
    public void outFun(){
        System.out.println("Outer對象.outFun");
        System.out.println("Outer.a = " + a);
        System.out.println("Outer.b = " + b);
        System.out.println("Outer對象.c = " + c);
        System.out.println("Outer.objects.d = " + d);
        System.out.println("Inner.a = " + Inner.a);
        Inner in = new Inner();
        System.out.println("Inner.objects.c = " + in.c);
    }
}

2、非靜態(tài)成員內部類

沒有static修飾的成員內部類叫做非靜態(tài)內部類。非靜態(tài)內部類的特點:

  • 和其他類一樣,它只是定義在外部類中的另一個完整的類結構        

    • 可以繼承自己的想要繼承的父類,實現(xiàn)自己想要實現(xiàn)的父接口們,和外部類的父類和父接口無關
    • 可以在非靜態(tài)內部類中聲明屬性、方法、構造器等結構,JDK16之后**也允許聲明靜態(tài)成員**。
    • 編譯后有自己的獨立的字節(jié)碼文件:外部類名$非靜態(tài)內部類名.class
    • 在外部類的外面使用非靜態(tài)內部類名時需要使用”包名.外部類名.非靜態(tài)內部類名“

       

  • 和外部類不同的是,它可以允許四種權限修飾符:public,protected,缺省,private
  • 在外部類中使用非靜態(tài)內部類==有限制==        

    • 在外部類的靜態(tài)成員中,只能使用非靜態(tài)內部類的靜態(tài)常量
    • 在外部類的非靜態(tài)成員中,可以創(chuàng)建非靜態(tài)內部類對象,然后通過非靜態(tài)內部類的對象使用非靜態(tài)內部類的所有成員

       

  • 可以在非靜態(tài)內部類中使用外部類的**所有成員**,哪怕是私有的        

    • 如果沒有重名問題,可以直接使用外部類的所有成員
    • 如果非靜態(tài)內部類與外部類的靜態(tài)成員重名,可以使用“外部類名."進行區(qū)別
    • 如果非靜態(tài)內部類與外部類的非靜態(tài)成員重名,可以使用“外部類名.this."進行區(qū)別

       

  • 如果權限修飾符允許,非靜態(tài)內部類也可以在外部類的外面使用。        

    • 如果使用非靜態(tài)內部類的靜態(tài)成員,就通過”外部類名.非靜態(tài)內部類名.靜態(tài)成員“的形式            
    • 在外部類的外面必須通過外部類的對象才能創(chuàng)建非靜態(tài)內部類的對象(通常應該避免這樣使用)                

      • 如果要在外部類的外面使用非靜態(tài)內部類的對象,通常在外部類中提供一個方法來返回這個非靜態(tài)內部類的對象比較合適
      • 因此在非靜態(tài)內部類的方法中有兩個this對象,一個是外部類的this對象,一個是內部類的this對象

                 

       

package com.atguigu.inner.notstaticinner;
public class TestNonStaticInner {
    public static void main(String[] args) {
        Outer.outMethod();
        System.out.println("-----------------------");
        Outer out = new Outer();
        out.outFun();
        System.out.println("####################################");
        System.out.println(Outer.Inner.s);
        System.out.println("-----------------------");
        Outer.Inner inner = new Outer().new Inner();
        inner.inFun();
    }
}
class Outer{
    private static String a = "外部類的靜態(tài)a";
    private static String b  = "外部類的靜態(tài)b";
    private String c = "外部類對象的非靜態(tài)c";
    private String d = "外部類對象的非靜態(tài)d";
    class Inner{
        private String a = "非靜態(tài)內部類對象的非靜態(tài)a";
        private String c = "非靜態(tài)內部類對象的非靜態(tài)c";
        public static final String s = "非靜態(tài)內部類的靜態(tài)常量";
        public void inFun(){
            System.out.println("Inner對象.inFun");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("Outer.b = " + b);
            System.out.println("Outer對象.c = " + Outer.this.c);
            System.out.println("Outer.objects.d = " + d);
            System.out.println("Inner對象.a = " + a);
            System.out.println("Inner對象c = " + c);
            System.out.println("Inner.s = " + Inner.s);
        }
    }
    public static void outMethod(){
        System.out.println("Outer.outMethod");
        System.out.println("Outer.a = " + a);
        System.out.println("Outer.b = " + b);
//        System.out.println("Outer對象.c = " + c);
//        System.out.println("Outer.objects.d = " + d);
//        Inner in = new Inner();//此處無法使用非靜態(tài)內部類
        System.out.println("Inner.s = " + Inner.s);
    }
    public void outFun(){
        System.out.println("Outer.objects.outFun");
        System.out.println("Outer.a = " + a);
        System.out.println("Outer.b = " + b);
        System.out.println("Outer.objects.c = " + c);
        System.out.println("Outer.objects.d = " + d);
        Inner in = new Inner();
        System.out.println("Inner.objects.a = " + in.a);
        System.out.println("Inner.objects.c = " + in.c);
        System.out.println("Inner.s = " + Inner.s);
    }
}

7.2.3 局部內部類

1、局部內部類

語法格式:

【修飾符】 class 外部類{
    【修飾符】 返回值類型  方法名(【形參列表】){
            【final/abstract】 class 內部類{
        }
    }    
}

局部內部類的特點:

  • 和外部類一樣,它只是定義在外部類的某個方法中的另一個完整的類結構        

    • 可以繼承自己的想要繼承的父類,實現(xiàn)自己想要實現(xiàn)的父接口們,和外部類的父類和父接口無關
    • 可以在局部內部類中聲明屬性、方法、構造器等結構,JDK16之后**也允許聲明靜態(tài)成員**            
    • 編譯后有自己的獨立的字節(jié)碼文件:外部類名$數字編號局部內部類名.class。                
    • 這里有編號是因為同一個外部類中,不同的方法中存在相同名稱的局部內部類

       

  • 和成員內部類不同的是,它前面不能有權限修飾符等
  • 局部內部類如同局部變量一樣,有作用域
  • 局部內部類中是否能訪問外部類的非靜態(tài)的成員,取決于所在的方法
  • 局部內部類中還可以使用所在方法的局部常量,即用final聲明的局部變量        

    • JDK1.8之后,如果某個局部變量在局部內部類中被使用了,自動加final
    • 為什么在局部內部類中使用外部類方法的局部變量要加final呢?考慮生命周期問題。

       

package com.atguigu.inner.local;
public class TestLocalInner {
    public static void main(String[] args) {
        Outer.outMethod();
        System.out.println("-------------------");
        Outer out = new Outer();
        out.outFun();
        System.out.println("===========================");
        //這里不能使用局部內部類
//        Outer.Inner in;
        //但是這里可以獲取局部內部類的對象,只能通過多態(tài)引用該局部內部類的對象
        Father f = Outer.outTest();
        f.m();
    }
}
class Outer{
    private static String a = "外部類的靜態(tài)a";
    private static String b  = "外部類的靜態(tài)b";
    private String c = "外部類對象的非靜態(tài)c";
    private String d = "外部類對象的非靜態(tài)d";
    public static void outMethod(){
        class Inner{
            private String a = "非靜態(tài)內部類1對象的非靜態(tài)a";
            private String c = "非靜態(tài)內部類1對象的非靜態(tài)c";
            public static final String s = "非靜態(tài)內部類1的靜態(tài)常量";
            public void inFun(){
                System.out.println("Inner對象.inFun");
                System.out.println("Outer.a = " + Outer.a);
                System.out.println("Outer.b = " + b);
//                System.out.println("Outer對象.c = " + Outer.this.c);
//                System.out.println("Outer.objects.d = " + d);
                System.out.println("Inner1對象.a = " + a);
                System.out.println("Inner1對象c = " + c);
                System.out.println("Inner1.s = " + Inner.s);
            }
        }
        new Inner().inFun();
    }
    public void outFun(){
        class Inner{
            private String a = "非靜態(tài)內部類2對象的非靜態(tài)a";
            private String c = "非靜態(tài)內部類2對象的非靜態(tài)c";
            public static final String s = "非靜態(tài)內部類2的靜態(tài)常量";
            public void inFun(){
                System.out.println("Inner2對象.inFun");
                System.out.println("Outer.a = " + Outer.a);
                System.out.println("Outer.b = " + b);
                System.out.println("Outer.objects.c = " + Outer.this.c);
                System.out.println("Outer.objects.d = " + d);
                System.out.println("Inner2對象.a = " + a);
                System.out.println("Inner2對象c = " + c);
                System.out.println("Inner2.s = " + Inner.s);
            }
        }
        new Inner().inFun();
    }
    public static Father outTest(){
        final int outA = 1;
        class Inner extends Father{
            @Override
            void m() {
                System.out.println("局部內部類重寫父類方法");
                System.out.println("局部內部類使用外部類的局部變量outA = " + outA);
            }
        }
        return new Inner();
    }
    public static void outOtherMethod(){
//        Inner in = new Inner();//局部內部類有作用域范圍限制
    }
}
abstract class Father{
    abstract void m();
}
package com.atguigu.inner.local;
public class OuterClass {
    public static void outTest(final int value){
        final int outA = 1;
        class InnerClass{
            void m() {
                System.out.println("value = " + value);
                System.out.println("outA = " + outA);
            }
        }
    }
}


如果外部類局部變量的值是確定的常量值,那么編譯器直接在局部內部類中就相當于使用了一個確定常量值;

如果外部類局部變量的值是待確定的常量值,那么編譯器會在局部內部類中用一個隱式的成員變量接收該局部變量的值,當局部內部類對象創(chuàng)建時自動完成賦值,即局部內部類中使用的已經不再是外部類的局部變量了,而是自己的一個成員變量;

package com.atguigu.inner.local;
public class OuterClass {
    public static Object outTest(final int value){
        final int outA = 1;
        class InnerClass{
            void m() {
                System.out.println("value = " + value);
                System.out.println("outA = " + outA);
            }
            @Override
            public String toString() {
                return "value = " + value + ",outA = " + outA;
            }
        }
//        value = 2;
//        outA = 2;
        return new InnerClass();
    }
    public static void main(String[] args) {
        System.out.println(outTest(1));
    }
}

2、匿名內部類

當我們在開發(fā)過程中,需要用到一個抽象類的子類的對象或一個接口的實現(xiàn)類的對象,而且只創(chuàng)建一個對象,而且邏輯代碼也不復雜。那么我們原先怎么做的呢?

  • (1)編寫類,繼承這個父類或實現(xiàn)這個接口
  • (2)重寫父類或父接口的方法
  • (3)創(chuàng)建這個子類或實現(xiàn)類的對象

這里,因為考慮到這個子類或實現(xiàn)類是一次性的,那么我們“費盡心機”的給它取名字,就顯得多余。那么我們完全可以使用匿名內部類的方式來實現(xiàn),避免給類命名的問題。

new 父類(【實參列表】){
    重寫方法...
}
//()中是否需要【實參列表】,看你想要讓這個匿名內部類調用父類的哪個構造器,如果調用父類的無參構造,那么()中就不用寫參數,如果調用父類的有參構造,那么()中需要傳入實參
new 父接口(){
    重寫方法...
}
//()中沒有參數,因為此時匿名內部類的父類是Object類,它只有一個無參構造

注意:

匿名內部類沒有名字,因此字節(jié)碼文件名是外部類名$編號.class。

匿名內部類是一種特殊的局部內部類,只不過沒有名稱而已。所有局部內部類的限制都適用于匿名內部類。例如:

  • 在匿名內部類中是否可以使用外部類的非靜態(tài)成員變量,看所在方法是否靜態(tài)
  • 在匿名內部類中如果需要訪問當前方法的局部變量,該局部變量需要加final

思考:這個對象能做什么呢?

(1)使用匿名內部類的對象直接調用方法
interface A{
    void a();
}
public class Test{
    public static void main(String[] args){
        new A(){
            @Override
            public void a() {
                System.out.println("aaaa");
            }
        }.a();
    }
}
(2)通過父類或父接口的變量多態(tài)引用匿名內部類的對象
interface A{
    void a();
}
public class Test{
    public static void main(String[] args){
        A obj = new A(){
            @Override
            public void a() {
                System.out.println("aaaa");
            }
        };
        obj.a();
    }
}
(3)匿名內部類的對象作為實參
interface A{
    void method();
}
public class Test{
    public static void test(A a){
        a.method();
    }
    
    public static void main(String[] args){
        test(new A(){
            @Override
            public void method() {
                System.out.println("aaaa");
            }
        });
    }   
}

7.2.4 幾種內部類對比

  靜態(tài)內部類非靜態(tài)內部類局部內部類匿名內部類
類角色字節(jié)碼文件外部類名$靜態(tài)內部類名.class外部類名$非靜態(tài)內部類名.class外部類名$編號局部內部類名.class外部類名$編號.class
父類或父接口正常正常正常指定一個直接父類或一個直接父接口
類成員正常正常正常正常,構造器只能有默認構造器
權限修飾符public、protected、缺省、privatepublic、protected、缺省、private
成員角色依賴于外部類依賴依賴依賴依賴
依賴于外部類的對象不依賴依賴看所在方法看所在方法
在外部類的靜態(tài)成員中使用內部類沒有限制不能直接new非靜態(tài)內部類的對象有嚴格作用域有嚴格作用域
在外部類的非靜態(tài)成員中使用內部類沒有限制沒有限制有嚴格作用域有嚴格作用域
在內部類中使用外部類的靜態(tài)成員沒有限制沒有限制沒有限制沒有限制
成員角色(續(xù))在內部類中使用外部類的非靜態(tài)成員不能沒有限制看所在方法看所在方法
在內部類中使用外部類的某局部變量局部變量加final局部變量加final
在外部類的外面使用內部類的靜態(tài)成員外部類名.靜態(tài)內部類名.靜態(tài)成員外部類名.非靜態(tài)內部類名.靜態(tài)成員
在外部類的外面獲取內部類的對象外部類名.靜態(tài)內部類名 變量 = 外部類名.靜態(tài)內部類名();外部類名 out變量 = new 外部類(); 
外部類名.非靜態(tài)內部類名 in變量 = out變量.new 非靜態(tài)內部類名();
只能通過外部類的某方法返回局部內部類的對象;只能通過外部類的某方法返回匿名內部類的對象;
與外部類的靜態(tài)成員重名外部類名.靜態(tài)成員外部類名.靜態(tài)成員外部類名.靜態(tài)成員外部類名.靜態(tài)成員
與外部類的非靜態(tài)成員重名外部類名.this.非靜態(tài)成員同左同左 
選擇依據 在內部類中不使用外部類的非靜態(tài)成員在內部類中需要使用外部類的非靜態(tài)成員。該內部類僅限于當前方法使用該內部類代碼簡潔,對象唯一性

7.3 枚舉

7.3.1 概述

某些類型的對象是有限的幾個,這樣的例子舉不勝舉:

  • 星期:Monday(星期一)......Sunday(星期天)
  • 性別:Man(男)、Woman(女)
  • 月份:January(1月)......December(12月)
  • 季節(jié):Spring(春節(jié))......Winter(冬天)
  • 支付方式:Cash(現(xiàn)金)、WeChatPay(微信)、Alipay(支付寶)、BankCard(銀行卡)、CreditCard(信用卡)
  • 員工工作狀態(tài):Busy(忙)、Free(閑)、Vocation(休假)
  • 訂單狀態(tài):Nonpayment(未付款)、Paid(已付款)、Fulfilled(已配貨)、Delivered(已發(fā)貨)、Checked(已確認收貨)、Return(退貨)、Exchange(換貨)、Cancel(取消)

枚舉類型本質上也是一種類,只不過是這個類的對象是固定的幾個,而不能隨意讓用戶創(chuàng)建。

在JDK1.5之前,需要程序員自己通過特殊的方式來定義枚舉類型。

在JDK1.5之后,Java支持enum關鍵字來快速的定義枚舉類型。

7.3.2 JDK1.5之前

在JDK1.5之前如何聲明枚舉類呢?

  • 構造器加private私有化
  • 本類內部創(chuàng)建一組常量對象,并添加public static修飾符,對外暴露這些常量對象
public class Season{
    public static final Season SPRING = new Season();
    public static final Season SUMMER = new Season();
    public static final Season AUTUMN = new Season();
    public static final Season WINTER = new Season();
    
    private Season(){
        
    }
    
    public String toString(){
        if(this == SPRING){
            return "春";
        }else if(this == SUMMER){
            return "夏";
        }else if(this == AUTUMN){
            return "秋";
        }else{
            return "冬";
        }
    }
}
public class TestSeason {
    public static void main(String[] args) {
        Season spring = Season.SPRING;
        System.out.println(spring);
    }
}

7.3.3 JDK1.5之后

1、enum關鍵字聲明枚舉

語法格式:

【修飾符】 enum 枚舉類名{
    常量對象列表
}
【修飾符】 enum 枚舉類名{
    常量對象列表;
    
    其他成員列表;
}
package com.atguigu.enumeration;
public enum Week {
    MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY
}
public class TestEnum {
    public static void main(String[] args) {
        Season spring = Season.SPRING;
        System.out.println(spring);
    }
}

2、枚舉類的要求和特點

枚舉類的要求和特點:

  • 枚舉類的常量對象列表必須在枚舉類的首行,因為是常量,所以建議大寫。
  • 如果常量對象列表后面沒有其他代碼,那么“;”可以省略,否則不可以省略“;”。
  • 編譯器給枚舉類默認提供的是private的無參構造,如果枚舉類需要的是無參構造,就不需要聲明,寫常量對象列表時也不用加參數,
  • 如果枚舉類需要的是有參構造,需要手動定義,有參構造的private可以省略,調用有參構造的方法就是在常量對象名后面加(實參列表)就可以。
  • 枚舉類默認繼承的是java.lang.Enum類,因此不能再繼承其他的類型。
  • JDK1.5之后switch,提供支持枚舉類型,case后面可以寫枚舉常量名。
  • 枚舉類型如有其它屬性,建議(**不是必須**)這些屬性也聲明為final的,因為常量對象在邏輯意義上應該不可變。
package com.atguigu.enumeration;
public enum Week {
    MONDAY("星期一"),
    TUESDAY("星期二"),
    WEDNESDAY("星期三"),
    THURSDAY("星期四"),
    FRIDAY("星期五"),
    SATURDAY("星期六"),
    SUNDAY("星期日");
    private final String description;
    private Week(String description){
        this.description = description;
    }
    @Override
    public String toString() {
        return super.toString() +":"+ description;
    }
}
package com.atguigu.enumeration;
public class TestWeek {
    public static void main(String[] args) {
        Week week = Week.MONDAY;
        System.out.println(week);
        switch (week){
            case MONDAY:
                System.out.println("懷念周末,困意很濃");break;
            case TUESDAY:
                System.out.println("進入學習狀態(tài)");break;
            case WEDNESDAY:
                System.out.println("死撐");break;
            case THURSDAY:
                System.out.println("小放松");break;
            case FRIDAY:
                System.out.println("又信心滿滿");break;
            case SATURDAY:
                System.out.println("開始盼周末,無心學習");break;
            case SUNDAY:
                System.out.println("一覺到下午");break;
        }
    }
}

3、枚舉類型常用方法

常用方法如下:

  • String toString(): 默認返回的是常量名(對象名),可以繼續(xù)手動重寫該方法!
  • String name():返回的是常量名(對象名)
  • int ordinal():返回常量的次序號,默認從0開始
  • 枚舉類型[] values():返回該枚舉類的所有的常量對象,返回類型是當前枚舉的數組類型,是一個靜態(tài)方法
  • 枚舉類型 valueOf(String name):根據枚舉常量對象名稱獲取枚舉對象
package com.atguigu.enumeration;
import java.util.Scanner;
public class TestEnumMethod {
    public static void main(String[] args) {
        Week[] values = Week.values();
        for (int i = 0; i < values.length; i++) {
            System.out.println((values[i].ordinal()+1) + "->" + values[i].name());
        }
        System.out.println("------------------------");
        Scanner input = new Scanner(System.in);
        System.out.print("請輸入星期值:");
        int weekValue = input.nextInt();
        Week week = values[weekValue-1];
        System.out.println(week);
        System.out.print("請輸入星期名:");
        String weekName =曦input.next();
        week = Week.valueOf(weekName);
        System.out.println(week);
        input.close();
    }
}

7.4 新特性:記錄類(了解)

Record類在JDK14、15預覽特性,在JDK16中轉正。

record是一種全新的類型,它本質上是一個 final類,同時所有的屬性都是 final修飾,它會自動編譯出get、hashCode 、比較所有屬性值的equals、toString 等方法,減少了代碼編寫量。使用 Record 可以更方便的創(chuàng)建一個常量類。

1.注意:

  • Record只會有一個全參構造
  • 重寫的equals方法比較所有屬性值
  • 可以在Record聲明的類中定義靜態(tài)字段、靜態(tài)方法或實例方法。
  • 不能在Record聲明的類中定義實例字段;
  • 不能顯式的聲明父類,默認父類是java.lang.Record類
  • 因為Record類是一個 final類,所以也沒有子類等。因此rRecord類不能聲明為abstract;
  • 屬性也是final
package com.atguigu.record;
public class TestRecord {
    public static void main(String[] args) {
        Triangle t = new Triangle(3, 4, 5);
        System.out.println(t);
        System.out.println("面積:" + t.area());
        System.out.println("周長:" + t.perimeter());
        System.out.println("邊長:" + t.a() + "," + t.b() + "," + t.c());
        Triangle t2 = new Triangle(3, 4, 5);
        System.out.println(t.equals(t2));
    }
}
record Triangle(double a, double b, double c) {
    public double area() {
        if (a > 0 && b > 0 && c > 0 && a + b > c && b + c > a && a + c > b) {
            double p = (a + b + c) / 2;
            return Math.sqrt(p * (p - a) * (p - b) * (p - c));
        }
        return 0.0;
    }
    public double perimeter() {
        if (a > 0 && b > 0 && c > 0 && a + b > c && b + c > a && a + c > b) {
            return a + b + c;
        }
        return 0.0;
    }
}

7.5 新特性:密封類(了解)

其實很多語言中都有`密封類`的概念,在Java語言中,也早就有`密封類`的思想,就是final修飾的類,該類不允許被繼承。而從JDK15開始,針對`密封類`進行了升級。

Java 15通過密封的類和接口來增強Java編程語言,這是新引入的預覽功能并在Java 16中進行了二次預覽,并在Java17最終確定下來。這個預覽功能用于限制超類的使用,密封的類和接口限制其他可能繼承或實現(xiàn)它們的其他類或接口。

語法格式如下:

【修飾劑】 sealed class 密封類 【extends 父類】【implements 父接口】 permits 子類{
    
}
【修飾劑】 sealed interface 接口 【extends 父接口們】 permits 實現(xiàn)類{
    
}

相關特點如下:

  • 密封類用 sealed 修飾符來描述,
  • 使用 permits 關鍵字來指定可以繼承或實現(xiàn)該類的類型有哪些
  • 一個類繼承密封類或實現(xiàn)密封接口,該類必須是sealed、non-sealed、final修飾的。
  • sealed修飾的類或接口必須有子類或實現(xiàn)類
package com.atguigu.sealed;
sealed class Graphic  permits Circle,Rectangle, Triangle {
}
final class Triangle extends Graphic{
}
non-sealed class Circle extends Graphic{
}
sealed class Rectangle extends Graphic permits Square{
}
final class Square extends Rectangle{
    
}

7.6 代碼塊(了解)

代碼塊分為靜態(tài)代碼塊和非靜態(tài)代碼塊。

7.6.1 靜態(tài)代碼塊與類初始化

靜態(tài)代碼塊又稱為類初始化塊,用于在類初始化時給靜態(tài)變量初始化。

執(zhí)行特點:每一個類的靜態(tài)代碼塊只會執(zhí)行1次,并且在類被加載時執(zhí)行。

語法格式:

【修飾符】 class 類{
    static{
        //靜態(tài)代碼塊
    }
}

7.6.2 非靜態(tài)代碼塊與實例初始化

非靜態(tài)代碼塊又稱為實例初始化塊,或者構造塊,用于在創(chuàng)建對象時給實例變量初始化。

執(zhí)行特點:每次new對象時,并且一定是先于本類構造器除了super()或super(實參列表)以外的所有代碼執(zhí)行。

語法格式:

【修飾符】 class 類{
    {
        //非靜態(tài)代碼塊
    }
}