Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)

文章目录

  • 一、 概述
    • (1) 介绍
      • 1. 什么是内部类
      • 2. 为什么要声明内部类
      • 3. 内部类使用举例
      • 4. 内部类的分类
    • (2)举例
    • (3)重点知识
      • 1. 对成员内部类的理解
      • 2. 创建成员内部类的实例
        • 2.1 静态成员内部类
        • 2.2 非静态成员内部类
      • 3. 在成员内部类中调用外部类的结构
        • 3.1 非静态成员内部类的方法里面调用属性
        • 3.2 非静态成员内部类调用方法
        • 3.3 字节码文件
      • 4. 局部内部类的使用
        • 4.1 介绍
        • 4.2 开发中
        • 4.3 字节码文件
        • 4.4 再举例
  • 二、 成员内部类
    • (1)概述
    • (2) 创建成员内部类对象
    • (3)举例
  • 三、 局部内部类
    • (1)非匿名局部内部类
    • (2) 匿名内部类
  • 四、练习
    • (1)练习1
    • (2)练习2
      • 1. 接口
      • 2. 抽象类
      • 3. 代码

一、 概述

面向对象三条主线:

①类及类的内部成员(属性、方法、构造器;代码块、内部类)

②封装、继承、多态

③关键字

平时开发的时候,自己定义内部类的机会比较少,一般都是源码会使用,我们要知道它怎么用的即可。

(1) 介绍

1. 什么是内部类

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类(InnerClass),类B则称为外部类(OuterClass)

2. 为什么要声明内部类

具体来说,当一个事物A的内部,还有一个部分需要一个完整的结构B(用一个变量不足以刻画它,里面可能还有更丰富的属性、方法)进行描述,而这个内部的完整的结构B又只为外部事物A提供服务,不在其他地方单独使用,那么整个内部的完整结构B最好使用内部类。

平常我们自己设计的时候,可能会将类A与类B设计为并列结构,但是源码会更加规范,它发现类B只在类A里面使用,那就不妨将类B声明在类A里面,表明只在A里面用,那么这个类B就是内部类。

总的来说,遵循高内聚、低耦合(该隐藏的隐藏、该暴露的暴露)的面向对象开发原则。

3. 内部类使用举例

<1> Thread类内部声明了State类(状态),表示线程的生命周期。

<2> HashMap类中声明了Node类,表示封装的key和value。

4. 内部类的分类

根据内部类声明的位置(如同变量的分类),我们可以分为:

  • 成员内部类:直接声明在外部类的里面。(方法、构造器的外面)

    • 使用static修饰的:静态的成员内部类(跟外部类的相关,随着类的加载而加载)
    • 不使用static修饰的:非静态的成员内部类(跟外部类的对象相关,随着对象的创建而加载)
  • 局部内部类:声明在方法内、构造器内、代码块内的内部类

    • 匿名的局部内部类
    • 非匿名的局部内部类

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第1张图片

(2)举例

class Person{   //外部类
    //静态成员内部类
    static class Dog{   //只供Person使用,并不是属于Person的一部分

    }

    //非静态成员内部类
    class Bird{

    }

    //方法
    public void method(){
        //局部内部类
        class InnerClass1{

        }
    }

    //构造器
    public Person(){
        //局部内部类
        class InnerClass2{  //可以与方法method()中的InnerClass1重名

        }
    }

    //代码块
    {
        //局部内部类
        class InnerClass3{

        }
    }

}

(3)重点知识

内部类这节要讲的知识:

  • 成员内部类的理解。
  • 如何创建成员内部类的实例。
  • 如何在成员内部类中调用外部类的结构。
  • 局部内部类的基本使用。

1. 对成员内部类的理解

关于成员内部类的理解:

  • 的角度看:

    • 内部可以声明属性、方法、构造器、代码块、内部类等结构。
    • 此内部类可以声明父类,可以实现接口(单继承,多实现)。
    • 可以使用final修饰(不能有子类)。
    • 可以使用abstract修饰(不能实例化)。
  • 外部类的成员的角度看:

    • 在内部可以调用外部类的结构。比如:属性、方法等
    • 除了使用public缺省权限修饰之外,还可以使用privateprotected修饰。(以前说的类都是外部类,可以使用public、默认权限之外,不能使用private)而成员可以使用四种权限来修饰。
    • 可以使用static修饰。(外部类用static修饰没有意义)

2. 创建成员内部类的实例

2.1 静态成员内部类

创建Person的静态的成员内部类Dog。

public class OuterClassTest {
    public static void main(String[] args) {
        //1.创建Person的静态的成员内部类的实例

    }
}

class Person{   //外部类
    //静态成员内部类
    static class Dog{   //只供Person使用,并不是属于Person的一部分

    }

}

Dog是静态的,随着类的加载而加载,要创建Dog的对象,不需要创建Person的对象。

