Java面向?qū)ο蠡A(chǔ)(下)

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

7.1 多態(tài)

7.1.1 什么是多態(tài)

多態(tài)是繼封裝、繼承之后,面向?qū)ο蟮牡谌筇匦浴?/p>

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

7.1.2 動(dòng)態(tài)多態(tài)性

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

  • 子類(lèi)繼承父類(lèi)或?qū)崿F(xiàn)接口
  • 方法重寫(xiě)
  • 多態(tài)引用

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

Java規(guī)定父類(lèi)型的變量可以接收子類(lèi)類(lèi)型的對(duì)象,這一點(diǎn)從邏輯上也是說(shuō)得通的。

父類(lèi)類(lèi)型 變量名 = 子類(lèi)對(duì)象; 
父接口類(lèi)型 變量名 = 實(shí)現(xiàn)類(lèi)對(duì)象; 

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

多態(tài)引用后調(diào)用方法的表現(xiàn)是:編譯時(shí)看左邊,運(yùn)行時(shí)看右邊。這就是Java虛方法的動(dòng)態(tài)綁定機(jī)制。所謂虛方法,就是可以被子類(lèi)/實(shí)現(xiàn)類(lèi)重寫(xiě)的方法。

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("貓咪吃魚(yú)仔");
    }
}
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();//不能調(diào)用子類(lèi)擴(kuò)展的方法
        p = new Cat();
        p.eat();
        p = new Pig();
        p.eat();
    }
}
運(yùn)行結(jié)果:
狗狗啃骨頭
貓咪吃魚(yú)仔
吃東西

3、如何獲取對(duì)象的運(yùn)行時(shí)類(lèi)型呢?

java.lang.Object類(lèi)中有一個(gè)方法可以獲取對(duì)象的運(yùn)行時(shí)類(lèi)型:

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

7.1.3 多態(tài)引用的應(yīng)用場(chǎng)景

1、聲明變量是父類(lèi)類(lèi)型,變量賦值子類(lèi)對(duì)象

- 方法的形參是父類(lèi)類(lèi)型,調(diào)用方法的實(shí)參是子類(lèi)對(duì)象

- 成員變量聲明父類(lèi)類(lèi)型,實(shí)際存儲(chǔ)的是子類(lèi)對(duì)象

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("貓咪吃魚(yú)仔");
    }
    @Override
    public String toString() {
        return "Cat{"+getNickname()+"}";
    }
}
package com.atguigu.polymorphism.grammar;
public class Owner {
    private String name;
    private Pet pet;//成員變量聲明為父類(lèi)類(lèi)型,實(shí)際賦值的是子類(lèi)對(duì)象
    
    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) {//方法形參聲明為父類(lèi)類(lèi)型,實(shí)參是子類(lèi)對(duì)象
        this.pet = pet;
    }
    //feed:喂食
    public void feed() {
        if (pet!= null) {
            //這里eat()執(zhí)行哪個(gè)類(lèi)的eat()方法,是根據(jù)pet對(duì)象的運(yùn)行時(shí)類(lèi)型動(dòng)態(tài)綁定的
            pet.eat();
        }
    }
    @Override
    public String toString() {
        return "Owner{" +
                "name='" + name + '\'' +
                ", pet=" + pet +
                //這里+pet,會(huì)自動(dòng)調(diào)用pet.toString(),至于執(zhí)行哪個(gè)類(lèi)的toString方法,也是根據(jù)pet對(duì)象的運(yùn)行時(shí)類(lèi)型動(dòng)態(tài)綁定的
                '}';
    }
}
package com.atguigu.polymorphism.grammar;
public class TestPersonPet {
    public static void main(String[] args) {
        Owner zhang = new Owner("張三");
        zhang.setPet(new Dog("旺財(cái)"));
        zhang.feed();
        System.out.println(zhang);
        
        
        Owner li = new Owner("李四");      
        li.setPet(new Cat("雪球"));
        li.feed();
        System.out.println(li);
    }
}
運(yùn)行結(jié)果:
狗狗啃骨頭
Owner{name='張三', pet=Dog{旺財(cái)}}
貓咪吃魚(yú)仔
Owner{name='李四', pet=Cat{雪球}}

2、數(shù)組元素是父類(lèi)類(lèi)型,元素對(duì)象是子類(lèi)對(duì)象

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];
        //數(shù)組的元素類(lèi)型聲明為父類(lèi)類(lèi)型,實(shí)際存儲(chǔ)的是子類(lèi)對(duì)象
        pets[0] = new Dog("旺財(cái)");
        pets[1] = new Cat("雪球");
        p.setPets(pets);
        p.feed();
        System.out.println(p);
    }
}

