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修饰的:非静态的成员内部类(跟外部类的对象相关,随着对象的创建而加载)
  • 局部内部类:声明在方法内、构造器内、代码块内的内部类

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

image.png

(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吗?不行哦,看下面:

在这里插入图片描述

同样需要说明是谁里面的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不是静态的),如下:

image.png

需要先创建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的调用呢?这里当然就近是自己类里面的啦。

image.png

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

image.png

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

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

image.png

这个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
        }
    }

}

🍺输出结果

image.png

☕强调

这里再次强调一下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()方法。

如下:

image.png

这里明显省略了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("人吃有营养的食物");
    }
}

🍺输出结果

image.png

☕强调

这里再次强调一下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{

        }
    }

}

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

image.png

☕分析

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

在这里插入图片描述

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

image.png

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

image.png

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

image.png

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

image.png

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

4. 局部内部类的使用

4.1 介绍

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

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

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

}
4.2 开发中

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

一般都是这样,

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

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

}

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

image.png

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

<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叫“提供接口的实现类的匿名对象”。

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

image.png

接口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;

    }

}

对应的字节码文件:

image.png

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

image.png

从这个角度来看,方式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方法里面提供的类,只不过匿名而已。

看一下字节码文件

image.png

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

二、 成员内部类

(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

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

image.png

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


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

那么直接在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();

可以看到现在是报错的:

image.png

为啥不让调用呢?

因为此时声明的是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()。

image.png

但是接口不能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那个例子类似了。

输出结果:

image.png

举例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方法)里面的且没有名字,最后就有一个号码。

image.png

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

image.png

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


【举例3】

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

image.png

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

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

输出结果:

image.png

接下来写一个普通的类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");
    }
}

输出结果:

image.png


【举例2】

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

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

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

image.png

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

image.png

再看一下当前类的父类

image.png

这里加了大括号与没加大括号差别很大,若是没有加大括号,那么getClass()结果就是C,getSuperclass()结果就是Object,如下:
image.png


【举例3】

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

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

输出结果:
image.png

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

🍺输出结果

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/209897.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大数据读本:暴雨以数字技术助力传统产业数字化转型

发展数字经济&#xff0c;产业数字化是重要引擎。暴雨作为数字经济的领军企业&#xff0c;近年来积极利用数字技术对传统产业进行全方位、全角度、全链条的改造&#xff0c;提高要素生产率&#xff0c;释放数字对经济发展的放大、叠加、倍增作用。在农业产业化方面&#xff0c;…

【开源】基于Vue和SpringBoot的校园二手交易系统

项目编号&#xff1a; S 009 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S009&#xff0c;文末获取源码。} 项目编号&#xff1a;S009&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 二手商品档案管理模…

K7系列FPGA多重启动(Multiboot)

Xilinx 家的 FPGA 支持多重启动功能&#xff08;Multiboot&#xff09;&#xff0c;即可以从多个 bin 文件中进行选择性加载&#xff0c;从而实现对系统的动态更新&#xff0c;或系统功能的动态调整。 这一过程可以通过嵌入在 bit 文件里的 IPROG 命令实现上电后的自动加载。而…

房产中介管理信息系统的设计与实现

摘 要 随着房地产业的开发&#xff0c;房产中介行业也随之发展起来&#xff0c;由于房改政策的出台&#xff0c;购房、售房、租房的居民越来越多&#xff0c;这对房产中介部门无疑是一个发展的契机。本文结合目前中国城市房产管理的实际情况和现阶段房屋产业的供求关系对房产中…

用Java写一个王者荣耀游戏

目录 sxt包 Background Bullet Champion ChampionDaji GameFrame GameObject Minion MinionBlue MinionRed Turret TurretBlue TurretRed beast包 Bear Beast Bird BlueBuff RedBuff Wolf Xiyi 打开Eclipse创建图片中的几个包 sxt包 Background package sxt;…

Rust语言项目实战(三) - 创建主循环

回顾 在前面的章节中&#xff0c;我们大致已经完成了如下的工作&#xff1a; 为游戏添加了音频文件为游戏准备了备用屏幕及设置为游戏准备了键盘的即时捕获输入的设置在退出游戏前恢复上述的设置 众所周知&#xff0c;游戏在不手动退出的情况下应该一直运行下去&#xff0c;…

编程好处、系统介绍、app演示

编程视频教学地址&#xff1a; 1、编程好处 1.1、自主开发 类似微信、qq等软件应用&#xff0c;解决人们日常生活问题 例如&#xff1a; 1&#xff09;你可以&#xff0c;自己开发一个网站&#xff0c;管理自己的日常生活照片&#xff0c;防止哪一天手机掉了或丢了&#xff0…

UVA11729 Commando War