若直接new一个Dog,又不合适,因为Dog也没有对外暴露。会报错,如下:

image.png

当然要说清楚是谁里面的Dog呀,所以需要这样:new Person.Dog();

那前面声明呢?直接是Dog吗?不行哦,看下面:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第2张图片

同样需要说明是谁里面的Dog,这样:Person.Dog dog=new Person.Dog();

此时Dog里面的方法,就可以调用啦。

比如:

代码

public class OuterClassTest {
    public static void main(String[] args) {
        //1.创建Person的静态的成员内部类的实例
        Person.Dog dog=new Person.Dog();
        dog.eat();
    }
}

class Person{   //外部类
    //1.静态成员内部类
    static class Dog{   //只供Person使用,并不是属于Person的一部分
        public void eat(){
            System.out.println("狗吃骨头");
        }
    }

}

输出结果

image.png

2.2 非静态成员内部类

创建Person的非静态的成员内部类的实例。

非静态的结构都需要依赖外部类的对象,所以要想创建Bird类的对象,就需要先创建外部类Person的对象。

若是像上面那种写法是不行的(因为Bird不是静态的),如下:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第3张图片

需要先创建Person的对象:Person p1=new Person();

接下来的写法很容易写错,这样来写:p1.new Bird();

左边的声明还是和上一个例子一样:Person.Bird bird=p1.new Bird();

注意这样的写法是错误的:

image.png

应该是p1这个对象去new。

此时Bird里面的方法,就可以调用啦。

比如:

代码

public class OuterClassTest {
    public static void main(String[] args) {

        //2.创建Person的非静态的成员内部类的实例
        //Person.Bird bird=new Person.Bird();   //报错
        Person p1=new Person();     //new p1.Bird();  报错
        Person.Bird bird=p1.new Bird();
        bird.eat();

    }
}

class Person{   //外部类

    //2.非静态成员内部类
    class Bird{
        public void eat(){
            System.out.println("鸟吃虫子");
        }
    }

}

输出结果

image.png

3. 在成员内部类中调用外部类的结构

这个其实没什么特别的,只是有的时候会出现重名的属性、方法。

这里就以内部类Bird为例。

3.1 非静态成员内部类的方法里面调用属性

比如在Person里面定义了一个String型的name,还有int型的age。

Bird类里面也定义一个name,还有方法show()。如下:

public class OuterClassTest {
    public static void main(String[] args) {
        //2.创建Person的非静态的成员内部类的实例
        Person.Bird bird=p1.new Bird();
        bird.eat();

    }
}

class Person{   //外部类
    //属性
    String name="Piter";
    int age=1;

    //非静态成员内部类
    class Bird{
        String name="啄木鸟";

        public void eat(){
            System.out.println("鸟吃虫子");
        }

        public void show(){
            System.out.println("age= "+age);
        }
    }

}

可以发现,在Bird类里面可以直接调用外部Person类里面的属性age。说明可以直接来写。

name的调用呢?这里当然就近是自己类里面的啦。

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第4张图片

若是此时show()方法有一个参数String name,那么此时输出语句的name就是形参的name了,如下:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第5张图片

那现在想调用Bird类里面的name咋办?

前面说过,用this去区分形参和环境变量,如下:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第6张图片

这个this指的是Bird还是Person?看this写在哪个方法里面的,谁调用show(),谁就是this。

此时show()是Bird里面的方法,所以this指的是Bird。

那如何去调用Person里面的name呢?

Person类里面的name属性可以这样来写:Person.this.name;

接下来用bird调用show()方法,如下:

代码

package yuyi01;

public class OuterClassTest {
    public static void main(String[] args) {

        //2.创建Person的非静态的成员内部类的实例
        Person.Bird bird=p1.new Bird();
        //bird.eat();

        bird.show("黄鹂");

    }
}

class Person{   //外部类
    //属性
    String name="Piter";
    int age=1;

    //非静态成员内部类
    class Bird{
        String name="啄木鸟";

        public void eat(){
            System.out.println("鸟吃虫子");
        }

        public void show(String name){
            System.out.println("age= "+age);
            System.out.println("name= "+name);
            System.out.println("BirdName= "+this.name); //Bird类里面的name
            System.out.println("PersonName= "+Person.this.name);    //Person类里面的name
        }
    }

}

输出结果

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第7张图片

☕强调

这里再次强调一下show()方法里面的这些调用,如下:

public void show(String name){
    System.out.println("age= "+age);    //直接输出的是外部类的age,与内部类的属性没有冲突,若要补全的话,应该是 Person.this.age
    System.out.println("name= "+name);  //这里的name是形参,传入的是什么输出的就是什么
    System.out.println("BirdName= "+this.name); //Bird类里面的name
    System.out.println("PersonName= "+Person.this.name);    //外部Person类里面的name
}
3.2 非静态成员内部类调用方法