3、方法返回值類(lèi)型聲明為父類(lèi)類(lèi)型,實(shí)際返回的是子類(lèi)對(duì)象

package com.atguigu.polymorphism.grammar;
public class PetShop {
    //返回值類(lèi)型是父類(lèi)類(lèi)型,實(shí)際返回的是子類(lèi)對(duì)象
    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的編譯時(shí)類(lèi)型是Pet,運(yùn)行時(shí)類(lèi)型是Dog
         */
        p1.setNickname("小白");
        System.out.println(p1);//自動(dòng)調(diào)用Dog類(lèi)的toString方法
        Pet p2 = shop.sale("Cat");
        /*
        p2的編譯時(shí)類(lèi)型是Pet,運(yùn)行時(shí)類(lèi)型是Cat
         */
        p2.setNickname("雪球");
        System.out.println(p2);//自動(dòng)調(diào)用Cat類(lèi)的toString方法
    }
}

7.1.4 向上轉(zhuǎn)型與向下轉(zhuǎn)型

1、向上轉(zhuǎn)型

(1)什么是向上轉(zhuǎn)型?

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

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

- 當(dāng)把子類(lèi)對(duì)象賦值給父類(lèi)的變量時(shí),此時(shí)通過(guò)這個(gè)父類(lèi)變量引用它時(shí),這個(gè)子類(lèi)對(duì)象就呈現(xiàn)為“父類(lèi)的類(lèi)型”,

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

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

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

2、向下轉(zhuǎn)型

(1)什么是向下轉(zhuǎn)型?

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

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

- 必須使用強(qiáng)制類(lèi)型轉(zhuǎn)換的語(yǔ)法

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

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

為了使用子類(lèi)擴(kuò)展的成員。

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

3、注意問(wèn)題

(1)無(wú)論向上還是向下,都只發(fā)生在編譯時(shí),對(duì)象的運(yùn)行時(shí)類(lèi)型不會(huì)變

首先,一個(gè)對(duì)象在new的時(shí)候創(chuàng)建的是哪個(gè)類(lèi)型的對(duì)象,它從頭至尾都不會(huì)變。即這個(gè)對(duì)象的運(yùn)行時(shí)類(lèi)型,本質(zhì)的類(lèi)型不會(huì)變。但是,把這個(gè)對(duì)象賦值給不同類(lèi)型的變量時(shí),這些變量的編譯時(shí)類(lèi)型卻不同。

這個(gè)和基本數(shù)據(jù)類(lèi)型的轉(zhuǎn)換是不同的?;緮?shù)據(jù)類(lèi)型是把數(shù)據(jù)值copy了一份,相當(dāng)于有兩種數(shù)據(jù)類(lèi)型的值。而對(duì)象的賦值不會(huì)產(chǎn)生兩個(gè)對(duì)象。

(2)向上轉(zhuǎn)型和向下轉(zhuǎn)型只支持父子類(lèi)或父接口與實(shí)現(xiàn)類(lèi)之間。

Dog dog = "hello";//錯(cuò)誤,不能向上也不能向下,不是父子類(lèi)之間

(3)向上轉(zhuǎn)型向下轉(zhuǎn)型操作編譯通過(guò),運(yùn)行時(shí)不一定通過(guò)

不是所有通過(guò)編譯的轉(zhuǎn)型轉(zhuǎn)換都是正確的,可能會(huì)發(fā)生ClassCastException

package com.atguigu.polymorphism.grammar;
public class ClassCastTest {
    public static void main(String[] args) {
        //沒(méi)有類(lèi)型轉(zhuǎn)換
        Dog dog = new Dog();//dog的編譯時(shí)類(lèi)型和運(yùn)行時(shí)類(lèi)型都是Dog
        //向上轉(zhuǎn)型
        Pet pet = new Dog("小白");//pet的編譯時(shí)類(lèi)型是Pet,運(yùn)行時(shí)類(lèi)型是Dog
        pet.eat();//可以調(diào)用父類(lèi)Pet有聲明的方法eat,但執(zhí)行的是子類(lèi)重寫(xiě)的eat方法體
//        pet.watchHouse();//不能調(diào)用父類(lèi)沒(méi)有的方法watchHouse
        Dog d = (Dog) pet;
        d.eat();//可以調(diào)用eat方法
        d.watchHouse();//可以調(diào)用子類(lèi)擴(kuò)展的方法watchHouse
        Cat c = (Cat) pet;//編譯通過(guò),因?yàn)閺恼Z(yǔ)法檢查來(lái)說(shuō),pet的編譯時(shí)類(lèi)型是Pet,Cat是Pet的子類(lèi),所以向下轉(zhuǎn)型語(yǔ)法正確
        //這句代碼運(yùn)行報(bào)錯(cuò)ClassCastException,因?yàn)閜et變量的運(yùn)行時(shí)類(lèi)型是Dog,Dog和Cat之間是沒(méi)有繼承關(guān)系的
    }
}

