Java基础(四) 内部类详解

news2024/11/22 15:50:55

Java 内部类详解

一. 内部类概述

内部类是嵌套在类内部进行定义的类,其外部的类则被称为外部类;按照内部类的定义位置,内部类可进一步划分为成员内部类、静态内部类、局部内部类和匿名内部类四种类型。内部类的出现实际上是进一步丰富了类的作用域和可访问限制,其特点和优势如下:

  • 封装性:内部类增强了面向对象的封装性,可以把一些实现细节封装在内部类中,隐藏在外部类之内,限制了其他对象的直接访问。

  • 代码组织:内部类使得相关的类可以紧密地封装在一起,提高了代码的可读性和可维护性。

  • 访问特权:内部类在一些场景下可以直接访问外部类的所有成员,包括私有成员,这在设计回调机制、事件监听器等场景下非常有用。

  • 生命周期相关性:某些内部类(非静态内部类)的实例与外部类的实例之间存在紧密的生命周期联系,它们共享同一个外部类实例。

二. 成员内部类

1. 成员内部类的定义与初始化

(1)定义位置: 成员内部类是定义在外部类的成员位置的内部类,并且没有static修饰;

(2)访问修饰符: 成员内部类可以添加任意访问修饰符(如public、protected、默认、private),且其外部类外的可访问性受修饰符影响;

(3)内容限制:

  • 成员内部类不能含有静态成员变量、静态代码块和静态方法(JDK16开始允许成员内部类中定义静态变量/方法),因为该类的可见性本身依赖于外部类的实例
  • 成员内部类可以包含构造函数、非静态方法、非静态成员属性、非静态代码块(按照类中出现顺序执行所有的非静态代码块,然后执行构造函数),且可使用任意访问修饰符修饰成员内容;
  • 成员内部类可以各自继承不同的父类或实现接口,当作一个完整的类去使用;

1.1 定义

//1. 外部类
public class OuterDemo01 {
    //2. 成员内部类
    public class InnerDemo01 {
        // 内部类数属性
        public int a = 1;
        // 内部类代码块
        {
            System.out.println("inner: code block run");
        }
        // 内部类构造方法
        public InnerDemo01(int _a){
            a = _a;
            System.out.println("inner: init");
        }
        // 内部类方法
        public void display(){
            System.out.println("inner: display a="+a);
        }
    }
}

1.2 初始化

成员内部类依赖于外部类,需要通过外部类对象来创建成员内部类的实例对象;具体可分为外部类内初始化和外部类外初始化两种情况。

(1)外部类内初始化

在外部类内,不管成员内部类用什么修饰符修饰(private、public等),其可访问性/可见性对于外部类来说不受影响,外部类可以像访问类内其他成员一样访问、创建内部类实例,且不用加外部类前缀声明,具体使用场景如下:

  1. 作为成员属性:可直接访问并初始化;
  2. 在非静态方法:可直接访问并初始化;
  3. 在静态方法:与在外部类外访问成员内部类一样,需要先进行外部类实例化;
//外部类
public class OuterDemo01 {
    //成员内部类(private修饰)
    private class InnerDemo01 {
        public int a = 1;

        public InnerDemo01(int _a){
            a = _a;
            System.out.println("inner: init");
        }
        
        public void display(){
            System.out.println("inner: display a="+a);
        }
    }
    //1. 成员属性:可直接访问并初始化
    private InnerDemo01 innerDemo01 = new InnerDemo01(0);
    //2. 非静态方法:可直接访问并初始化
    public void innerDisplay_1(){
        InnerDemo01 inner = new InnerDemo01(10);
        inner.display();
    }
    //3. 静态方法:与在外部类外访问成员内部类一样,需要先进行外部类实例化
    public static void innerDisplay_2(){
        OuterDemo01 outer = new OuterDemo01();
        InnerDemo01 inner = outer.new InnerDemo01(11); //外部类内声明内部类的类型可以不用加外部类名前缀即 OuterDemo01.InnerDemo01
        inner.display();
    }
}

(2)外部类外初始化

在外部类外,成员内部类的可访问性/可见性严格受访问修饰符控制,且创建/初始化时需要先进行外部类的实例化并加上外部类前缀声明,具体使用场景举例如下:

  • 内部类声明为public修饰:允许对外部类外访问,可以直接创建/初始化成员内部类对象实例,常用格式为 OuterClass.InnerClass inner = new OuterClass().new InnerClass()

  • 内部类声明为private修饰:无法对外部类外访问,只能通过外部类提供方法获取成员内部类对象实例。但注意此时无法在外部直接使用内部类类型接收,因为内部类对外不可见/不可声明,只能通过多态(可见父类、可见接口等)的方式接收并使用,该场景以ArrayList源码中的Itr类型为例:

    在这里插入图片描述

//外部类
public class OuterDemo02 {
    //成员内部类
     public class InnerDemo02 {
        public int a = 1;

        public InnerDemo02(int _a){
            a = _a;
            System.out.println("inner: init");
        }

        public void display(){
            System.out.println("inner: display a="+a);
        }
    }
}

//Test(外部类外)
public class Test {
    public static void main(String[] args) {
        // 实例化方法1
        OuterDemo02 outer1 = new OuterDemo02();
        OuterDemo02.InnerDemo02 inner1 = outer1.new InnerDemo02(10);
        // 实例化方法2
        OuterDemo02.InnerDemo02 inner2 = new OuterDemo02().new InnerDemo02(10);
    }
}
//外部类
public class OuterDemo02 {
    //成员内部类(private修饰)
     private class InnerDemo02 {
        public int a = 1;