内部类里面能不能调用外部类的方法呢?

比如现在在Person类里面写了一个方法eat(),与内部类Bird中的eat()方法重名,如下:

class Person{   //外部类
    //属性
    String name="Piter";
    int age=1;

    //静态成员内部类
    static class Dog{   //只供Person使用,并不是属于Person的一部分
        public void eat(){
            System.out.println("狗吃骨头");
        }
    }

    //非静态成员内部类
    class Bird{
        String name="啄木鸟";

        public void eat(){
            System.out.println("鸟吃虫子");
        }
        //...
    }

    //举例方法
    public void eat(){
        System.out.println("人吃有营养的食物");
    }

    //...
}

现在在内部类Bird中写一个show1()方法,调用eat()方法,很显然,这里调用的是自己的eat()方法。

如下:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第8张图片

这里明显省略了this.

若此时想调用外部类的eat()方法,就需要这样写:Person.this.eat();

然后在测试类里面测试:bird.show1();

代码

package yuyi01;

public class OuterClassTest {
    public static void main(String[] args) {
        //2.创建Person的非静态的成员内部类的实例
        Person.Bird bird=p1.new Bird();

        bird.show1();

    }
}

class Person{   //外部类
    //属性
    String name="Piter";
    int age=1;

    //静态成员内部类
    static class Dog{   //只供Person使用,并不是属于Person的一部分
        public void eat(){
            System.out.println("狗吃骨头");
        }
    }

    //非静态成员内部类
    class Bird{
        String name="啄木鸟";

        public void eat(){
            System.out.println("鸟吃虫子");
        }

        public void show1(){
            eat();  //Bird中的eat()方法, 即: this.eat();
            this.eat();
            Person.this.eat();  //外部类Person中的eat()方法
        }

    }

    //举例方法
    public void eat(){
        System.out.println("人吃有营养的食物");
    }
}

输出结果

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第9张图片

☕强调

这里再次强调一下show1()方法里面的这些调用,如下:

public void show1(){
    eat();  //Bird中的eat()方法, 即: this.eat();
    this.eat();
    Person.this.eat();  //外部类Person中的eat()方法
}

内部类有能力调用外部类的结构,静态成员内部类不能调用非静态的结构,非静态成员内部类调用外部静态、非静态都可以。

3.3 字节码文件
package yuyi01;

/**
 * ClassName: OuterClassTest
 * Package: yuyi01
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/29 0029 16:15
 */
public class OuterClassTest {
    public static void main(String[] args) {
        //1.创建Person的静态的成员内部类的实例
        Person.Dog dog=new Person.Dog();
        dog.eat();

        //2.创建Person的非静态的成员内部类的实例
        //Person.Bird bird=new Person.Bird();   //报错
        Person p1=new Person();     //new p1.Bird();  报错
        Person.Bird bird=p1.new Bird();
        bird.eat();

        bird.show("黄鹂");

        bird.show1();

    }
}

class Person{   //外部类
    //属性
    String name="Piter";
    int age=1;

    //静态成员内部类
    static class Dog{   //只供Person使用,并不是属于Person的一部分
        public void eat(){
            System.out.println("狗吃骨头");
        }
    }

    //非静态成员内部类
    class Bird{
        String name="啄木鸟";

        public void eat(){
            System.out.println("鸟吃虫子");
        }

        public void show1(){
            eat();  //Bird中的eat()方法, 即: this.eat();
            this.eat();
            Person.this.eat();  //外部类Person中的eat()方法
        }

        public void show(String name){
            System.out.println("age= "+age);    //直接输出的是外部类的age,与内部类的属性没有冲突,若要补全的话,应该是 Person.this.age
            System.out.println("name= "+name);  //这里的name是形参,传入的是什么输出的就是什么
            System.out.println("BirdName= "+this.name); //Bird类里面的name
            System.out.println("PersonName= "+Person.this.name);    //外部Person类里面的name
        }
    }

    //举例方法
    public void eat(){
        System.out.println("人吃有营养的食物");
    }


    //方法
    public void method(){
        //局部内部类
        class InnerClass1{

        }
    }

    //构造器
    public Person(){
        //局部内部类
        class InnerClass2{  //可以与方法method()中的InnerClass1重名

        }
    }

    //代码块
    {
        //局部内部类
        class InnerClass3{

        }
    }

}

编译完之后会生成字节码文件,接口和类都会对应字节码文件

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第10张图片

☕分析

Person类有对应的字节码文件Person.class

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第11张图片

OuterClassTest类也有对应的字节码文件,如下:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第12张图片

Bird类与Dog类是Person类的内部类,对应字节码文件有一个$符:Person$Bird.classPerson$Dog.class(静态与非静态没有区分)。

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第13张图片

③类InnerClass1与类InnerClass2InnerClass3都是局部内部类

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第14张图片