UVA11729 Commando War 题面翻译 突击战 你有n个部下&#xff0c;每个部下需要完成一项任务。第i个部下需要你花Bj分钟交代任务&#xff0c;然后他就会立刻独立地、无间断地执行Ji分钟后完成任务。你需要选择交代任务的顺序&#xff0c;使得所有任务尽早执行完毕&#xff08…

高斯混合模型:GMM和期望最大化算法的理论和代码实现

高斯混合模型(gmm)是将数据表示为高斯(正态)分布的混合的统计模型。这些模型可用于识别数据集中的组&#xff0c;并捕获数据分布的复杂、多模态结构。 gmm可用于各种机器学习应用&#xff0c;包括聚类、密度估计和模式识别。 在本文中&#xff0c;将首先探讨混合模型&#xf…

【Java Web学习笔记】 1 - HTML入门

项目代码 https://github.com/yinhai1114/JavaWeb_LearningCode/tree/main/html 零、网页的组成 HTML是网页内容的载体。内容就是网页制作者放在页面上想要让用户浏览的信息&#xff0c;可以包含文字、图片视频等。 CSS样式是表现。就像网页的外衣。比如&#xff0c;标题字体、…

electerm下载和安装

electerm下载和安装 一、概述 electerm 是一款免费开源、基于electron/ssh2/node-pty/xterm/antd/ subx等libs的终端/ssh/sftp客户端(linux, mac, win)。 而且个人觉得electerm界面更好看一些&#xff0c;操作都是类似的。 二、下载安装 下载地址&#xff1a;https://elec…

正则表达式从放弃到入门(1):“正则表达式”是什么?

正则表达式从放弃到入门&#xff08;1&#xff09;&#xff1a;“正则表达式”是什么&#xff1f; 本博文转载自 这是一篇”正则表达式”扫盲贴&#xff0c;如果你还不理解什么是正则表达式&#xff0c;看这篇文章就对了。 如果你已经掌握了”正则表达式”&#xff0c;就不用再…

pip包管理工具

pip 是 Python 包管理工具&#xff0c;该工具提供了对Python包的查找、下载、安装、卸载的功能。 Python 2.7.9 或 Python 3.4 以上版本的python都自带 pip 工具 1. 配置pip国内镜像 pip安装的包都存在于外国的服务器上&#xff0c;速度会非常慢&#xff0c;可以给pip配置国内…

全栈冲刺 之 一天速成MySQL

一、为什么使用数据库 数据储存在哪里&#xff1f; 硬盘、网盘、U盘、光盘、内存&#xff08;临时存储&#xff09; 数据持久化 使用文件来进行存储&#xff0c;数据库也是一种文件&#xff0c;像excel &#xff0c;xml 这些都可以进行数据的存储&#xff0c;但大量数据操作…

arcgis投影栅格不可用

1、使用【投影栅格】工具进行栅格数据投影转换时报错。 解决方法&#xff1a;如果使用的是arcgis10.5及以下的版本&#xff0c;则需要更换更高的版本&#xff0c;因为这个是软件问题&#xff0c;需要更换到arcgis10.6及以上版本&#xff0c;更高级别的版本已经修复了这个问题。…

【题目】链表相关算法题

文章目录 一. 合并两个有序链表题目解析算法原理代码编写 二. 相交链表问题题目解析算法原理代码编写 三. 环形链表问题1. 判断是否有环2. 计算环的长度3. 找到环的入口点 四. 反转链表方法一&#xff1a;边迭代、边逆置方法二&#xff1a;头插 五. 判断链表是否回文题目解析算…

LinkedList详解

LinkedList详解 LinkedList是List接口的一个主要的实现类之一&#xff0c;基于链表的实现。以java8为例来了解一下LinkedList的源码实现 继承关系 public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>,…

进制转化总结

来源&#xff0c;做个笔记&#xff0c;讲的还蛮清楚通信原理-2.5 数据封装与传输05_哔哩哔哩_bilibili ip地址范围

6 新建工程——寄存器

文章目录 6.1 本地新建工程文件夹6.2 新建工程6.2.1 选择CPU型号6.2.2 在线添加库文件6.2.3 添加文件6.2.4 复制存储器分配文件6.2.5 配置选项卡6.2.5.1 Linker6.2.5.2 Target6.2.5.3 Output 选项卡6.2.5.4 Listing 选项卡6.2.6 下载器配置 版本说明&#xff1a;MDK5.24 6.1 本…

许战海战略文库|重回大众视野的健力宝如何重生

摘要&#xff1a;销售额连续7年没有增长;产业主品牌定位不清晰;产品不协同缺少产品战略;子品牌无法形成合力新产品共性不足;过度差异化缺少渠道战略;被渠道能力更强的品牌挤压。火遍世界的“东方魔水”从第一品牌到被人遗忘&#xff0c;健力宝该如何重生? 健力宝诞生于1984年&…