4、instanceof關(guān)鍵字

為了避免ClassCastException的發(fā)生,Java提供了 `instanceof` 關(guān)鍵字,用來(lái)判斷對(duì)象的類(lèi)型關(guān)系,只要用instanceof判斷返回true的,那么強(qiáng)轉(zhuǎn)為該類(lèi)型就一定是安全的,不會(huì)報(bào)ClassCastException異常。

變量 instanceof 數(shù)據(jù)類(lèi)型 

那么,哪些instanceof判斷會(huì)返回true呢?

- 對(duì)象的編譯時(shí)類(lèi)型 與  instanceof后面數(shù)據(jù)類(lèi)型是 父子類(lèi)關(guān)系才編譯通過(guò)

- 對(duì)象的運(yùn)行時(shí)類(lèi)型<= instanceof后面數(shù)據(jù)類(lèi)型,運(yùn)行結(jié)果才為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中預(yù)覽,在JDK16中轉(zhuǎn)正。有了它就不需要編寫(xiě)先通過(guò)instanceof判斷再?gòu)?qiáng)制轉(zhuǎn)換的代碼。

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 經(jīng)典面試題

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

在Java中虛方法是指在編譯階段和類(lèi)加載階段都不能確定方法的調(diào)用入口地址,在運(yùn)行階段才能確定的方法,即可能被重寫(xiě)的方法。

當(dāng)我們通過(guò)“對(duì)象.方法”的形式調(diào)用一個(gè)虛方法時(shí),要如何確定它具體執(zhí)行哪個(gè)方法呢?

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

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

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

(2)運(yùn)行時(shí)動(dòng)態(tài)綁定:再看這個(gè)對(duì)象xx的運(yùn)行時(shí)類(lèi)型,如果這個(gè)對(duì)象xx的運(yùn)行時(shí)類(lèi)重寫(xiě)了剛剛找到的那個(gè)匹配的方法,那么執(zhí)行重寫(xiě)的,否則仍然執(zhí)行剛才編譯時(shí)類(lèi)型中的那個(gè)匹配的方法

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的編譯時(shí)類(lèi)型MyClass,在MyClass中找最匹配的
                匹配的原則:看實(shí)參的編譯時(shí)類(lèi)型與方法形參的類(lèi)型的匹配程度
                 A:找最匹配    實(shí)參的編譯時(shí)類(lèi)型 = 方法形參的類(lèi)型
                 B:找兼容      實(shí)參的編譯時(shí)類(lèi)型 < 方法形參的類(lèi)型
                 實(shí)參f的編譯時(shí)類(lèi)型是Father,形參(Father f) 、(Son s)
                 最匹配的是public void method(Father f)
            (2)動(dòng)態(tài)綁定:看my的運(yùn)行時(shí)類(lèi)型MySub,看在MySub中是否有對(duì)    public void method(Father f)進(jìn)行重寫(xiě)
                發(fā)現(xiàn)有重寫(xiě),如果有重寫(xiě),就執(zhí)行重寫(xiě)的
                    public void method(Father d) {
                        System.out.println("sub--");
                    }
             */
        my.method(s);//son
            /*
            (1)靜態(tài)分派:看my的編譯時(shí)類(lèi)型MyClass,在MyClass中找最匹配的
                匹配的原則:看實(shí)參的編譯時(shí)類(lèi)型與方法形參的類(lèi)型的匹配程度
                 A:找最匹配    實(shí)參的編譯時(shí)類(lèi)型 = 方法形參的類(lèi)型
                 B:找兼容      實(shí)參的編譯時(shí)類(lèi)型 < 方法形參的類(lèi)型
                 實(shí)參s的編譯時(shí)類(lèi)型是Son,形參(Father f) 、(Son s)
                 最匹配的是public void method(Son s)
            (2)動(dòng)態(tài)綁定:看my的運(yùn)行時(shí)類(lèi)型MySub,看在MySub中是否有對(duì) public void method(Son s)進(jìn)行重寫(xiě)
                發(fā)現(xiàn)沒(méi)有重寫(xiě),如果沒(méi)有重寫(xiě),就執(zhí)行剛剛父類(lèi)中找到的方法
             */
        my.method(d);//sub--father
             /*
            (1)靜態(tài)分派:看my的編譯時(shí)類(lèi)型MyClass,在MyClass中找最匹配的
                匹配的原則:看實(shí)參的編譯時(shí)類(lèi)型與方法形參的類(lèi)型的匹配程度
                 A:找最匹配    實(shí)參的編譯時(shí)類(lèi)型 = 方法形參的類(lèi)型
                 B:找兼容      實(shí)參的編譯時(shí)類(lèi)型 < 方法形參的類(lèi)型
                 實(shí)參d的編譯時(shí)類(lèi)型是Daughter,形參(Father f) 、(Son s)
                 最匹配的是public void method(Father f)
            (2)動(dòng)態(tài)綁定:看my的運(yùn)行時(shí)類(lèi)型MySub,看在MySub中是否有對(duì) public void method(Father f)進(jìn)行重寫(xiě)
                發(fā)現(xiàn)有重寫(xiě),如果有重寫(xiě),就執(zhí)行重寫(xiě)的
                    public void method(Father d) {
                        System.out.println("sub--");
                    }
             */
    }
}

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

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);//錯(cuò)誤
        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);//錯(cuò)誤
    }
}