它们前面有1、2、3,因为局部内部类是要放在相应的方法、构造器、代码块里面的,是可以同名的,前面加个标号以示区分。
在文件夹里面也不能出现同名文件啊,通过1、2、3来区分同名的局部内部类。

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第15张图片

大家就熟悉一下,如何表达内部类的字节码文件,通过外部类加一个$符来表达。

4. 局部内部类的使用

4.1 介绍

局部内部类:定义在方法、构造器、代码块中。更多是在方法里面。

比如,A就是局部内部类:

public class OuterClassTest1 {
    //说明:局部内部类的使用
    public void method1(){
        //局部内部类
        class A{
        	//可以声明属性、方法等
        }
    }

}
4.2 开发中

其实,在开发当中,上面的写法不常见。

一般都是这样,

比如在调用此方法的时候,需要返回一个实例,这个实例是Comparable类型的,如下:

//开发中的场景
public Comparable getInstance(){

}

这个Comparable类型其实是一个接口:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第16张图片

现在需要返回一个接口的实例,接口要想造实例,要先提供一个实现类,这个实现类就写到内部了。

<1> 方式1-提供接口的实现类的匿名对象

public class OuterClassTest1 {
    //开发中的场景
    public Comparable getInstance(){
        //提供实现Comparable接口的类
        
        //方式1:提供接口的实现类的匿名对象
        class MyComparable implements Comparable{
            //重写Comparable里面的抽象方法
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        }
    	return new MyComparable();
    }

}

这里就相当于提供了Comparable的实现类MyComparable,这个类写到方法里面了,就是局部内部类

当我们调用getInstance()方法的时候,就返回了Comparable一个实现类的对象。


<2> 方式2-提供接口的匿名实现类的匿名对象

刚才接口Comparable的实现类MyComparable是有名的,而造的对象没有名:return new MyComparable();,所以刚才的方式1叫“提供接口的实现类的匿名对象”。

现在让实现类也匿名。这样写呢:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第17张图片

接口Comparable没有构造器啊,怎么造对象?其实这里是用Comparable充当一下而已,毕竟实现类匿名了。

形式上用接口来充当一下。

小括号后边用一个大括号,体现它是一个实现类,然后把接口里面的抽象方法重写一下即可。

如下:

public class OuterClassTest1 {

    //开发中的场景
    public Comparable getInstance(){
        //提供实现Comparable接口的类

        //方式 2:提供接口的匿名实现类的匿名对象
        return new Comparable(){
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        };

    }

}

现在我们写的就是“接口的匿名实现类的匿名对象”。实现类虽然匿名了,但还是有的,所以也是内部类。

就是在创建匿名实现类的对象时候,利用了接口的多态,左边是接口类型,右边因为是又利用接口类型来new,但其实new 接口(){}里面的所有就是匿名实现类。

通俗一点就是你造了一个没有名字的类,并且这个类实现了某接口的抽象方法,那么这个没有名字的类就是那个接口的实现类,而没有名字的实现类就叫匿名实现类。


<3> 方式3-提供接口的匿名实现类的对象

可能刚才的方式2大家有点接受不了,那就来看一下有名对象

如下:

public class OuterClassTest1 {
    //开发中的场景
    public Comparable getInstance(){
        //提供实现Comparable接口的类

        //方式 3:提供接口的匿名实现类的对象
        Comparable c=new Comparable() {
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        };
        return c;
    }

}

<4> 方式4-提供接口的实现类的对象

再来看一下最基础的方式吧。

如下:

public class OuterClassTest1 {
    //开发中的场景
    public Comparable getInstance(){
        //方式 4:提供接口的实现类的对象
        class MyComparable implements Comparable{
            //重写Comparable里面的抽象方法
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        }
        MyComparable m=new MyComparable();
        return m;
    }
}
4.3 字节码文件
package yuyi01;

/**
 * ClassName: OuterClass
 * Package: yuyi01
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/30 0030 17:28
 */
public class OuterClassTest1 {
    //说明:局部内部类的使用
    public void method1(){
        //局部内部类
        class A{
            //可以声明属性、方法等
        }
    }

    //开发中的场景
    public Comparable getInstance(){
        //提供实现Comparable接口的类
        //方式 1:提供接口的实现类的匿名对象
        /*class MyComparable implements Comparable{
            //重写Comparable里面的抽象方法
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        }
        return new MyComparable();*/

        //方式 2:提供接口的匿名实现类的匿名对象
        /* return new Comparable(){
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        };*/

        //方式 3:提供接口的匿名实现类的对象
        /*Comparable c=new Comparable() {
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        };
        return c;*/

        //方式 4:提供接口的实现类的对象
        class MyComparable implements Comparable{
            //重写Comparable里面的抽象方法
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        }
        MyComparable m=new MyComparable();
        return m;

    }

}

对应的字节码文件:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第18张图片