        public InnerDemo02(int _a){
            a = _a;
            System.out.println("inner: init");
        }

        public void display(){
            System.out.println("inner: display a="+a);
        }
    }
    // 成员内部类获取:非静态方法
    public InnerDemo02 getInnerInstance(int init_val){
         return new InnerDemo02(init_val);
    }
    // 成员内部类获取:静态方法
    public static InnerDemo02 getInnerInstanceStatic(int init_val){
         return new OuterDemo02().new InnerDemo02(init_val);
    }
}

//Test(外部类外)
public class Test {
    public static void main(String[] args) {
        // 获取方法1:直接获取
        OuterDemo02 outer1 = new OuterDemo02();
        //OuterDemo02.InnerDemo02 inner1 = outer1.getInnerInstance(10); //报错:java: com.study.OuterDemo02.InnerDemo02 在 com.study.OuterDemo02 中是 private 访问控制
        System.out.println(outer1.getInnerInstance(10)); //可直接打印类地址
        // 获取方法2:多态接收
        OuterDemo02 outer2 = new OuterDemo02();
        Object inner2 = outer2.getInnerInstance(11);//多态接收
        // 获取方法3:静态方法+多态接收
        Object inner3 = OuterDemo02.getInnerInstanceStatic(12);
    }
}

2. 成员内部类的访问控制

2.1 内部类访问外部类

(1)访问所有:成员内部类中可以直接访问外部类的所有成员、方法和其他内部类(内部类之间不共享变量),包括静态、非静态,也可以是任何修饰符修饰的。在内部类中通常使用 [OuterClassName].this 来指代外部类对象的引用地址,其在内部类编译生成时隐式添加。

(2)命名冲突: 内部类使用成员(包括变量、属性、函数)时,当外部类的成员和内部类的成员出现重名/同名时,会发生隐藏现象(外部类的成员会被隐藏),默认情况下遵循就近原则即方法局部变量 > 内部类成员 > 外部类成员,可通过明确声明来指定。

(1)通用场景

//外部类
public class OuterDemo03 {

    private static String a = "outer static filed";

    public int b = 99;

    public static void outer_static_display() {
        System.out.println("outer static method");
    }

    private void outer_display() {
        System.out.println("outer method");
    }
    //内部类
    class InnerDemo03 {

        public void displayOuter() {

            // 1.成员内部类访问外部类的静态变量(private)
            System.out.println(OuterDemo03.a);
            System.out.println(a);//等价

            // 2.成员内部类访问外部类的静态方法
            OuterDemo03.outer_static_display();
            outer_static_display();

            // 3.成员内部类访问并修改外部类的成员变量
            b += 1;
            System.out.println(b);
            OuterDemo03.this.b += 1; //成员内部类在编译时,初始化参数持有外部类的引用OuterDemo03.this
            System.out.println(OuterDemo03.this.b);


            // 4.成员内部类访问外部类的普通方法(private)
            outer_display();
            OuterDemo03.this.outer_display();
        }
    }
}
//Test
public class Test {
    public static void main(String[] args) {
        OuterDemo03.InnerDemo03 inner = new OuterDemo03().new InnerDemo03();
        inner.displayOuter();
    }
}

(2)命名冲突

//外部类
public class OuterDemo03 {

    public int a = 10;

    //内部类
    class InnerDemo03 {

        public int a = 20;

        public void display() {
            int a = 30;

            System.out.println("method a=" + a); // 30
            System.out.println("inner a=" + this.a); // 20
            System.out.println("outer a=" + OuterDemo03.this.a); // 10
        }
    }
}

2.2 外部类访问内部类

外部类访问内部类的成员则需要通过内部类的实例来访问,并且在外部类内可以访问内部类的任意private/protected成员,就像访问自己的private/protected成员一样。

//外部类
public class OuterDemo03 {

    public int a = 10;

    //内部类
    class InnerDemo03 {

        private int a = 20;

        private void inner_display() {
            System.out.println("inner private display");
        }
    }

    public void outer_display(){
        // 实例化内部类对象
        InnerDemo03 inner = new InnerDemo03();
        // 访问并修改内部类private属性
        inner.a += 1;
        System.out.println("inner a=" + inner.a);//21
        // 访问内部类private方法
        inner.inner_display();
    }
}

三. 静态内部类

1. 静态内部类的定义与初始化

(1)定义位置: 静态内部类也是定义在外部类的成员位置,并且需要使用static修饰,类似于外部类的静态方法/成员;静态内部类无需依赖于外部类,它可以独立于外部类对象而存在,可以和外部类一样使用,

(2)访问修饰符: 静态内部类可以添加任意访问修饰符(如public、protected、默认、private),且其外部类外的可访问性受修饰符影响;

(3)内容限制:

  • 静态内部类可以拥有任何成员(静态、非静态),和普通类一样;
  • 静态内部类可以包含构造函数、方法、成员属性、代码块,且可使用任意访问修饰符修饰成员内容;
  • 静态内部类可以继承父类或实现接口,当作一个完整的类去使用;

1.1 定义

//1. 外部类
public class OuterDemo04 {
    //2. 静态内部类
    public static class InnerDemo04 {
        // 内部类属性
        public int a = 1;
        // 内部类静态属性
        public static String class_name = "InnerDemo04";
        // 内部类代码块
        {
            System.out.println("inner: code block run");
        }
        // 内部类静态代码块
        static {
            System.out.println("inner: static code block run");
        }
        // 内部类构造方法
        public InnerDemo04(int _a){
            a = _a;
            System.out.println("inner: init");
        }
        // 内部类方法
        public void display(){
            System.out.println("inner: display a = " + a);
        }
        // 内部类静态方法
        public static void static_display(){
            System.out.println("inner: static display class name = " + class_name);
        }
    }
}