7.2 內(nèi)部類(lèi)

7.2.1 概述

1、什么是內(nèi)部類(lèi)?

將一個(gè)類(lèi)A定義在另一個(gè)類(lèi)B里面,里面的那個(gè)類(lèi)A就稱(chēng)為**內(nèi)部類(lèi)**,B則稱(chēng)為**外部類(lèi)**。

2、為什么要聲明內(nèi)部類(lèi)呢?

總的來(lái)說(shuō),遵循高內(nèi)聚低耦合的面向?qū)ο箝_(kāi)發(fā)總原則。便于代碼維護(hù)和擴(kuò)展。

3、內(nèi)部類(lèi)都有哪些形式?

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

  • 成員內(nèi)部類(lèi):        

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

       

  • 局部?jī)?nèi)部類(lèi):        

    • 有名字的局部?jī)?nèi)部類(lèi)
    • 匿名的內(nèi)部類(lèi)

       

7.2.2 成員內(nèi)部類(lèi)

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

語(yǔ)法格式:

【修飾符】 class 外部類(lèi){
    【其他修飾符】 【static】 class 內(nèi)部類(lèi){
    }
}

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

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

  • 和其他類(lèi)一樣,它只是定義在外部類(lèi)中的另一個(gè)完整的類(lèi)結(jié)構(gòu)        

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

       

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

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

       

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

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

       

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

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

       

其實(shí)嚴(yán)格的講(在James Gosling等人編著的《The Java Language Specification》書(shū)中)靜態(tài)內(nèi)部類(lèi)不是內(nèi)部類(lèi),而是類(lèi)似于C++的嵌套類(lèi)的概念,外部類(lèi)僅僅是靜態(tài)內(nèi)部類(lèi)的一種命名空間的限定名形式而已。所以接口中的內(nèi)部類(lèi)通常都不叫內(nèi)部類(lèi),因?yàn)榻涌谥械膬?nè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 = "外部類(lèi)的靜態(tài)a";
    private static String b  = "外部類(lèi)的靜態(tài)b";
    private String c = "外部類(lèi)對(duì)象的非靜態(tài)c";
    private String d = "外部類(lèi)對(duì)象的非靜態(tài)d";
    static class Inner{
        private static String a ="靜態(tài)內(nèi)部類(lèi)的靜態(tài)a";
        private String c = "靜態(tài)內(nèi)部類(lèi)對(duì)象的非靜態(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對(duì)象.c = " + Outer.c + "," + Outer.this.c);//不能訪問(wèn)外部類(lèi)的非靜態(tài)成員