若是匿名实现类的匿名对象,字节码文件是这样的:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第19张图片

从这个角度来看,方式2也有内部类,只不过这个内部类是一个方法里面匿名的类。

4.4 再举例

之前在接口那一节说过这样一个例子:

【USBTest.java】

package yuyi01;

/**
 * ClassName: USBTest
 * Package: yuyi01
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/26 0026 15:59
 */
public class USBTest {
    public static void main(String[] args) {
        //造一个电脑
        Computer computer=new Computer();   //computer类调用抽象接口的抽象方法,具体类实现抽象接口(重写方法)

        //场景一:创建接口实现类(Printer)的对象(printer)
        //造一个打印机
        Printer printer=new Printer();
        //在电脑上连打印机
        computer.transferData(printer); //transferData()方法里面声明的是USB类型,实际赋值的是Printer类型。即:USB usb=new Printer();

        System.out.println();   //换行

        //场景二:创建接口实现类的匿名对象
        computer.transferData(new Camera());

        System.out.println();   //换行

        //场景三: 创建接口匿名实现类的对象
        USB usb1=new USB(){
            public void start(){
                System.out.println("U盘开始工作");
            }

            public void stop(){
                System.out.println("U盘结束工作");
            }
        };
        computer.transferData(usb1);

        System.out.println();   //换行

        //场景四: 创建接口匿名实现类的匿名对象
        computer.transferData(new USB(){
            public void start(){
                System.out.println("扫描仪开始工作");
            }

            public void stop(){
                System.out.println("扫描仪结束工作");
            }
        });


    }
}

//电脑
class Computer{
    public void transferData(USB usb){  //多态:USB usb=new Printer();
        System.out.println("设备连接成功...");
        usb.start();

        System.out.println("数据传输细节操作...");

        usb.stop();
    }
}

//打印机
class Printer implements USB{   //打印机实现USB接口

    @Override
    public void start() {
        System.out.println("打印机开始工作");
    }

    @Override
    public void stop() {
        System.out.println("打印机结束工作");
    }
}

//照相机
class Camera implements USB{

    @Override
    public void start() {
        System.out.println("照相机开始工作");
    }

    @Override
    public void stop() {
        System.out.println("照相机结束工作");
    }
}


//USB接口
interface USB{
    //声明常量,比如USB的长、宽、高...

    //方法
    public abstract void start();

    void stop();
}

这里的场景三就相当于一个局部内部类:(在main方法里面的匿名实现类)

public class USBTest {
    public static void main(String[] args) {
        //造一个电脑
        Computer computer=new Computer();   //computer类调用抽象接口的抽象方法,具体类实现抽象接口(重写方法)


        //场景三: 创建接口匿名实现类的对象
        USB usb1=new USB(){
            public void start(){
                System.out.println("U盘开始工作");
            }

            public void stop(){
                System.out.println("U盘结束工作");
            }
        };
        computer.transferData(usb1);

    }
}

//电脑
class Computer{
    public void transferData(USB usb){  //多态:USB usb=new Printer();
        System.out.println("设备连接成功...");
        usb.start();

        System.out.println("数据传输细节操作...");

        usb.stop();
    }
}

//USB接口
interface USB{
    //声明常量,比如USB的长、宽、高...

    //方法
    public abstract void start();

    void stop();
}

场景一与场景二都不算是内部类,Printer和Camera都在外部定义了,属于外部类。

场景三和场景四都是在main方法里面提供的类,只不过匿名而已。

看一下字节码文件

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第20张图片

字节码文件可以不关注,代码会写就行。

二、 成员内部类

(1)概述

如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类,否则声明为非静态内部类

语法格式:

[修饰符] class 外部类{
    [其他修饰符] [static] class 内部类{
    }
}

成员内部类的使用特征,概括来讲有如下两种角色:

  • 成员内部类作为类的成员的角色
    • 和外部类不同,Inner class还可以声明为private或protected;
    • 可以调用外部类的结构。(注意:在静态内部类中不能使用外部类的非静态成员)
    • Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
  • 成员内部类作为类的角色
    • 可以在内部定义属性、方法、构造器等结构
    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
    • 可以声明为abstract类 ,因此可以被其它的内部类继承
    • 可以声明为final的,表示不能被继承
    • 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)

注意点:

  1. 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
  2. 成员内部类可以直接使用外部类的所有成员,包括私有的数据
  3. 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的

(2) 创建成员内部类对象

  • 实例化静态内部类
外部类名.静态内部类名 变量 = 外部类名.静态内部类名();
变量.非静态方法();
  • 实例化非静态内部类
外部类名 变量1 = new 外部类();
外部类名.非静态内部类名 变量2 = 变量1.new 非静态内部类名();
变量2.非静态方法();

(3)举例