1.2 初始化

静态内部类无需依赖于外部类,它可以独立于外部类对象而存在,这意味着无需先创建外部类实例就能创建静态内部类的实例,和外部类一样去使用,接下来将从外部类内初始化和外部类内初始化两种情况分别进行介绍。

(1)外部类内初始化

在外部类内,不管静态内部类用什么关键词修饰(private、public等),其可访问性/可见性对于外部类来说不受影响,外部类可以像访问类内其他成员一样访问、创建内部类实例,且不用加外部类前缀声明,具体使用场景如下:

  1. 作为成员属性:可直接访问并初始化;
  2. 在非静态方法:可直接访问并初始化;
  3. 在静态方法:与在外部类外访问静态内部类一样,由于不依赖外部类所以可直接创建静态内部类的实例对象;
//外部类
public class OuterDemo04 {
    //静态内部类(private修饰)
    private static class InnerDemo04 {
        public int a = 1;
        public static String class_name = "InnerDemo04";

        public InnerDemo04(int _a){
            a = _a;
            System.out.println("inner: init");
        }

        public void display(){
            System.out.println("inner: display a = " + a);
        }

        public static void static_display(){
            System.out.println("inner: static display class name = " + class_name);
        }
    }
    //1. 成员属性:可直接访问并初始化
    private InnerDemo04 innerDemo04 = new InnerDemo04(10);
    //2. 非静态方法:可直接访问并初始化
    public void innerDisplay_1(){
        InnerDemo04 inner = new InnerDemo04(20);
        inner.display();
    }
    //3. 静态方法:与在外部类外访问内部类一样
    public static void innerDisplay_2(){
        InnerDemo04 inner = new InnerDemo04(30);
        inner.display();
    }
}

(2)外部类外初始化

在外部类外,静态内部类的可访问性/可见性严格受访问修饰符控制;并且静态内部类无需依赖外部类,可直接创建静态内部类的实例对象,具体使用场景举例如下:

  • 内部类声明为public修饰:允许对外部类外访问,可以直接创建/初始化静态内部类对象实例,常用格式为 OuterClass.InnerStaticClass inner = new OuterClass.InnerStaticClass()

  • 内部类声明为private修饰:无法对外部类外直接访问,只能通过外部类提供方法获取静态内部类对象实例,与成员内部类的情况类似。

//外部类
public class OuterDemo04 {
    //静态内部类
    public static class InnerDemo04 {
        // 内部类属性
        public int a = 1;
        // 内部类静态属性
        public static String class_name = "InnerDemo04";
        // 内部类构造方法
        public InnerDemo04(int _a){
            a = _a;
            System.out.println("inner: init");
        }
        // 内部类方法
        public void display(){
            System.out.println("inner: display a = " + a);
        }
        // 内部类静态方法
        public static void static_display(){
            System.out.println("inner: static display class name = " + class_name);
        }
    }
}
//Test
public class Test {
    public static void main(String[] args) {
        // 直接调用静态内部类的静态方法
        OuterDemo04.InnerDemo04.static_display();
        // 直接实例化静态内部类对象
        OuterDemo04.InnerDemo04 inner = new OuterDemo04.InnerDemo04(10);
        inner.display();
    }
}

2. 静态内部类的访问控制

(1)访问静态: 静态内部类只能访问外部类中任意修饰符修饰的的静态成员(变量、方法);若需要访问外部类非静态的实例成员,则通常需要通过外部类实例对象或持有外部类实例引用。与成员内部类不同的是,静态内部类并不会隐式生成并持有外部类的引用,这也是其只能访问外部类静态成员的原因。

(2)命名冲突: 静态内部类的命名冲突情况与成员内部类相似,重名时会发生隐藏现象(外部类的成员会被隐藏),默认情况下遵循就近原则,可通过明确声明来进行指定。

2.1 内部类访问外部类

(1)通用场景

//外部类
public class OuterDemo04 {
    public int outer_a = 1;
    private static String outer_name = "OuterDemo04";
    private void outer_display(){
        System.out.println("outer: display");
    }
    public static void outer_static_display(){
        System.out.println("outer: static display");
    }

    //静态内部类
    public static class InnerDemo04 {
        public int inner_a = 1;
        public static String inner_name = "InnerDemo04";
        public InnerDemo04(int _a){
            inner_a = _a;
            System.out.println("inner: init");
        }
        // 内部类方法
        public void inner_display(){
            System.out.println("inner display:");
            // 静态内部类访问外部类的实例成员变量
            //System.out.println("inner: display outer_a = " + OuterDemo04.this.outer_a); //报错:'OuterDemo04.this' cannot be referenced from a static context
            // 静态内部类访问外部类的静态成员变量(private)
            System.out.println("inner: display outer_name = " + outer_name);
            System.out.println("inner: display outer_name = " + OuterDemo04.outer_name); // 等价
            // 静态内部类访问外部类的静态成员方法
            outer_static_display();
            OuterDemo04.outer_static_display(); // 等价
        }
        // 内部类静态方法
        public static void inner_static_display(){
            System.out.println("inner static display:");
            // 静态内部类静态方法访问外部类的静态成员变量(private)
            System.out.println("inner: display outer_name = " + outer_name);
            // 静态内部类静态方法访问外部类的静态成员方法
            outer_static_display();
        }
    }
}
//Test
public class Test {
    public static void main(String[] args) {
        // 调用静态内部类的静态方法访问
        OuterDemo04.InnerDemo04.inner_static_display();
        // 实例化静态内部类对象访问
        OuterDemo04.InnerDemo04 inner = new OuterDemo04.InnerDemo04(10);
        inner.inner_display();
    }
}