//            System.out.println("Outer對(duì)象.d = " + Outer.d + "," + Outer.this.d);//不能訪問(wèn)外部類(lèi)的非靜態(tài)成員
            System.out.println("Inner.a = " + a);
            //System.out.println("Inner對(duì)象.c = " + c);//不能訪問(wèn)自己的非靜態(tài)成員
        }
        public void inFun(){
            System.out.println("Inner對(duì)象.inFun");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("Outer.b = " + b);
//            System.out.println("Outer對(duì)象.c = " + Outer.c + "," + Outer.this.c);//不能訪問(wèn)外部類(lèi)的非靜態(tài)成員
//            System.out.println("Outer對(duì)象.d = " + Outer.d + "," + Outer.this.d);//不能訪問(wèn)外部類(lèi)的非靜態(tài)成員
            System.out.println("Inner.a = " + a);
            System.out.println("Inner對(duì)象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對(duì)象.c = " + c);
//        System.out.println("Outer對(duì)象.d = " + d);
        System.out.println("Inner.a = " + Inner.a);
        Inner in = new Inner();
        System.out.println("Inner對(duì)象.c = " + in.c);
    }
    public void outFun(){
        System.out.println("Outer對(duì)象.outFun");
        System.out.println("Outer.a = " + a);
        System.out.println("Outer.b = " + b);
        System.out.println("Outer對(duì)象.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)成員內(nèi)部類(lèi)

沒(méi)有static修飾的成員內(nèi)部類(lèi)叫做非靜態(tài)內(nèi)部類(lèi)。非靜態(tài)內(nèi)部類(lèi)的特點(diǎn):

  • 和其他類(lèi)一樣,它只是定義在外部類(lèi)中的另一個(gè)完整的類(lèi)結(jié)構(gòu)        

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

       

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

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

       

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

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

       

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

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

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

                 

       

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 = "外部類(lèi)的靜態(tài)a";
    private static String b  = "外部類(lèi)的靜態(tài)b";
    private String c = "外部類(lèi)對(duì)象的非靜態(tài)c";
    private String d = "外部類(lèi)對(duì)象的非靜態(tài)d";
    class Inner{
        private String a = "非靜態(tài)內(nèi)部類(lèi)對(duì)象的非靜態(tài)a";
        private String c = "非靜態(tài)內(nèi)部類(lèi)對(duì)象的非靜態(tài)c";
        public static final String s = "非靜態(tài)內(nèi)部類(lèi)的靜態(tài)常量";
        public void inFun(){
            System.out.println("Inner對(duì)象.inFun");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("Outer.b = " + b);
            System.out.println("Outer對(duì)象.c = " + Outer.this.c);
            System.out.println("Outer.objects.d = " + d);
            System.out.println("Inner對(duì)象.a = " + a);
            System.out.println("Inner對(duì)象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對(duì)象.c = " + c);
//        System.out.println("Outer.objects.d = " + d);
//        Inner in = new Inner();//此處無(wú)法使用非靜態(tài)內(nèi)部類(lè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 局部?jī)?nèi)部類(lèi)

1、局部?jī)?nèi)部類(lèi)

語(yǔ)法格式:

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

局部?jī)?nèi)部類(lèi)的特點(diǎn):

  • 和外部類(lèi)一樣,它只是定義在外部類(lèi)的某個(gè)方法中的另一個(gè)完整的類(lèi)結(jié)構(gòu)        

    • 可以繼承自己的想要繼承的父類(lèi),實(shí)現(xiàn)自己想要實(shí)現(xiàn)的父接口們,和外部類(lèi)的父類(lèi)和父接口無(wú)關(guān)
    • 可以在局部?jī)?nèi)部類(lèi)中聲明屬性、方法、構(gòu)造器等結(jié)構(gòu),JDK16之后**也允許聲明靜態(tài)成員**            
    • 編譯后有自己的獨(dú)立的字節(jié)碼文件:外部類(lèi)名$數(shù)字編號(hào)局部?jī)?nèi)部類(lèi)名.class。                
    • 這里有編號(hào)是因?yàn)橥粋€(gè)外部類(lèi)中,不同的方法中存在相同名稱(chēng)的局部?jī)?nèi)部類(lèi)

       

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

    • JDK1.8之后,如果某個(gè)局部變量在局部?jī)?nèi)部類(lèi)中被使用了,自動(dòng)加final
    • 為什么在局部?jī)?nèi)部類(lèi)中使用外部類(lèi)方法的局部變量要加final呢?考慮生命周期問(wèn)題。

       

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("===========================");
        //這里不能使用局部?jī)?nèi)部類(lèi)