public class TestMemberInnerClass {
    public static void main(String[] args) {
        //创建静态内部类实例,并调用方法
        Outer.StaticInner inner = new Outer.StaticInner();
        inner.inFun();
        //调用静态内部类静态方法
        Outer.StaticInner.inMethod();

        System.out.println("*****************************");
        
        //创建非静态内部类实例(方式1),并调用方法
        Outer outer = new Outer();
        Outer.NoStaticInner inner1 = outer.new NoStaticInner();
        inner1.inFun();

        //创建非静态内部类实例(方式2)
        Outer.NoStaticInner inner2 = outer.getNoStaticInner();
        inner1.inFun();
    }
}
class Outer{
    private static String a = "外部类的静态a";
    private static String b  = "外部类的静态b";
    private String c = "外部类对象的非静态c";
    private String d = "外部类对象的非静态d";

    static class StaticInner{
        private static String a ="静态内部类的静态a";
        private String c = "静态内部类对象的非静态c";
        public static void inMethod(){
            System.out.println("Inner.a = " + a);
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("b = " + b);
        }
        public void inFun(){
            System.out.println("Inner.inFun");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("Inner.a = " + a);
            System.out.println("b = " + b);
            System.out.println("c = " + c);
//            System.out.println("d = " + d);//不能访问外部类的非静态成员
        }
    }

    class NoStaticInner{
        private String a = "非静态内部类对象的非静态a";
        private String c = "非静态内部类对象的非静态c";

        public void inFun(){
            System.out.println("NoStaticInner.inFun");
            System.out.println("Outer.a = " + Outer.a);
            System.out.println("a = " + a);
            System.out.println("b = " + b);
            System.out.println("Outer.c = " + Outer.this.c);
            System.out.println("c = " + c);
            System.out.println("d = " + d);
        }
    }


    public NoStaticInner getNoStaticInner(){
        return new NoStaticInner();
    }
}

三、 局部内部类

(1)非匿名局部内部类

语法格式:

[修饰符] class 外部类{
    [修饰符] 返回值类型  方法名(形参列表){
            [final/abstract] class 内部类{
    	}
    }    
}
  • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
    • 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
  • 和成员内部类不同的是,它前面不能有权限修饰符等
  • 局部内部类如同局部变量一样,有作用域
  • 局部内部类中是否能访问外部类的非静态的成员,取决于所在的方法

举例:

/**
 * ClassName: TestLocalInner
 * @Author 尚硅谷-宋红康
 * @Create 17:19
 * @Version 1.0
 */
public class TestLocalInner {
    public static void main(String[] args) {
        Outer.outMethod();
        System.out.println("-------------------");

        Outer out = new Outer();
        out.outTest();
        System.out.println("-------------------");

        Runner runner = Outer.getRunner();
        runner.run();

    }
}
class Outer{

    public static void outMethod(){
        System.out.println("Outer.outMethod");
        final String c = "局部变量c";
        class Inner{
            public void inMethod(){
                System.out.println("Inner.inMethod");
                System.out.println(c);
            }
        }

        Inner in = new Inner();
        in.inMethod();
    }

    public void outTest(){
        class Inner{
            public void inMethod1(){
                System.out.println("Inner.inMethod1");
            }
        }

        Inner in = new Inner();
        in.inMethod1();
    }

    public static Runner getRunner(){
        class LocalRunner implements Runner{
            @Override
            public void run() {
                System.out.println("LocalRunner.run");
            }
        }
        return new LocalRunner();
    }

}
interface Runner{
    void run();
}

(2) 匿名内部类

因为考虑到这个子类或实现类是一次性的,那么我们“费尽心机”的给它取名字,就显得多余。那么我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题。

new 父类([实参列表]){
    重写方法...
}
new 父接口(){
    重写方法...
}

举例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:通过父类或父接口的变量多态引用匿名内部类的对象

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");
			}
    	});
    }   
}

四、练习

(1)练习1

题目描述

编写一个匿名内部类,它继承Object,并在匿名内部类中,声明一个方法public void test()打印雨翼轻尘。

请编写代码调用这个方法。

分析

若是写成外部类,应该是这样来写,大家都能看得懂:

public class ObjectTest {
    public static void main(String[] args) {
        SubObject sub1 = new SubObject();  //创建SubObject的实例
        sub1.test();
    }
}

class SubObject extends Object{ //继承于Object可写可不写,本身就继承于Object
    public void test(){
        System.out.println("雨翼轻尘");
    }
}

输出结果:

image.png

回归到一开始说“面向对象”时候讲过的事儿:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第21张图片

以后写代码全是这个套路。


现在需要的是匿名,通常只调用一次。

那么直接在main方法里面做即可。

//提供有一个继承于Object的匿名子类的对象
new Object();   //这样写表示new的是一个Object

但是现在new的不是Object,那么就加上一个大括号。

在大括号里面写上刚才的方法即可,如下:

