Java基础(四) 内部类详解

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.InnerDemo01inner.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) {// 实例化方法1OuterDemo02 outer1 = new OuterDemo02();OuterDemo02.InnerDemo02 inner1 = outer1.new InnerDemo02(10);// 实例化方法2OuterDemo02.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.thisSystem.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); // 30System.out.println("inner a=" + this.a); // 20System.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); //privateinner.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;// 重写父类方法: 如果重写方法为非必要的,原则上是可以没有重写方法部分的(该处不重写也可以,直接使用父类方法)@Overridepublic 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;// 重写父类方法@Overridepublic 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() {// 实现父类抽象方法@Overridevoid display() {System.out.println("extends-abstract inner class display.");}};inner_abstract.display();//3. 实现接口: 必须实现所有接口抽象方法OuterInterface inner_interface = new OuterInterface() {// 实现接口抽象方法@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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_01: aload_12: putfield      #1                  // Field this$0:Lcom/study/OuterDemo02;5: aload_06: invokespecial #2                  // Method java/lang/Object."<init>":()V9: aload_010: iconst_111: putfield      #3                  // Field a:I14: aload_015: iload_216: putfield      #3                  // Field a:I19: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;22: ldc           #5                  // String inner: init24: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V27: returnpublic void display();Code:0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;3: new           #7                  // class java/lang/StringBuilder6: dup7: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V10: ldc           #9                  // String inner: display a=12: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;15: aload_016: getfield      #3                  // Field a:I19: 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;)V28: 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_01: invokespecial #3                  // Method java/lang/Object."<init>":()V4: aload_05: bipush        107: putfield      #1                  // Field a:I10: returnpublic void display();Code:0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #5                  // String inner: display5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: 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_01: aload_12: putfield      #3                  // Field this$0:Lcom/study/OuterDemo03;5: aload_06: invokespecial #4                  // Method java/lang/Object."<init>":()V9: aload_010: bipush        2012: putfield      #2                  // Field a:I15: return// 隐式生成 private a 的 get 方法 access$000static int access$000(com.study.OuterDemo03$InnerDemo03);Code:0: aload_01: getfield      #2                  // Field a:I4: ireturn// 隐式生成 private a 的 set 方法 access$002static int access$002(com.study.OuterDemo03$InnerDemo03, int);Code:0: aload_01: iload_12: dup_x13: putfield      #2                  // Field a:I6: ireturn// 隐式生成 private inner_display 的访问方法access$100static void access$100(com.study.OuterDemo03$InnerDemo03);Code:0: aload_01: invokespecial #1                  // Method inner_display:()V4: return
}

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

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

相关文章

Modbus转BACnet/IP网关的技术实现与应用

引言 随着智能建筑和工业自动化的快速发展&#xff0c;不同通信协议之间的数据交换也变得日益重要。Modbus和BACnet/IP是两种广泛应用于自动化领域的通信协议&#xff0c;Modbus以其简单性和灵活性被广泛用于工业自动化&#xff0c;而BACnet/IP则在楼宇自动化系统中占据主导地…

Android APP 音视频(03)CameraX预览与MediaCodec编码

说明&#xff1a; 此CameraX预览和编码实操主要针对Android12.0系统。通过CameraX预览获取yuv格式数据&#xff0c;将yuv格式数据通过mediacodec编码输出H264码流&#xff08;使用ffmpeg播放&#xff09;&#xff0c;存储到sd卡上。 1 CameraX 和 MediaCodec简介 1.1 CameraX…

“微软蓝屏”事件,给IT行业带来的宝贵经验和教训

“微软蓝屏”事件是指2024年7月19日发生的一次全球性技术故障&#xff0c;主要涉及微软视窗&#xff08;Windows&#xff09;操作系统及其相关应用和服务。 以下是对该事件的详细解析&#xff1a; 一、事件概述 发生时间&#xff1a;2024年7月19日事件影响&#xff1a;全球多个…

【科学文献计量】中国知网(CNKI) 文献素材库生成软件详细使用说明

CNKI 文献素材库生成软件制作 1 背景2 使用步骤2.1 文献检索2.2 文献导出2.3 软件生成1 背景 在进行中文文献的综述时,往往是要借助中国知网(CNKI)文献检索平台,写作插入文献时会用Endnote软件进行辅助。因此就有需求:对于CNKI检索的结果直接导出到本地,第一是方便快速阅…

基于STM32的农业大棚温湿度采集控制系统的设计

目录 1、设计要求 2、系统功能 3、演示视频和实物 4、系统设计框图 5、软件设计流程图 6、原理图 7、主程序 8、总结 &#x1f91e;大家好&#xff0c;这里是5132单片机毕设设计项目分享&#xff0c;今天给大家分享的是智能教室。 设备的详细功能见网盘中的文章《8、基…

“机器说人话”-AI 时代的物联网

万物互联的物联网愿景已经提了许多年了&#xff0c;但是实际效果并不理想&#xff0c;除了某些厂商自己的产品生态中的产品实现了互联之外&#xff0c;就连手机控制空调&#xff0c;电视机和调光灯都没有实现。感觉小米做的好一点&#xff0c;而华为的鸿蒙的全场景&#xff0c;…

C#+layui+echarts实现动态生成折线图

概要 C#layuiecharts实现动态生成折线图 整体架构流程 后端是c#语言编写的业务流程,前端是layui和echarts 技术细节 1.先看echarts折线图需要什么样子的数据,在想后端怎么处理 2.后端代码 List<ValveTempData> list new List<ValveTempData>(); string …