//        Outer.Inner in;
        //但是這里可以獲取局部?jī)?nèi)部類(lèi)的對(duì)象,只能通過(guò)多態(tài)引用該局部?jī)?nèi)部類(lèi)的對(duì)象
        Father f = Outer.outTest();
        f.m();
    }
}
class Outer{
    private static String a = "外部類(lèi)的靜態(tài)a";
    private static String b  = "外部類(lèi)的靜態(tài)b";
    private String c = "外部類(lèi)對(duì)象的非靜態(tài)c";
    private String d = "外部類(lèi)對(duì)象的非靜態(tài)d";
    public static void outMethod(){
        class Inner{
            private String a = "非靜態(tài)內(nèi)部類(lèi)1對(duì)象的非靜態(tài)a";
            private String c = "非靜態(tài)內(nèi)部類(lèi)1對(duì)象的非靜態(tài)c";
            public static final String s = "非靜態(tài)內(nèi)部類(lèi)1的靜態(tài)常量";
            public void inFun(){
                System.out.println("Inner對(duì)象.inFun");
                System.out.println("Outer.a = " + Outer.a);
                System.out.println("Outer.b = " + b);
//                System.out.println("Outer對(duì)象.c = " + Outer.this.c);
//                System.out.println("Outer.objects.d = " + d);
                System.out.println("Inner1對(duì)象.a = " + a);
                System.out.println("Inner1對(duì)象c = " + c);
                System.out.println("Inner1.s = " + Inner.s);
            }
        }
        new Inner().inFun();
    }
    public void outFun(){
        class Inner{
            private String a = "非靜態(tài)內(nèi)部類(lèi)2對(duì)象的非靜態(tài)a";
            private String c = "非靜態(tài)內(nèi)部類(lèi)2對(duì)象的非靜態(tài)c";
            public static final String s = "非靜態(tài)內(nèi)部類(lèi)2的靜態(tài)常量";
            public void inFun(){
                System.out.println("Inner2對(duì)象.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對(duì)象.a = " + a);
                System.out.println("Inner2對(duì)象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("局部?jī)?nèi)部類(lèi)重寫(xiě)父類(lèi)方法");
                System.out.println("局部?jī)?nèi)部類(lèi)使用外部類(lèi)的局部變量outA = " + outA);
            }
        }
        return new Inner();
    }
    public static void outOtherMethod(){
//        Inner in = new Inner();//局部?jī)?nèi)部類(lèi)有作用域范圍限制
    }
}
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);
            }
        }
    }
}


如果外部類(lèi)局部變量的值是確定的常量值,那么編譯器直接在局部?jī)?nèi)部類(lèi)中就相當(dāng)于使用了一個(gè)確定常量值;

如果外部類(lèi)局部變量的值是待確定的常量值,那么編譯器會(huì)在局部?jī)?nèi)部類(lèi)中用一個(gè)隱式的成員變量接收該局部變量的值,當(dāng)局部?jī)?nèi)部類(lèi)對(duì)象創(chuàng)建時(shí)自動(dòng)完成賦值,即局部?jī)?nèi)部類(lèi)中使用的已經(jīng)不再是外部類(lèi)的局部變量了,而是自己的一個(gè)成員變量;

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、匿名內(nèi)部類(lèi)

當(dāng)我們?cè)陂_(kāi)發(fā)過(guò)程中,需要用到一個(gè)抽象類(lèi)的子類(lèi)的對(duì)象或一個(gè)接口的實(shí)現(xiàn)類(lèi)的對(duì)象,而且只創(chuàng)建一個(gè)對(duì)象,而且邏輯代碼也不復(fù)雜。那么我們?cè)仍趺醋龅哪兀?/p>

  • (1)編寫(xiě)類(lèi),繼承這個(gè)父類(lèi)或?qū)崿F(xiàn)這個(gè)接口
  • (2)重寫(xiě)父類(lèi)或父接口的方法
  • (3)創(chuàng)建這個(gè)子類(lèi)或?qū)崿F(xiàn)類(lèi)的對(duì)象

這里,因?yàn)榭紤]到這個(gè)子類(lèi)或?qū)崿F(xiàn)類(lèi)是一次性的,那么我們“費(fèi)盡心機(jī)”的給它取名字,就顯得多余。那么我們完全可以使用匿名內(nèi)部類(lèi)的方式來(lái)實(shí)現(xiàn),避免給類(lèi)命名的問(wèn)題。