//提供有一个继承于Object的匿名子类的对象
new Object(){
    public void test(){
        System.out.println("雨翼轻尘");
    }
};

这就相当于把这个匿名子类的对象也顺带造好了。这个对象有名字,写前面即可。

注意因为子类没有名字,所以只能用多态的方式赋给父类Object。如下:

//提供有一个继承于Object的匿名子类的对象
Object obj=new Object(){
    public void test(){
        System.out.println("雨翼轻尘");
    }
};

现在创建的可不是Object对象,Object对象里面也不可能有test()方法啊,既然里面声明了test()方法了,那就只能理解为Object的子类。

现在就提供了有一个继承于Object的匿名子类的对象obj

接下来拿着obj去调用test()方法,如下:

//提供有一个继承于Object的匿名子类的对象
Object obj=new Object(){
    public void test(){
        System.out.println("雨翼轻尘");
    }
};
obj.test();

可以看到现在是报错的:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第22张图片

为啥不让调用呢?

因为此时声明的是Object,里面根本就没有test()方法。

这里相当于是多态,必须要转成子类的类型才能调test()。但是现在子类没有名字啊,那对象也别起名字了,没啥用,直接调用test,

如下:

//提供有一个继承于Object的匿名子类的匿名对象
new Object(){
    public void test(){
        System.out.println("雨翼轻尘");
    }
}.test();

这种写法之前遇到过,比如:new Object().toString();,直接new了一个Object对象,然后直接调toString()方法。

现在这个题目的情况是,我们造了一个Object子类的对象,用这个对象去调用test()方法。

子类?我们在Object()后面多增加了一个大括号,里面增加了一个方法。写完之后这个对象就已经有了这个方法,既然有,那么就可以直接去调用这个方法。

//new Object().toString();

new Object(){
    //再增加一个方法test()
    public void test(){
        System.out.println("雨翼轻尘");
    }
}.test();

代码

【ObjectTest.java】

package yuyi01;

/**
 * ClassName: ObjectTest
 * Package: yuyi01
 * Description:
 *      编写一个匿名内部类,它继承Object,并在匿名内部类中,声明一个方法public void test()打印雨翼轻尘。
 * @Author 雨翼轻尘
 * @Create 2023/12/1 0001 13:27
 */
public class ObjectTest {
    public static void main(String[] args) {

        //提供有一个继承于Object的匿名子类的匿名对象
        new Object(){
            public void test(){
                System.out.println("雨翼轻尘");
            }
        }.test();
    }
}

输出结果

image.png

(2)练习2

这里为了大家理解,再举个例子。

1. 接口

先看一段代码:

package yuyi01;

/**
 * ClassName: OuterClass2
 * Package: yuyi01
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2023/12/1 0001 14:04
 */
public class OuterClassTest2 {
    public static void main(String[] args) {
        SubA a=new SubA();
        a.method(); //调用的是重写的方法
    }
}

interface A{    //接口A
    public void method();   //抽象方法
}

class SubA implements A{
    @Override
    public void method() {
        System.out.println("Sub");
    }
}

很简单,之前说接口的时候,已经很明白了。

输出结果:

在这里插入图片描述


【举例1 :提供接口匿名实现类的对象】

此时我们new一个A()。

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第23张图片

但是接口不能new,所以在后面加一个大括号,在里面将抽象方法重写,如下:

//举例1:提供接口匿名实现类的对象
A a1=new A(){
    public void method(){
        System.out.println("匿名实现类重写的方法method()");
    }
};

理解为:提供了接口A的匿名实现类的对象

然后通过a1来调用method()方法,如下:

//举例1
A a1=new A(){
    public void method(){
        System.out.println("匿名实现类重写的方法method()");
    }
};
a1.method();

输出结果:

image.png

举例1中类没有名,对象有名,叫a1


【举例2:提供接口匿名实现类的匿名对象】

//举例2:提供接口匿名实现类的匿名对象
new A(){
    public void method(){
            System.out.println("匿名实现类重写的方法method2()");
        }
}.method();

这里就跟Object那个例子类似了。

输出结果:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第24张图片

举例2中直接拿对象调了方法,对象没有名字。

2. 抽象类

比如现在有一个抽象类B,如下:

abstract class B{   //抽象类B
    //抽象类里面可以有抽象方法,也可以没有
    public abstract void method2();  //抽象类
}

【标准写法】

package yuyi01;

public class OuterClassTest2 {
    public static void main(String[] args) {

        //举例1
        SubB s1=new SubB();
        s1.method2();
    }
}


abstract class B{   //抽象类B
    //抽象类里面可以有抽象方法,也可以没有
    public abstract void method2();  //抽象类
}

class SubB extends B{
    //重写B中的抽象方法
    @Override
    public void method2() {
        System.out.println("SubB");
    }
}

【举例2】

现在提供继承于抽象类的匿名子类的对象,那就new一个B,