(2)命名冲突

//外部类
public class OuterDemo04 {
    private static String name = "OuterDemo04";
    public static void display(){
        System.out.println("outer: static display");
    }

    //静态内部类
    public static class InnerDemo04 {
        public String name = "InnerDemo04";
        public void display(){
            System.out.println("inner: display");
        }
        // 命名冲突
        public void inner_test(){
            String name = "inner_test";
            System.out.println(name); // "inner_test"
            System.out.println(this.name); // "InnerDemo04"
            System.out.println(OuterDemo04.name); // "OuterDemo04"
        }
    }
}

2.2 外部类访问内部类

(1)静态成员: 外部类可直接访问静态内部类的静态成员,且可以是任何修饰符修饰的;

(2)非静态成员: 外部类想要访问静态内部类的非静态成员,需要先创建静态内部类实例对象,再通过实例对象去访问,同样也可以是任何修饰符修饰的;

//外部类
public class OuterDemo04 {

    //静态内部类(default)
    static class InnerDemo04 {
        private int a = 10;
        public static String name = "InnerDemo04";

        public void display(){
            System.out.println("inner: display");
        }

        private static void static_display(){
            System.out.println("inner: static display");
        }
    }
    //外部类访问内部类
    public void inner_test(){
        // 外部类访问静态成员变量
        System.out.println(InnerDemo04.name);
        // 外部类访问静态成员函数(private)
        InnerDemo04.static_display();
        // 外部类访问非静态成员(实例化)
        InnerDemo04 inner = new InnerDemo04();
        System.out.println(inner.a); //private
        inner.display();
    }
}

四. 局部内部类

1. 局部内部类的定义与初始化

(1)定义位置: 局部内部类是定义在外部类某一作用域内(如静态代码块、代码块、静态方法、普通方法)的类,它和成员内部类的区别在于局部内部类的访问/可见性仅限于该作用域内,类似于局部变量。这种设计允许将类的定义限制在非常特定的范围中,只在需要的地方创建和使用;

(2)访问修饰符: 局部内部类没有访问修饰符,且不能使用static修饰;局部内部类对外部不可见,即不能从外部类的其他方法域或外部其他类中直接访问;

(3)内容限制:

  • 局部内部类和成员内部类一样不能含有静态成员(JDK16开始允许局部内部类中定义静态变量/方法);
  • 局部内部类可以包含构造函数、非静态方法、非静态成员属性、非静态代码块,且可使用任意访问修饰符修饰成员内容;
  • 局部内部类可以继承父类或实现接口,当作一个完整的类在局部使用;

(4)初始化:局部内部类无访问修饰符,且不能使用static修饰,因此在局部内部类的相应作用域内可以直接通过new对象的方式创建局部内部类的实例对象。

//外部类
public class OuterDemo05 {
    //1. 非静态方法作用域
    public void innerClassDisplay(){
        // 局部内部类
        class InnerDemo05 {
            // 内部类数属性
            public int a = 0; //Static declarations in inner classes are not supported at language level '8'
            // 内部类代码块
            {
                System.out.println("inner: code block run");
            }
            // 内部类构造方法
            public InnerDemo05(int _a){
                a = _a;
                System.out.println("inner: init");
            }
            // 内部类方法
            public void display(){
                System.out.println("inner: display a = " + a);
            }
        }
        //使用局部内部类
        InnerDemo05 inner = new InnerDemo05(10);
        inner.display();
    }
    //2. 静态方法作用域
    public static void innerClassDisplayWithStatic(){
        // 局部内部类
        class InnerDemo05 {
            // 内部类数属性
            public int a = 0;
            // 内部类代码块
            {
                System.out.println("inner: code block run");
            }
            // 内部类构造方法
            public InnerDemo05(int _a){
                a = _a;
                System.out.println("inner: init");
            }
            // 内部类方法
            public void display(){
                System.out.println("inner: display a = " + a);
            }
        }
        //使用局部内部类
        InnerDemo05 inner = new InnerDemo05(10);
        inner.display();
    }
}
//Test
public class Test {
    public static void main(String[] args) {
        // 静态方法
        OuterDemo05.innerClassDisplayWithStatic();
        // 普通方法
        OuterDemo05 outer = new OuterDemo05();
        outer.innerClassDisplay();
    }
}

2. 局部内部类的访问控制

2.1 内部类访问外部类

(1)静态域: 若内部类定义在静态域部分中,则可以访问外部类的静态成员,且可以是任何访问修饰符修饰的;

(2)非静态域: 若内部类定义在非静态域部分中,则可以访问外部类的任何成员(静态、非静态),且可以是任何访问修饰符修饰的;

(3)局部变量: 局部内部类可以访问其所在作用域内的所有局部变量(包括方法形参),前提是这些变量必须是 final 类型(对于引用类型的变量是不允许指向新的对象),即在局部内部类的生命周期内其值不会改变。在Java 8之前,Java要求被局部内部类、匿名内部类访问的局部变量必须使用final修饰,从Java 8开始这个限制取消了,Java 8更加智能:如果局部变量被匿名内部类访问,那么该局部变量相当于会自动使用final修饰。