new 父類(lèi)(【實(shí)參列表】){
    重寫(xiě)方法...
}
//()中是否需要【實(shí)參列表】,看你想要讓這個(gè)匿名內(nèi)部類(lèi)調(diào)用父類(lèi)的哪個(gè)構(gòu)造器,如果調(diào)用父類(lèi)的無(wú)參構(gòu)造,那么()中就不用寫(xiě)參數(shù),如果調(diào)用父類(lèi)的有參構(gòu)造,那么()中需要傳入實(shí)參
new 父接口(){
    重寫(xiě)方法...
}
//()中沒(méi)有參數(shù),因?yàn)榇藭r(shí)匿名內(nèi)部類(lèi)的父類(lèi)是Object類(lèi),它只有一個(gè)無(wú)參構(gòu)造

注意:

匿名內(nèi)部類(lèi)沒(méi)有名字,因此字節(jié)碼文件名是外部類(lèi)名$編號(hào).class。

匿名內(nèi)部類(lèi)是一種特殊的局部?jī)?nèi)部類(lèi),只不過(guò)沒(méi)有名稱(chēng)而已。所有局部?jī)?nèi)部類(lèi)的限制都適用于匿名內(nèi)部類(lèi)。例如:

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

思考:這個(gè)對(duì)象能做什么呢?

(1)使用匿名內(nèi)部類(lèi)的對(duì)象直接調(diào)用方法
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)通過(guò)父類(lèi)或父接口的變量多態(tài)引用匿名內(nèi)部類(lèi)的對(duì)象
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)匿名內(nèi)部類(lèi)的對(duì)象作為實(shí)參
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 幾種內(nèi)部類(lèi)對(duì)比

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

7.3 枚舉

7.3.1 概述

某些類(lèi)型的對(duì)象是有限的幾個(gè),這樣的例子舉不勝舉:

  • 星期: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(已確認(rèn)收貨)、Return(退貨)、Exchange(換貨)、Cancel(取消)

枚舉類(lèi)型本質(zhì)上也是一種類(lèi),只不過(guò)是這個(gè)類(lèi)的對(duì)象是固定的幾個(gè),而不能隨意讓用戶(hù)創(chuàng)建。

在JDK1.5之前,需要程序員自己通過(guò)特殊的方式來(lái)定義枚舉類(lèi)型。

在JDK1.5之后,Java支持enum關(guān)鍵字來(lái)快速的定義枚舉類(lèi)型。

7.3.2 JDK1.5之前

在JDK1.5之前如何聲明枚舉類(lèi)呢?

  • 構(gòu)造器加private私有化
  • 本類(lèi)內(nèi)部創(chuàng)建一組常量對(duì)象,并添加public static修飾符,對(duì)外暴露這些常量對(duì)象
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關(guān)鍵字聲明枚舉

語(yǔ)法格式:

【修飾符】 enum 枚舉類(lèi)名{
    常量對(duì)象列表
}
【修飾符】 enum 枚舉類(lèi)名{
    常量對(duì)象列表;
    
    其他成員列表;
}
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、枚舉類(lèi)的要求和特點(diǎn)

枚舉類(lèi)的要求和特點(diǎn):

  • 枚舉類(lèi)的常量對(duì)象列表必須在枚舉類(lèi)的首行,因?yàn)槭浅A?,所以建議大寫(xiě)。
  • 如果常量對(duì)象列表后面沒(méi)有其他代碼,那么“;”可以省略,否則不可以省略“;”。
  • 編譯器給枚舉類(lèi)默認(rèn)提供的是private的無(wú)參構(gòu)造,如果枚舉類(lèi)需要的是無(wú)參構(gòu)造,就不需要聲明,寫(xiě)常量對(duì)象列表時(shí)也不用加參數(shù),
  • 如果枚舉類(lèi)需要的是有參構(gòu)造,需要手動(dòng)定義,有參構(gòu)造的private可以省略,調(diào)用有參構(gòu)造的方法就是在常量對(duì)象名后面加(實(shí)參列表)就可以。
  • 枚舉類(lèi)默認(rèn)繼承的是java.lang.Enum類(lèi),因此不能再繼承其他的類(lèi)型。
  • JDK1.5之后switch,提供支持枚舉類(lèi)型,case后面可以寫(xiě)枚舉常量名。
  • 枚舉類(lèi)型如有其它屬性,建議(**不是必須**)這些屬性也聲明為final的,因?yàn)槌A繉?duì)象在邏輯意義上應(yīng)該不可變。
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("進(jìn)入學(xué)習(xí)狀態(tài)");break;
            case WEDNESDAY:
                System.out.println("死撐");break;
            case THURSDAY:
                System.out.println("小放松");break;
            case FRIDAY:
                System.out.println("又信心滿(mǎn)滿(mǎn)");break;
            case SATURDAY:
                System.out.println("開(kāi)始盼周末,無(wú)心學(xué)習(xí)");break;
            case SUNDAY:
                System.out.println("一覺(jué)到下午");break;
        }
    }
}