B b=new B();

但是B是抽象类啊,提供子类的同时把抽象方法也重写一下。

抽象类虽然有构造器,但是有抽象方法,所以在后面加一个大括号,在里面将抽象方法重写一下,或者可以叫“实现”,一般都将抽象方法的重写叫实现

B b=new B(){
    public  void method2(){
        System.out.println("继承于抽象类的子类调用的方法");
    }
};
b.method2();

输出结果:

在这里插入图片描述

这个子类没有名字,但是对象有名字,叫b。

这里可以证明一下它是一个子类,Object中有一个方法叫getClass(),获取对象所属的类,如下:

System.out.println(b.getClass());

可以看到,b所属的类与B没有关系,因为这个类没有名字,写在main方法里面的,所以是类OuterClassTest2里面的内部类,由于又是方法(main方法)里面的且没有名字,最后就有一个号码。

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第25张图片

还可以获取当前类的父类System.out.println(b.getClass().getSuperclass());,即B,如下:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第26张图片

方法里面定义的内部类,它的父类当然是父类B啦。


【举例3】

直接new一个B,然后直接调用method2(),显然现在不靠谱,因为抽象类不能造对象。

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第27张图片

所以在小括号后面要将方法重写一下:

new B(){
    public  void method2(){
        System.out.println("继承于抽象类的子类调用的方法2");
    }
}.method2();

输出结果:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第28张图片

接下来写一个普通的类C,然后写一个方法method3(),如下:

class C{
    public void method3(){
        System.out.println("C");
    }
}

调用一下:

【标准写法】

public class OuterClassTest2 {
    public static void main(String[] args) {
        //举例1
        C c=new C();
        c.method3();

    }
}

class C{
    public void method3(){
        System.out.println("C");
    }
}

输出结果:

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第29张图片


【举例2】

将上面那种写法换一个方式:

//举例2:提供一个继承于C的匿名子类的对象
C c1=new C(){};
c1.method3();

因为C里面没有抽象方法,所以没有提示我们要去重写,这里只不过打印的还是C而已。

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第30张图片

跟上面一样,这里可以看一下当前对象所属的类

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第31张图片

再看一下当前类的父类

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第32张图片

这里加了大括号与没加大括号差别很大,若是没有加大括号,那么getClass()结果就是C,getSuperclass()结果就是Object,如下:
Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第33张图片


【举例3】

还可以将method3()方法重写,这样对象调用的就是重写的方法了,如下:

//举例3
C c2=new C(){
    public void method3(){
        System.out.println("SubC");
    }  
};
c2.method3();

输出结果:
Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第34张图片

3. 代码

这里将练习2的代码放在这里,供大家学习使用。

代码

package yuyi01;

/**
 * ClassName: OuterClass2
 * Package: yuyi01
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2023/12/1 0001 14:04
 */
public class OuterClassTest2 {
    public static void main(String[] args) {
        SubA a=new SubA();
        a.method(); //调用的是重写的方法

        //举例1:提供接口匿名实现类的对象
        A a1=new A(){
            public void method(){
                System.out.println("匿名实现类重写的方法method()");
            }
        };
        a1.method();

        //举例2:提供接口匿名实现类的匿名对象
        new A(){
            public void method(){
                System.out.println("匿名实现类重写的方法method2()");
            }
        }.method();


        //举例1
        SubB s1=new SubB();
        s1.method2();

        //举例2:提供继承于抽象类的匿名子类的对象
        B b=new B(){
            public  void method2(){
                System.out.println("继承于抽象类的子类调用的方法");
            }
        };
        b.method2();
        System.out.println(b.getClass());
        System.out.println(b.getClass().getSuperclass());

        //举例3
        new B(){
            public  void method2(){
                System.out.println("继承于抽象类的子类调用的方法2");
            }
        }.method2();


        //举例1
        C c=new C();
        c.method3();

        //举例2
        C c1=new C(){};
        //C c1=new C();
        c1.method3();

        System.out.println(c1.getClass());
        System.out.println(c1.getClass().getSuperclass());

        //举例3
        C c2=new C(){
            public void method3(){
                System.out.println("SubC");
            }
        };
        c2.method3();


    }
}

interface A{    //接口A
    public void method();   //抽象方法
}

class SubA implements A{
    @Override
    public void method() {
        System.out.println("Sub");
    }
}

abstract class B{   //抽象类B
    //抽象类里面可以有抽象方法,也可以没有
    public abstract void method2();  //抽象类
}

class SubB extends B{
    //重写B中的抽象方法
    @Override
    public void method2() {
        System.out.println("SubB");
    }
}

class C{
    public void method3(){
        System.out.println("C");
    }
}

输出结果

Java面向对象(高级)-- 类的成员之五:内部类(InnerClass)_第35张图片

你可能感兴趣的:(Java基础,java,面向对象(高级),类的成员,内部类)