(4)命名冲突: 局部内部类和成员内部类一样,在命名冲突时会发生隐藏现象,遵循就近原则;

//外部类
public class OuterDemo05 {
    private int a = 10;
    public static String outer_name = "OuterDemo05";
    public void outer_display(){
        System.out.println("outer: display");
    }
    private static void outer_static_display(){
        System.out.println("outer: static display");
    }

    //1. 非静态方法作用域
    public void innerClassDisplay(){
        int a = 20;
        // 局部内部类
        class InnerDemo05 {
            public void inner_display(){
                // 访问局部变量
                System.out.println("inner: local variable a = " + a); // a=20
                // a += 20; //修改报错:Variable 'a' is accessed from within inner class, needs to be final or effectively final
                // 访问外部类成员变量(private)
                System.out.println("inner: display a = " + OuterDemo05.this.a); //a=10
                // 访问外部类成员函数
                outer_display();
                // OuterDemo05.this.outer_display(); // 等价
                // 访问外部类静态成员变量
                System.out.println("inner: display outer name = " + outer_name);
                // 访问外部类静态成员函数(private)
                OuterDemo05.outer_static_display();
            }
        }
        //使用局部内部类
        InnerDemo05 inner = new InnerDemo05();
        inner.inner_display();
    }
    //2. 静态方法作用域
    public static void innerClassDisplayWithStatic(){
        // 局部内部类
        class InnerDemo05 {
            public void inner_display(){
                // 无法访问非静态成员
                // System.out.println("inner: display a = " + a); // Non-static field 'a' cannot be referenced from a static context
                // 访问外部类静态成员变量
                System.out.println("inner: display outer name = " + outer_name);
                // 访问外部类静态成员函数(private)
                OuterDemo05.outer_static_display();
            }
        }
        //使用局部内部类
        InnerDemo05 inner = new InnerDemo05();
        inner.inner_display();
    }
}

2.2 外部类访问内部类

外部类在相应的作用域中,可以通过直接创建局部内部类的实例对象来访问局部内部类中的内容,且可以是任何访问修饰符修饰的内部类成员。

//外部类
public class OuterDemo05 {
    //非静态方法作用域
    public void innerClassDisplay(){
        // 局部内部类
        class InnerDemo05 {
            private int a = 10;
            private void inner_display(){
                System.out.println("inner display");
            }
        }
        //使用局部内部类
        InnerDemo05 inner = new InnerDemo05();
        //访问局部内部类任意成员
        System.out.println("inner a = " + inner.a);
        inner.inner_display();
    }
}

五. 匿名内部类

1. 匿名内部类的定义与初始化

匿名内部类没有显式名称,且定义时直接省略类名,即在定义时不需要使用 class 关键字为其命名。匿名内部类是一种内部类的简化写法,其实例化与定义同时进行(在创建匿名内部类对象的同时也完成了类的定义),通常伴随着 new 关键字一起使用,用于创建并初始化一个该内部类的实例,这种“创建即使用”的特性使得匿名内部类适用于那些只需一次性使用的场景。

(1)定义位置: 可以定义在外部类的任意位置(如静态变量、成员变量、静态代码块、代码块、静态方法、普通方法中),类和成员都不能使用static修饰;

(2)访问修饰符: 匿名内部类没有访问修饰符,匿名内部类只能在声明它的方法或代码块内部使用,因为它没有类名所以也无法在其他地方被引用。

(3)内容限制:

  • 匿名内部类可拥有非静态成员(非静态方法、非静态成员属性、非静态代码块),且成员可使用任何修饰符修饰,但不能拥有静态成员(和局部内部类一样);
  • 由于匿名内部类没有类名,因此无法自定义构造函数,实际上JDK为匿名内部类默认生成了构造函数;如果想实现构造方法的一些初始化功能,可以通过代码块实现;
  • 同样因为匿名内部类没有类名,所以它无法其他类继承或实现,但匿名内部类通常用于快速继承父类(可以是普通类或抽象类)或实现接口;

(4)使用特点:

  • 继承或实现: 匿名内部类本质上也是一个内部类,但由于其实例化与定义同时进行的特性,它不会像其他内部类一样事先定义内部类模板,因此匿名内部类的实例化只能借助于其他现有的类,所以只能被用于继承普通类、抽象类或实现接口,且只能继承一个类或实现一个接口;注意必须实现所有方法,匿名内部类不能是抽象类;
  • 无名称: 由于匿名内部类在定义时省略类名,因此在实际使用时要么一次性创建并立即使用,要么通过父类/接口的类型进行多态接收;
  • 多态: 在通过父类/接口多态接收匿名内部类实现的子类实例时,只能调用父类/接口中的父类方法/重写方法,而无法访问到子类自己实现的方法;这种多态的限制使得匿名内部类常用于快速实现抽象类或接口的方法重写,当然对于普通类或有默认实现的抽象方法,也可以不重写父类方法;

1.1 定义

new 父类构造器或接口() {
    // 定义类体
    // 包含方法、属性等
};
//普通类
class OuterNormalClass {
    int a = 10;
    void display() {
        System.out.println("outer normal class display.");
    }
}
//抽象类
abstract class OuterAbstractClass {
    abstract void display();
}
//接口
interface OuterInterface {
    void display();
}