Docker容器逃逸漏洞-CVE-2024-21626

Snyk 在 Docker 引擎以及其他容器化技术(例如 Kubernetes)使用的 runc <=1.1.11 的所有版本中发现了一个漏洞。利用此问题可能会导致容器逃逸到底层主机操作系统,无论是通过执行恶意映像还是使用恶意 Dockerfile 或上游映像构建映像(即使用时FROM) CVE-2024-21626原理…

生成式人工智能之路,从马尔可夫链到生成对抗网络

人工智能&#xff08;Artificial intelligence&#xff0c;AI&#xff09;技术在过去几年中取得了显著进展&#xff0c;其中生成式AI&#xff08;Generative AI&#xff09;因其强大的内容生成能力而备受关注。生成式AI可以创建新的文本、图像、音频、视频、代码以及其他形式的…

SSRF学习笔记

1.NAT学习 Nat&#xff08;Network Address Translation&#xff0c;网络地址转换&#xff09;是 一种网络通信技术主要用于将私有网络中的内部IP地址转换成公共网络中的公共IP地址&#xff0c;以实现局域网内部设备访问互联网的功能。具体来说&#xff0c;Nat有以下几个主要…

Matlab画不同指标的对比图

目录 一、指标名字可修改 二、模型名字可修改 三、输入数据可修改 软件用的是Matlab R2024a。 clear,clc,close all figure1figure(1); % set(figure1,Position,[300,100,800,600],Color,[1 1 1]) axes1 axes(Parent,figure1);%% Initialize data points 一、指标名字可修…

MongoDB 学习笔记

一、简介 1、MongoDB 是什么 MongoDB 是一个基于分布式文件存储的数据库&#xff0c;官方地址 https://www.mongodb.com/ 2、数据看是什么 数据库&#xff08;DataBase&#xff09;是按照数据结构来组织、存储和管理数据的应用程序。 3、数据库的作用 主要作用是 管理数据…

mybatis中的缓存(一级缓存、二级缓存)

文章目录 前言一、MyBatis 缓存概述二、一级缓存1_初识一级缓存2_一级缓存命中原则1_StatementId相同2_查询参数相同3_分页参数相同4_sql 语句5_环境 3_一级缓存的生命周期1_缓存的产生2_缓存的销毁3_网传的一些谣言 4_一级缓存核心源码5_总结 三、二级缓存1_开启二级缓存2_二级…

基于Hutool实现自定义模板引擎,实现json个性化模板引擎转换

文章目录 前言编写引擎类&#xff1a;JsonTemplateEngine编写模板类&#xff1a;CustomTemplate编写测试代码测试json文件测试类 前言 由于百度搜索json模板引擎&#xff0c;推荐的都是一些freemarker之类的&#xff0c;需要引入其他的依赖&#xff0c;而且在编写json模板的时…

学习在测试时学习(Learning at Test Time)_ 具有表达性隐藏状态的循环神经网络(RNNs)

摘要 https://arxiv.org/pdf/2407.04620 自注意力机制在长文本语境中表现良好&#xff0c;但其复杂度为二次方。现有的循环神经网络&#xff08;RNN&#xff09;层具有线性复杂度&#xff0c;但其在长文本语境中的性能受到隐藏状态表达能力的限制。我们提出了一种新的序列建模…

LabVIEW多线圈电磁式振动发电机测试

开发了一种基于LabVIEW设计的多线圈电磁式振动发电机测试系统。系统通过高效的数据采集、波峰检测及相位差计算&#xff0c;优化了传统振动发电机的测试流程&#xff0c;提升了电压波形分析的精度和效率&#xff0c;具有较好的应用前景和推广价值。 项目背景 随着可再生能源技…

遇到not allow unquoted fieldName怎么办

前言 Exception in thread "main" com.alibaba.fastjson2.JSONException: not allow unquoted fieldName, offset 2, character , line 1, column 3, fastjson-version 2.0.25 { "data":null, "code":200, "msg":"成功"…

LIS系统源码,实验室管理信息系统LIS,.Net C#语言开发,支持DB2,Oracle,MS SQLServer等主流数据库

实验室管理信息系统LIS源码&#xff0c;采用.Net C#语言开发&#xff0c;C/S架构。支持DB2&#xff0c;Oracle&#xff0c;MS SQLServer等主流数据库。&#xff08;LIS系统全套商业源码&#xff0c;自主版权&#xff0c;多家大型综合医院应用案例&#xff0c;适合二次开发&…

Golang | Leetcode Golang题解之第279题完全平方数

题目&#xff1a; 题解&#xff1a; // 判断是否为完全平方数 func isPerfectSquare(x int) bool {y : int(math.Sqrt(float64(x)))return y*y x }// 判断是否能表示为 4^k*(8m7) func checkAnswer4(x int) bool {for x%4 0 {x / 4}return x%8 7 }func numSquares(n int) i…

四、GD32 MCU 常见外设介绍 (6) ADC 模块介绍

6.1.ADC 基础知识 12 位逐次逼近式模数转换器模块&#xff08;ADC&#xff09;&#xff0c;可以采样来自于外部输入通道、内部输入通道的模拟信号&#xff0c;采样转换后&#xff0c;转换结果可以按照最低有效位对齐或最高有效位对齐的方式保存在相应的数据寄存器中。 6.2.GD…