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等),其可访问性/可见性对于外部类来说不受影响,外部类可以像访问类内其他成员一样访问、创建内部类实例,且不用加外部类前缀声明,具体使用场景如下:
- 作为成员属性:可直接访问并初始化;
- 在非静态方法:可直接访问并初始化;
- 在静态方法:与在外部类外访问成员内部类一样,需要先进行外部类实例化;
//外部类
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等),其可访问性/可见性对于外部类来说不受影响,外部类可以像访问类内其他成员一样访问、创建内部类实例,且不用加外部类前缀声明,具体使用场景如下:
- 作为成员属性:可直接访问并初始化;
- 在非静态方法:可直接访问并初始化;
- 在静态方法:与在外部类外访问静态内部类一样,由于不依赖外部类所以可直接创建静态内部类的实例对象;
//外部类
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
}