public class Test {
    public static void main(String[] args) {
        //1. 继承普通类
        //(1)多态接收(下同)
        OuterNormalClass inner_normal = new OuterNormalClass() {
            // 定义属性
            private int b = 20;
            // 重写父类方法: 如果重写方法为非必要的,原则上是可以没有重写方法部分的(该处不重写也可以,直接使用父类方法)
            @Override
            public void display(){
                System.out.println("extends-normal inner class display.");
            }
            // 定义子类新方法
            public void printVal(){
                System.out.println("extends-normal inner class print val = " + b);
            }
        };
        inner_normal.display(); //调用重写方法
        System.out.println(inner_normal.a); //访问父类属性
        // inner_normal.printVal(); //报错:多态特性,父类指针调用不到子类定义的新方法
        //(2)创建并使用
        new OuterNormalClass() {
            // 定义属性
            private int b = 20;
            // 重写父类方法
            @Override
            public void display(){
                System.out.println("extends-normal inner class display.");
            }
            // 定义子类新方法
            public void printVal(){
                System.out.println("extends-normal inner class print val = " + b);
            }
        }.printVal();
        //2. 继承抽象类:匿名内部类不能是抽象类,必须实现所有抽象方法
        OuterAbstractClass inner_abstract = new OuterAbstractClass() {
            // 实现父类抽象方法
            @Override
            void display() {
                System.out.println("extends-abstract inner class display.");
            }
        };
        inner_abstract.display();
        //3. 实现接口: 必须实现所有接口抽象方法
        OuterInterface inner_interface = new OuterInterface() {
            // 实现接口抽象方法
            @Override
            public void display() {
                System.out.println("implements-interface inner class display.");
            }
        };
        inner_interface.display();
    }
}

1.2 初始化

匿名内部类实例化与定义同时进行,即在创建匿名内部类对象的同时也完成了类的定义,匿名内部类的初始化使用场景包括(以线程Thread为例):

  • 多态接收:通过多态接收实例
  • 一次性使用:初始化并立即使用
  • 参数传递:作为参数传递
public class Test {
    public static void main(String[] args) {
        //1. 多态接收
        Thread t = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++){
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("[1] : " + i);
                }
            }
        };
        t.start();
        //2. 一次性使用
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++){
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("[2] : " + i);
                }
            }
        }.start();
        //3. 参数传递(Runnable接口)
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++){
                    try {
                        Thread.sleep(1000); //注意
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("[3] : " + i);
                }
            }
        }).start();
    }
}

2. 匿名内部类的访问控制

匿名内部类的访问控制与局部内部类基本一致,局部内部类的所有访问限制都对其生效,具体情况如下:

  • 静态域: 若定义在静态部分中,可以访问外部类静态的成员,可以是任何修饰符修饰的;
  • 非静态域: 若定义在非静态部分中,可以访问外部类任何的成员(静态、非静态),可以是任何修饰符修饰的;
  • 自定义成员:外部类想要访问匿名内部类的成员,需要能拿到匿名内部类实例对象,再通过实例对象去访问;若通过多态接收则只能访问匿名内部类父类的原有成员(属性、方法),对于匿名内部类中新定义的成员还是无法访问;

六. 深入理解内部类

不论任何类型的内部类,外部类和内部类在编译之后会生成两个独立的不同的class文件,并会使用‘$’符号进行分割命名,具体命名规则如下:

  • 成员内部类:外部类名$内部类名.class
  • 静态内部类:外部类名$内部类名.class
  • 局部内部类:外部类名$编号+内部类名.class
  • 匿名内部类:外部类名$编号.class

在该部分调试过程中,我们使用JDK自带的反编译工具 javap 来进行调试,其具体使用基本命令格式为:javap -c ,其中参数含义为:

  • -c 对生成java字节码进行反汇编/编译

1. 为什么内部类可访问外部类任意成员

1.1 成员内部类、非静态域的局部内部类、匿名内部类

成员内部类以及非静态域的局部内部类和匿名内部类,在编译时会在内部类字节码文件中隐式生成并持有一个指向外部类的引用成员变量this,并在初始化时传入引用参数,这也是为什么我们通过OuterClass.this能够访问到外部类的原因。

D:\codes\javaCodes\JDK8_Feature\innerclass_demo\target\classes\com\study>javap -c OuterDemo02$InnerDemo02.class
Compiled from "OuterDemo02.java"
class com.study.OuterDemo02$InnerDemo02 {
  public int a;

  final com.study.OuterDemo02 this$0;// 隐式生成并持有外部类的引用this$0,内部类中可以使用OuterDemo02.this关键字访问

  public com.study.OuterDemo02$InnerDemo02(com.study.OuterDemo02, int); // 构造函数传入引用参数
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Lcom/study/OuterDemo02;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: aload_0
      10: iconst_1
      11: putfield      #3                  // Field a:I
      14: aload_0
      15: iload_2
      16: putfield      #3                  // Field a:I
      19: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: ldc           #5                  // String inner: init
      24: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: return

  public void display();
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #7                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #9                  // String inner: display a=
      12: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: aload_0
      16: getfield      #3                  // Field a:I
      19: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      22: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      25: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      28: return
}

1.2 静态内部类、静态域的局部内部类、匿名内部类

静态内部类以及静态域的局部内部类和匿名内部类,在编译时则不会生成并持有指向外部类的引用成员变量,因此它们只能访问外部类的静态成员。

D:\codes\javaCodes\JDK8_Feature\innerclass_demo\target\classes\com\study>javap -c OuterDemo04$InnerDemo04.class
Compiled from "OuterDemo04.java"
//不会生成并持有外部类的引用,因此只能访问外部类的静态成员
class com.study.OuterDemo04$InnerDemo04 {
  public static java.lang.String name;