3、枚舉類(lèi)型常用方法

常用方法如下:

  • String toString(): 默認(rèn)返回的是常量名(對(duì)象名),可以繼續(xù)手動(dòng)重寫(xiě)該方法!
  • String name():返回的是常量名(對(duì)象名)
  • int ordinal():返回常量的次序號(hào),默認(rèn)從0開(kāi)始
  • 枚舉類(lèi)型[] values():返回該枚舉類(lèi)的所有的常量對(duì)象,返回類(lèi)型是當(dāng)前枚舉的數(shù)組類(lèi)型,是一個(gè)靜態(tài)方法
  • 枚舉類(lèi)型 valueOf(String name):根據(jù)枚舉常量對(duì)象名稱(chēng)獲取枚舉對(duì)象
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("請(qǐng)輸入星期值:");
        int weekValue = input.nextInt();
        Week week = values[weekValue-1];
        System.out.println(week);
        System.out.print("請(qǐng)輸入星期名:");
        String weekName =曦input.next();
        week = Week.valueOf(weekName);
        System.out.println(week);
        input.close();
    }
}

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

Record類(lèi)在JDK14、15預(yù)覽特性,在JDK16中轉(zhuǎn)正。

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

1.注意:

  • Record只會(huì)有一個(gè)全參構(gòu)造
  • 重寫(xiě)的equals方法比較所有屬性值
  • 可以在Record聲明的類(lèi)中定義靜態(tài)字段、靜態(tài)方法或?qū)嵗椒ā?/li>
  • 不能在Record聲明的類(lèi)中定義實(shí)例字段;
  • 不能顯式的聲明父類(lèi),默認(rèn)父類(lèi)是java.lang.Record類(lèi)
  • 因?yàn)镽ecord類(lèi)是一個(gè) final類(lèi),所以也沒(méi)有子類(lèi)等。因此rRecord類(lèi)不能聲明為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("周長(zhǎng):" + t.perimeter());
        System.out.println("邊長(zhǎng):" + 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 新特性:密封類(lèi)(了解)

其實(shí)很多語(yǔ)言中都有`密封類(lèi)`的概念,在Java語(yǔ)言中,也早就有`密封類(lèi)`的思想,就是final修飾的類(lèi),該類(lèi)不允許被繼承。而從JDK15開(kāi)始,針對(duì)`密封類(lèi)`進(jìn)行了升級(jí)。

Java 15通過(guò)密封的類(lèi)和接口來(lái)增強(qiáng)Java編程語(yǔ)言,這是新引入的預(yù)覽功能并在Java 16中進(jìn)行了二次預(yù)覽,并在Java17最終確定下來(lái)。這個(gè)預(yù)覽功能用于限制超類(lèi)的使用,密封的類(lèi)和接口限制其他可能繼承或?qū)崿F(xiàn)它們的其他類(lèi)或接口。

語(yǔ)法格式如下:

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

相關(guān)特點(diǎn)如下:

  • 密封類(lèi)用 sealed 修飾符來(lái)描述,
  • 使用 permits 關(guān)鍵字來(lái)指定可以繼承或?qū)崿F(xiàn)該類(lèi)的類(lèi)型有哪些
  • 一個(gè)類(lèi)繼承密封類(lèi)或?qū)崿F(xiàn)密封接口,該類(lèi)必須是sealed、non-sealed、final修飾的。
  • sealed修飾的類(lèi)或接口必須有子類(lèi)或?qū)崿F(xiàn)類(lèi)
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)代碼塊與類(lèi)初始化

靜態(tài)代碼塊又稱(chēng)為類(lèi)初始化塊,用于在類(lèi)初始化時(shí)給靜態(tài)變量初始化。

執(zhí)行特點(diǎn):每一個(gè)類(lèi)的靜態(tài)代碼塊只會(huì)執(zhí)行1次,并且在類(lèi)被加載時(shí)執(zhí)行。

語(yǔ)法格式:

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

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

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

執(zhí)行特點(diǎn):每次new對(duì)象時(shí),并且一定是先于本類(lèi)構(gòu)造器除了super()或super(實(shí)參列表)以外的所有代碼執(zhí)行。

語(yǔ)法格式:

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