  com.study.OuterDemo04$InnerDemo04();
    Code:
       0: aload_0
       1: invokespecial #3                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        10
       7: putfield      #1                  // Field a:I
      10: return

  public void display();
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #5                  // String inner: display
       5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

2. 为什么局部/匿名内部类只能访问局部final变量

(1)生命周期不一致: 当方法执行完毕之后,局部变量的生命周期理论上就结束了,而此时局部/匿名内部类对象的生命周期很可能还没有结束,那么内部类中继续访问局部变量就变成不可能了,为了避免这种生命周期不一致的问题,局部/匿名内部类中的访问的局部变量实际上是外部变量的复制(拷贝引用),二者本质上并不是同一个数据。

(2)数据不一致: 在拷贝引用中(以基本数据类型为例),内部类中若对数据进行了修改,而此时外部类方法中的局部变量值是不会发生改变的,这就导致了数据不一致的问题,所以为了保持参数的一致性,就规定要使用final来避免数据的改变。

(3)特殊情况: 对于特殊的对象引用来说,虽然不能直接修改引用指向的地址值,但是可以通过该引用修改外部类方法中对应对象实例的属性值,这种方式虽然代码实现上可行,但逻辑层面上并不建议,不利于维护代码的可读性。

3. 为什么外部类可访问内部类任意成员

对于内部类的private成员(属性、方法)来说,在编译时会在内部类字节码文件中隐式生成对应的access方法函数,用于对外部类提供访问入口,因此外部类可访问内部类的任意成员。

//外部类
public class OuterDemo03 {

    //内部类
    class InnerDemo03 {
        // private 属性
        private int a = 20;
        // private 方法
        private void inner_display() {
            System.out.println("inner private display");
        }
    }
}
D:\codes\javaCodes\JDK8_Feature\innerclass_demo\target\classes\com\study>javap -c OuterDemo03$InnerDemo03.class
Compiled from "OuterDemo03.java"
class com.study.OuterDemo03$InnerDemo03 {
  final com.study.OuterDemo03 this$0;

  com.study.OuterDemo03$InnerDemo03(com.study.OuterDemo03);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #3                  // Field this$0:Lcom/study/OuterDemo03;
       5: aload_0
       6: invokespecial #4                  // Method java/lang/Object."<init>":()V
       9: aload_0
      10: bipush        20
      12: putfield      #2                  // Field a:I
      15: return
  // 隐式生成 private a 的 get 方法 access$000
  static int access$000(com.study.OuterDemo03$InnerDemo03);
    Code:
       0: aload_0
       1: getfield      #2                  // Field a:I
       4: ireturn
  // 隐式生成 private a 的 set 方法 access$002
  static int access$002(com.study.OuterDemo03$InnerDemo03, int);
    Code:
       0: aload_0
       1: iload_1
       2: dup_x1
       3: putfield      #2                  // Field a:I
       6: ireturn
  // 隐式生成 private inner_display 的访问方法access$100
  static void access$100(com.study.OuterDemo03$InnerDemo03);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method inner_display:()V
       4: return
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React 学习——行内样式、外部样式、动态样式

三种样式的写法 import "./index.css"; //同级目录下的样式文件 function App() {const styleCol {color: green,fontSize: 40px}// 动态样式const isBlock false;return (<div className"App">{/* 行内样式 */}<span style{{color:red,fontSiz…

FreeModbus学习——eMBPoll轮询

FreeModbus版本&#xff1a;1.6 eMBPoll在mb.c文件中 eMBPoll 函数是一个核心的 Modbus 协议栈事件处理函数&#xff0c;负责接收和发送帧&#xff0c;处理不同的事件&#xff0c;并根据需要返回错误码。 eMBErrorCode eMBPoll( void ) {static UCHAR *ucMBFrame; …

zabbix添加钉钉告警机器人使用bash和python两种脚本

zabbix添加钉钉告警机器人使用bash和python两种脚本 查看脚本目录 vi /etc/zabbix/zabbix_server.conf# 脚本存放路径 AlertScriptsPath/usr/lib/zabbix/alertscripts编写脚本&#xff08;二选一&#xff09; bash脚本 编写脚本 cd /usr/lib/zabbix/alertscripts vi zabbi…

00 RabbitMQ:前言

00 RabbitMQ&#xff1a;前言 1. 前言1.1. 举个&#x1f330;&#xff1a;快递案例1.1.1. 过程对比1.1.2. 延伸到程序中 1.2. 举个&#x1f330;&#xff1a;订单案例1.2.1. 流程1.2.2. 耦合1.2.3. 响应时间1.2.4. 并发压力1.2.5. 系统结构弹性 1.3. 总结 1. 前言 1.1. 举个&a…

机器人开源调度系统OpenTCS-6最新版本地源码运行

OpenTCS 项目使用 Gradle 而不是 Maven&#xff0c;那么需要使用 Gradle 来导入和构建项目。在 IntelliJ IDEA 中导入和运行使用 Gradle 的项目&#xff0c;可以按照以下步骤进行操作&#xff1a; 克隆 OpenTCS 源码 首先&#xff0c;克隆 OpenTCS 的源码到本地。您可以使用以…

2. 深度学习的项目流程(批量化打包数据、构建模型、训练模型、波士顿房价预测、激活函数、多层感知机)

深度学习流程 1. 深度学习基本流程1.1 流程图1.2 代码实现1.3 基本概念 2. 深度学习项目流程2.1 批量化打包数据2.2 构建模型2.3 训练模型&#xff08;1&#xff09;筹备训练&#xff08;2&#xff09;开始训练 2.4 模型推理 3. 深度学习实现波士顿房价预测3.1 数据读取、切分、…

Visio绘制的Sigmoid激活函数结构图,可导出高清图片,可修改,无水印。

Visio绘制的Sigmoid激活函数结构图,可导出高清图片&#xff0c;可修改&#xff0c;无水印。 方便用于小论文写作&#xff0c;方便用于毕业设计。 Visio版本为2021版&#xff0c;可用更高版本打开。 下载地址&#xff1a;地址 图片展示&#xff1a;

对递归的一些理解。力扣206题:翻转链表

今天在刷力扣的时候&#xff0c;在写一道翻转链表的题目的过程中&#xff0c;在尝试使用递归解决该问题的时候&#xff0c;第一版代码却每次都返回的是null&#xff0c;这个错误让我尝试去debug了一下&#xff0c;最终找出了问题&#xff0c;并且让我对递归有了一些更深的理解&…

Windows下帆软BI(finebi)单机部署移植(Tomcat)攻略

一、基础环境 操作系统&#xff1a;Windows 10 64bit 帆软BI 版本&#xff1a;V9.0/V10.0 HTTP工具&#xff1a;Tomcat 外置数据库&#xff1a;Oracle 11g 实验内容&#xff1a;将已经部署好的帆软BI从一台电脑移植到另一台电脑 二、前期准备 1、做好外置数据库移植&…

STM32CUBEIDE FreeRTOS操作教程(一):LED闪灯

STM32CUBEIDE FreeRTOS操作教程&#xff08;一&#xff09;&#xff1a;LED闪灯 STM32CUBEIDE(不是STM32CUBEMX)开发环境集成了STM32 HAL库进行FreeRTOS配置和开发的组件&#xff0c;不需要用户自己进行FreeRTOS的移植。这里介绍最简化的用户操作类应用教程。以STM32F401RCT6开…

JCR一区级 | Matlab实现GA-Transformer-LSTM多变量回归预测

JCR一区级 | Matlab实现GA-Transformer-LSTM多变量回归预测 目录 JCR一区级 | Matlab实现GA-Transformer-LSTM多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.【JCR一区级】Matlab实现GA-Transformer-LSTM多变量回归预测&#xff0c;遗传优化算法&#…

Vue3--

一、pinia &#xff08;集中式状态&#xff08;数据&#xff09;管理&#xff09; 1、准备一个效果 2、存储读取数据 3、修改数据三种方式 4、storeToRefs 5、getters 当state中的数据&#xff0c;需要经过处理后在使用时&#xff0c;可以使用getters配置 6、$subscribe的使用…

QT总结——图标显示坑

最近写代码遇到一个神仙大坑&#xff0c;我都怀疑我软件是不是坏了&#xff0c;这里记录一下。 写qt工程的时候我们一般会设置图标&#xff0c;这个图标是窗体的图标同时也是任务栏的图标&#xff0c;但是我发现生成的exe没有图标&#xff0c;这个时候就想着给他加一个图标&…

Linux(CentOS)的“应用商城” —— yum

Linux&#xff08;CentOS&#xff09;的“应用商城” —— yum 关于 yum 和软件包Linux 系统&#xff08;CentOS&#xff09;的生态yum 相关操作yum 本地配置yum 安装 lrzsz.x86_64 关于 yum 和软件包 首先 yum 是软件下载安装管理的客户端&#xff0c;类似各种手机里的“应用…

OpenAI发布GPT-4 Mini的深度分析及中国大模型的弯道超车机会

引言 在OpenAI封禁中国IP访问其API后&#xff0c;紧接着推出了GPT-4 Mini&#xff0c;这是一个引发广泛关注和讨论的新举措。此举不仅让人们质疑OpenAI的战略方向&#xff0c;更引发了对中国大模型是否能弯道超车的讨论。本文将详细分析GPT-4 Mini的特点、市场影响及中国大模型…

eclipse中的classbean导入外部class文件,clean项目后删除问题

最近被eclipse搞得头疼&#xff0c;下午终于解决 eclipse创建的java项目中&#xff0c;类的输出目录是classbean。由于项目需要&#xff0c;classbean目录下已经导入了外部的类&#xff0c;但每次clean项目时&#xff0c;会把class删掉。 广泛查询&#xff0c;eclipse不清空c…

jenkins删除历史构建记录

1、 登录jenkins&#xff0c;进入【Manage Jenkins】-【Script Console】&#xff0c;输入&#xff1a; def jobName "Test" //删除的项目名称 def maxNumber 60 // 保留的最小编号&#xff0c;意味着小于该编号的构建都将被删除 Jenkins.instance.getItemByFullN…

javascript鼠标跟随星星动画特效

鼠标跟随星星动画特效https://www.bootstrapmb.com/item/14781 创建一个鼠标跟随的星星动画特效在JavaScript中通常涉及到HTML、CSS和JavaScript的结合。以下是一个简单的步骤说明和示例代码&#xff0c;用于创建一个基本的鼠标跟随星星动画&#xff1a; HTML (index.html) ht…

从理论到实践:如何用 TDengine 打造完美数据模型​

在用 TDengine 进行数据建模之前&#xff0c;我们需要回答两个关键问题&#xff1a;建模的目标用户是谁&#xff1f;他们的具体需求是什么&#xff1f;在一个典型的时序数据管理方案中&#xff0c;数据采集和数据应用是两个主要环节。如下图所示&#xff1a; 对于数据采集工程师…

大数据-44 Redis 慢查询日志 监视器 慢查询测试学习

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…