您现在的位置是:首页 > 文章详情

【Java核心技术卷】深入理解Java的内部类

日期:2019-11-11点击:305

通过图示进行分析:
在这里插入图片描述
该图展示了Java内部类的编译解释过程. 你会看到整个过程很繁琐.

因为历史原因, Java语言规范 和字节码语言规范有不重叠的部分, 最初的时候,它们是重叠的. 但是后来随着Java的发展,有新的东西需要加入,比如说泛型,但是字节码语言规范不能够轻易变更,因为这个涉及到兼容问题. 能够动的只有编译器, 通过编译器把Java源程序编译成满足字节码语言规范的字节码文件进行执行.今天的内部类 和泛型一样,也是后来加入的. 关于内部类的实现,其实编译器在后面做了很多很多的东西, 虽然内部类有被Lambda表达式取代的趋势(Lambda表达式有自己的解释器),但是还是需要五深入理解的.

直接介绍凝练点:
1.Java源程序要遵循Java语言规范,Java编译器按照Java语言规范来编译Java源程序。
2.字节码程序要遵循字节码语言规范,JVM的Java解释器/JIT编译器按照字节码语言规范来解释/编译运行字节码程序。
3.Java语言与字节码语言是两种不同的语言。它们的语言规范有相同处,也存在不同处
例如:

  • 相同处:均有访问控制符。一个类内部的私有成员只能被该类的其它成员所访问,其它类无法访问该类的私有成员。
  • 不同处:
    1)内部类是Java语言的规范,字节码规范中无内部类指令,JVM对内部类一无所知。

2)在Java规范中一个类中不允许定义函数签名完全相同的两个方法,与返回类型无关。在字节码规范中一个类中允许定义函数签名完全相同而返回类型不同的两个方法。

4.Java编译器
打个比方,Java语言如同中文,字节码语言如同英文,Java编译器如同一个翻译。这个翻译,不仅要把符合中文语言规范的中文翻译成符合英文语言规范的英文(这个翻译是知道中文语法和英文语法的差异的),而且翻译出来的英文在意思上和中文在意思上应该完全相同。
a)按照Java语言规范编译Java源程序
b)生成的字节码程序必须满足字节码语言规范
c)生成的字节码程序必须严格完成Java源程序的功能和安全

由于字节码规范中无内部类指令,内部类是Java语言的规范。所以,Java编译器既要按照内部类的Java语法来编译源程序,又要按照无内部类的字节码语法来生成字节码程序,同时生成的无内部类的字节码程序要完成有内部类的源程序的功能。所以编译器是意译,而不是直译。
一定要记住,运行的程序是字节码程序而不是Java源程序,内存模型是JVM虚拟机运行字节码的结果。程序员一定要编写出符合Java语言规范的高质量可维护的、可扩展的源程序,对字节码和内存模型的理解,可以加深程序员对Java编译器、Java解释器,特别是对Java源程序的理解。即加深对源程序的运行过程,过程的深度和细节的理解,更好的完成源程序的功能和安全。

在Java语言中,内部类是指在一个外部类的内部再定义一个类。内部类可以是静态static的,也可用public,default,protected和private修饰。(而外部顶级类只能使用public和default,外部顶级类无静态static)。而在字节码语言中,只有类的概念,没有外部类和内部类的概念,类只能使用public和default进行访问控制。

注意:内部类是一个编译器现象,JVM虚拟机并不知道内部类与常规类有什么不同。对于源文件中一个名为Outer的外部类和其内部定义的名为Inner的内部类,编译成功后会出现Outer.class和Outer$Inner.class两个字节码文件以及文件中的Outer和$Inner两个类,编译器将会把内部类翻译成用$(美元符号)分隔外部类名与内部类名的独立的常规类名,而虚拟机则对此一无所知,虚拟机在运行的时候,也是把Outer$Inner作为一种常规类来处理的。所以内部类的成员变量/方法名可以和外部类的相同。
Java内部类语法描述的是内部类和外部类的关系,而与外部类的父类无关(因为编译器生成的字节码程序必须严格完成Java源程序的功能和安全,要修改外部类,而外部类的父类可能是第三方类,是无法修改的)。

使用外部类的主要原因:
1、内部类一般只为其外部类使用。
2、内部类方法可以访问该内部类定义所在的外部类中的数据,包括私有数据。内部类提供了某种进入外部类的窗户。
3、 安全)访问控制:内部类可以对同一个包中的其它类隐藏起来。
4、 命名控制:尽管内部类重名,但是它们的外部类不重名,它们编译后的类名是一个合成名。所以可以避免内部类重名和多义。
5、 当定义一个事件监听器和其它回调函数时,可使用匿名内部类。
6、 也是最吸引人的原因,每个内部类都能独立地继承一个接口,而无论外部类是否已经继承了某个接口。因此,内部类使多重继承的解决方案变得更加完整。

内部类标识符
每个类会产生一个.class文件,文件名即为类名。同样,内部类也会产生这么一个.class文件,但是它的名称却不是内部类的类名,而是有着严格的限制:外围类的名字,加上$,再加上内部类名字。(后面会进行反编译,到时候就能看明白了)


全部的内部类总共有六种:
在这里插入图片描述
我们首先介绍成员内部类:
成员内部类(非静态内部类,member inner class)
在外部类的内部,定义的非静态的内部类,叫成员内部类。
按照Java语言语法规定:
在这里插入图片描述
a) 外部类的所有实例成员对内部类可见。成员内部类的实例对象,有外部类的实例对象的引用,所以可以访问外部类实例的所有成员(包括私有的)。
b) 外部类的静态成员对内部类可见。成员内部类可以访问外部类的所有静态成员(包括私有的)。
c) 外部类对内部类可见。在内部类中可以new生成外部类的实例对象。
在这里插入图片描述
d) 内部类对外部类可见。因为编译后的内部类至少是包内,其构造器至少是包内,所以在外部类中可以new生成内部类的实例对象。
外部类按常规的类访问方式使用内部类,唯一的差别是外部类可以访问成员内部类的所有方法与属性,包括私有方法与属性。
$其他语法规定$
e) 成员内部类可以使用public、protected或private访问修饰符进行访问控制,内部类能够隐藏起来,不为同一包的其它类访问。成员内部类一般当做成员变量设置为private。
f) 成员内部类是非静态的。所以在成员内部类中,不能定义静态字段、静态方法和静态内部类,因为成员内部类需要先创建外部类的实例对象,才能创建自己的对象;但是可以定义非静态字段、非静态方法和非静态内部类。

Java内部类的实例对象有一个隐式引用,它引用了创建该内部类实例对象的外部类的实例对象。通过这个指针可以访问外部类的实例对象的全部状态(包括私有的)。

这个结合了后面的例子 :
成员内部类的实例对象是依附外部类的实例对象而存在的,也就是说,如果要创建成员内部类的实例对象,前提是必须存在一个外部类的实例对象。即在没有外部类的实例对象时,是不能创建内部类的实例对象的. 在创建CowLeg内部类的实例对象之前,必须先创建Cow外部类的实例对象。

CowLeg类的实例对象是在Cow类的方法中创建的。由于CowLeg类是私有的,只有Cow类的方法才能创建CowLeg类的实例对象。如果CowLeg类是公有的,在Cow类的内、外部均可以编写代码:Cow cow = new Cow(); CowLeg cowLeg = cow.new CowLeg();

局部内部类既可以访问自身的数据域,也可以访问创建它的外部类的实例对象的所有数据域. weight可以访问外部类的实例对象的私有数据域。

成员内部类的特殊Java语法规则:
1) OuterClass.this
外部类实力引用的语法格式:OuterClass.this
例如:在内部类实例对象的方法中引用外部类实例对象的私有数据域
System.out.println("本牛腿所在奶牛重:" + Cow.this.weight);
2) outerObject.new InnerClass(construction parameters)
内部类实例对象的构造器语法格式:
outerObject.new InnerClass(construction parameters)
例如:在外部类实例对象的方法中创建部类实例对象
CowLeg cl = this.new CowLeg(1.12, "黑白相间");
3) OuterClass.InnerClass
例如:如果内部类在外部类作用域之外是可见的,则可以这样引用内部类。
Cow.CowLeg //如果CowLeg类在Cow类的外部是可见的

public class Cow { private double weight; // 外部类的两个重载的构造器 public Cow() { } public Cow(double weight) { this.weight = weight; } // 定义一个成员内部类 private class CowLeg { // 成员内部类的两个实例变量 private double length; private String color; // 由于成员内部类的实例的创建依赖于外部类的实例的创建,所以成员内部类不可以包含静态成员 //private static int age; // 成员内部类的两个重载的构造方法 public CowLeg() { } public CowLeg(double length, String color) { this.length = length; this.color = color; } // length、color的setter和getter方法 public void setLength(double length) { this.length = length; } public double getLength() { return this.length; } public void setColor(String color) { this.color = color; } public String getColor() { return this.color; } // 成员内部类的方法 public void info() { System.out.println("当前牛腿颜色是:" + color + ", 高:" + length); // 直接访问外部类的private修饰的成员变量 //weight引用了创建内部类实例对象的外部类的实例对象的私有数据域。 System.out.println("本牛腿所在奶牛重:" + weight); //如果不写Cow.this.,编译器将自动添加 //System.out.println("本牛腿所在奶牛重:" + Cow.this.weight); } } public void test() { //CowLeg类的实例对象是在Cow类的实例方法中创建的。 //所以,在创建CowLeg内部类的实例对象之前,必先创建Cow外部类的实例对象。 CowLeg cl = new CowLeg(1.12, "黑白相间"); //如果不写this.编译器将自动添加。 //CowLeg cl = this.new CowLeg(1.12, "黑白相间"); cl.info(); } public static void main(String[] args) { Cow cow = new Cow(378.9); cow.test(); //CowLeg cl = cow.new CowLeg(1.12, "黑白相间"); //System.out.println("本牛腿所在颜色:" +cl.color); } //被翻译为以下字节码,说明外部类可以访问成员内部类的私有成员。 // System.out.println("本牛腿所在颜色:" + CowLeg.access$100(cl)); } } 

结果:
在这里插入图片描述
反编译:

 编译器编译后的字节码类文件: 1. Cow.class // //编译器在外部类添加了静态方法 Cow.access$0(Cow arg0)。它将返回作为参数传递给它的对象的私有域weight。 //如果内部类不访问外部类的私有字段,将不会在外部类中添加静态方法Cow.access$0(Cow arg0)。 public class Cow { private double weight; public Cow() { } public Cow(double weight) { this.weight = weight; } public void test() { //编译器将CowLeg cl = new CowLeg(1.12, "黑白相间");语句编译为 CowLeg cl = new CowLeg(this, 1.12D, "黑白相间"); cl.info(); } //编译器在外部类添加了静态方法 static double access$0(Cow arg0){ return arg0.weight; } public static void main(String[] args) { Cow cow = new Cow(378.9D); cow.test(); } }

2. Cow$CowLeg.class

//编译器为了在内部类的实例中引用外部类的实例对象,必添加一个附加的实例域Cow this$0(this$0名字是由编译器合成的,在自编写的代码中不应该引用它,因为合成名称可能不同)。 //另外,编译器修改了所有的内部类的构造器,添加了一个引用外部类实例的参数Cow arg0。 //不管内部类是否访问外部类,内部类的构造器是一样的,均有Cow arg0参数。 class Cow$CowLeg { private double length; private String color; //编译器必添加一个附加的实例域Cow this$0 final Cow this$0; //编译器在内部类的构造方法中,必添加一个引用外部类实例的形参Cow arg0 public Cow$CowLeg(Cow arg0) { this.this$0 = arg0; } public Cow$CowLeg(Cow arg0, double length, String color) { this.this$0 = arg0; this.length = length; this.color = color; } public void setLength(double length) { this.length = length; } public double getLength() { return this.length; } public void setColor(String color) { this.color = color; } public String getColor() { return this.color; } public void info() { System.out.println("当前牛腿颜色是:" + this.color + ", 高:" + this.length); System.out.println("本牛腿所在奶牛重:" + Cow.access$0(this.this$0)); } }

是不是一下子清晰明白了!
看一下它的内存对象模型
在这里插入图片描述
附注:成员内部类为什么不能定义静态的属性或者静态方法

//根据成员内部类的定义: //1、首先生成外部类的实例对象 //2、然后生成绑定到外部类实例对象的成员内部类实例对象 //外部类实例对象的生成一定要先于成员内部类实例对象的生成 public class InnerClassDemo { //对于final修饰的成员变量,说明该变量是只读变量,只能进行一次赋值操作,只可能存在一下两种赋值情况,在这两个地方我们必须给它们赋初始值。 //1)声明该成员变量时赋值: // a) 如果表达式的值是常量表达式(返回基本类型数据),则编译器编译时就计算出该表达式的值,知道该值。那么编译器就可能会在编译时期,优化程序。 // b) 如果不是常量表达式(返回引用类型数据),则编译器编译时,就不知道该值。 //2)在构造方法中赋值,运行时赋值,则编译器编译时,就不知道该值。 //实例成员变量被默认初始化为0 int x; //静态成员变量必须被显式初始化,要么在声明时赋值初始化,要么在静态块中初始化。否则,语法错误。 static final int i; static final int j = 2; //静态块,当类被JVM加载到内存时,静态块的静态代码执行。 static{ i = 1; System.out.println("i = " + i); System.out.println("j = " + j); } public static void main(String[] args) { System.out.println("welcom!"); } class InnerClass{ //1、在成员内部类中,只有编译器在编译的时候赋值号右边是常量表达式(编译时,可以计算出表达式的基本类型值), // 左边是只读静态常量的情况才可以存在静态只读常量 // 然后编译器把他当做编译期常量来使用,其实说白了就是和这个外部类的实例无关。 //static final int i = 50; //static final String str = "s"; //2、以下均不可以: //2.1 虽然也为static final只读静态变量,但是是在构造方法中运行时赋值,编译时并不知道其值。 //static final int i; //static final String str; //2.2 虽然也为static final只读静态变量,但是赋值号右边不是常量表达式(返回引用类型数据),编译时并不知道其引用的实例值。 //static final String str = new String(""); //2.3 只是静态变量,运行时可以动态赋值 //static int i = 50; //3、但是没有外部类实例此内部类不需要外部类实例就初始化了变量,与了成员内部类的定义相悖。 // 由于3不可以,所以2也不可以。因为编译器无法区别2和3这两种情况。 //static InnerClass innerClass = new InnerClass(); //4、静态方法中,但是没有外部类实例 //static void method() { InnerClass innerClass = new InnerClass(); }; } } 

其实内部类并不是完全不能出现static 这样的修饰的,只要符合第一种情况的就是可以的。
编译器是无法区别第二种,第三种情况的,第三种的情况肯定是不行的与内部成员类的定义相驳,所以第二种情况在语法上也被禁止了。
第三种情况,根据初始化的流程我们知道,在类加载的时候,static变量就必须被显式初始化,那么我们InnerClass成员内部类的实例对象在没有InnerClassDemo外部类的实例对象的时候便生成了。这样这个成员内部类就脱离了外部类的掌控,不需要外部类的对象就可以生成内部类的对象,这与成员内部类的定义就相驳了,因为我们知道成员内部类的对象必须是先有外部类的对象才能创建,成员内部类的对象 脱离了其外部类的对象 就不会存在,并且是绑定在一起的,所以成员内部类不可以定义静态变量。


下面介绍第二个
静态内部类(static inner class / Static Nested Class)
将成员内部类的使用再深入限制一步,假如内部类的实例对象不需要引用外部类的实例对象,只是将一个类隐藏在另外一个类的内部,可将该内部类静态化。
在外部类的内部,定义的静态的内部类,叫静态内部类。(或叫嵌套类)
提示:静态内部类在实际工作中用的并不是很多。
按照Java语言语法规定:
在这里插入图片描述
a) 外部类的静态成员对内部类可见。静态内部类可以访问外部类的所有静态成员(包括私有的)。
b) 外部类对内部类可见。在内部类中可以new生成外部类的实例对象。
c) 外部类的实例成员对内部类不可见。静态内部类的实例对象,没有外部类的实例对象的引用,所以不可以访问外部类实例。
在这里插入图片描述
d) 内部类对外部类可见。因为编译后的内部类至少是包内,其构造器至少是包内,所以在外部类中可以new生成内部类的实例对象。
其它语法:
e) 静态内部类可以使用public、protected或private访问修饰符进行访问控制,内部类能够隐藏起来,不为同一包的其它类访问。一般当做成员变量设置为private。
f) 静态内部类是静态的。所以在静态内部类中,可以定义静态字段、静态方法和静态内部类;也可以定义非静态字段、非静态方法和非静态内部类。

与成员内部类相比,静态内部类没有编译器自动添加的实例字段和构造器参数。即静态内部类的实例是不可以访问外部类的实例成员。
静态内部类在实际工作中用的并不是很多。如在程序测试的时候,为了避免在各个Java源文件中书写主方法的代码,可以将主方法写入到静态内部类中,以减少代码的书写量,让代码更加的简洁。

public class StaticInnerClassDemo { private int prop1 = 5; private static int prop2 = 9; static class StaticInnerClass { // 静态内部类里可以包含静态成员 // private static int age; // private int number = 28; public void accessOuterProp() { // 下面代码出现错误: // 静态内部类无法直接访问外部类的实例变量 //System.out.println(prop1); // 下面代码正常 System.out.println(prop2); } } public static void main(String[] args) { StaticInnerClass staticInnerClass = new StaticInnerClass(); staticInnerClass.accessOuterProp(); //System.out.println(staticInnerClass.age); //被翻译为以下字节码,说明外部类可以访问静态内部类的私有成员。 //System.out.println(StaticInnerClass.access$100(staticInnerClass)); } }

编译器编译后的字节码类文件:
1. StaticInnerClassDemo.class

//编译器在外部类添加了静态方法StaticInnerClassDemo.access$0(),它将返回私有的静态域prop2。通过静态方法访问私有的静态字段。 //如果内部类不访问外部类的静态私有成员,将不会添加静态方法StaticInnerClassDemo. access$0()。 public class StaticInnerClassDemo { private int prop1 = 5; private static int prop2 = 9; static int access$0(){ return StaticInnerClassDemo.prop2; } public static void main(String[] args) { StaticInnerClass staticInnerClass = new StaticInnerClass(); staticInnerClass.accessOuterProp(); } }

2. StaticInnerClassDemo$StaticInnerClass.class

class StaticInnerClassDemo$StaticInnerClass { public void accessOuterProp() { System.out.println(StaticInnerClassDemo.access$0()); } }

静态类的内存对象模型:
在这里插入图片描述

附录:java 内部类和静态内部类的区别
1、静态内部类可以有静态成员(方法,属性),而非静态内部类则不能有静态成员(方法,属性)。
2、静态内部类只能够访问外部类的静态成员,而非静态内部类则可以访问外部类的所有成员(方法,属性)。
3、实例化一个非静态的内部类的方法:

  • a.先生成一个外部类对象实例
    OutClassTest oc1 = new OutClassTest();
  • b.通过外部类的对象实例生成内部类对象
    OutClassTest.InnerClass no_static_inner = oc1.new InnerClass();

4、 实例化一个静态内部类的方法:

  • a.不依赖于外部类的实例,直接实例化内部类对象

     OutClassTest.InnerStaticClass inner = 
  1. OutClassTest.InnerStaticClass();
  • b.调用内部静态类的方法或静态变量,通过类名直接调用

     OutClassTest.InnerStaticClass.static_value OutClassTest.InnerStaticClass.getMessage()

接着介绍另外一种内部类:
局部内部类(local inner class)
在外部类的方法中,定义的非静态的命名的内部类,叫局部内部类。(因为内部类可以访问外部类方法的形参和局部变量而得此名)
可以分为:在外部类的实例方法内部的局部内部类和在外部类的静态方法内部的局部内部类。
提示:在实际开发中很少使用局部内部类,只是因为局部内部类的作用域很小,只能在当前方法中使用。

按照Java语言语法规定:
1.在外部类的实例方法内部的局部内部类。
在这里插入图片描述
a) 实例方法的形参对局部内部类可见。局部内部类的实例对象,可以有外部类的实例方法的形参的字段,可以访问外部类实例方法的形参。这些形参在JDK8之前必须被声明为final,但在JDK8中就不需要了。
b) 实例方法的局部变量对局部内部类可见。局部内部类的实例对象,可以有外部类的实例方法的局部变量的字段,可以访问外部类实例方法的局部变量。这些局部变量在JDK8之前必须被声明为final,但在JDK8中就不需要了。
在这里插入图片描述
a) 外部类的实例成员对局部内部类可见。局部内部类的实例对象,有外部类的实例对象的引用,所以可以访问外部类实例的所有成员(包括私有的)。
b) 外部类的静态成员对局部内部类可见。局部内部类可以访问外部类的所有静态成员(包括私有的)。
c) 外部类对局部内部类可见。在内部类中可以new生成外部类的实例对象。
在这里插入图片描述
d) 局部内部类对外部类的实例方法可见。因为编译后的内部类至少是包内,其构造器至少是包内,所以在外部类的实例方法中可以new生成局部内部类的实例对象。
在这里插入图片描述
e) 局部内部类不能使用public、protected或private访问修饰符进行访问控制,它的作用域被限定在声明该局部内部类的方法块中,所以在外部类的方法中不可以new生成局部内部类的实例对象。除了局部内部类所在的外部类方法,没有任何方法知道内部类的存在。
其他语法规定:
f) 局部内部类是非静态的。所以在局部内部类中,不能定义静态字段、静态方法和静态内部类;但是可以定义非静态字段、非静态方法和非静态内部类。

class InstanceLocalOut { private int age = 12; // //final形参、final局部变量,是编译器的语法,字节码中并不存在。 //使用final可以使得形参、局部变量与在局部内部类实例建立的字段拷贝保持一致。 //1. 在JDK8之前的版本,必需要写final修饰符 // 1)如果写上final形参,告知编译器,形参在方法内部是不能改变的; // 2)如果写上final局部变量,告知编译器,局部变量在方法内部只能赋值一次, // 以后不能改变的; //2. 在JDK8及其以后的版本,不需要再写final修饰符了(写上也无妨),由编译器自动判断 // 1)如果局部内部类使用了形参, // 则编译器在编译时自动判断形参在方法内部是不能改变的; // 2) 如果局部内部类使用了方法内部的局部变量, // 则编译器在编译时自动判断局部变量在方法内部只能赋值一次,以后不能改变的。 public void Print(final int x) { final int m = 8; // 在实例方法中定义一个局部内部类 class InstanceLocalIn { // 局部内部类的实例方法 public void inPrint() { // 直接访问外部类的private修饰的成员变量age System.out.println(age); // 直接访问外部类实例方法的形参x System.out.println(x); // 直接访问外部类实例方法的局部变量m System.out.println(m); } } // InstanceLocalIn类的实例对象是在InstanceLocalOut类的实例方法中创建的。 //所以,在创建InstanceLocalIn局部内部类的实例对象之前,必先创建InstanceLocalOut外部类的实例对象(外部类Print方法的隐藏形参this)。 InstanceLocalIn instanceLocalIn = new InstanceLocalIn(); instanceLocalIn.inPrint(); } } public class InstanceLocalInnerClass { public static void main(String[] args) { InstanceLocalOut out = new InstanceLocalOut(); out.Print(3); } } 

反编译:

//外部类 //编译器在外部类添加了静态方法 InstanceLocalOut.access$0(InstanceLocalOut arg0)。它将返回 作为参数传递给它的对象 的私有域age。 //如果内部类不访问外部类的私有字段,将不会在外部类中添加静态方法InstanceLocalOut.access$0(InstanceLocalOut arg0)。 -------------------------------------------------------------------- import InstanceLocalOut.1InstanceLocalIn; class InstanceLocalOut { private int age = 12; public void Print(int x) { byte m = 8; //编译器将InstanceLocalIn instanceLocalIn = new InstanceLocalIn();语句编译为 1InstanceLocalIn instanceLocalIn = new 1InstanceLocalIn(this, x, m); instanceLocalIn.inPrint(); } //编译器在外部类添加了静态方法 static double access$0(InstanceLocalOut arg0){ return arg0.age; } } //外部类的实例方法中的局部内部类 //编译器为了在内部类的实例中引用外部类的实例对象,必添加一个附加的实例域InstanceLocalOut this$0(this$0名字是由编译器合成的,在自编写的代码中不应该引用它)。 //如果内部类访问外部类的实例方法中的形参x,编译器将修改内部类,添加一个附加的实例域参数int val$x。否则,将不会添加附加的实例域。 //如果内部类访问外部类的实例方法中的局部变量m,编译器将修改内部类,添加一个附加的实例域参数int val$m。否则,将不会添加附加的实例域。 class InstanceLocalOut$1InstanceLocalIn { final InstanceLocalOut this$0; private final int val$x; private final int val$m; InstanceLocalOut$1InstanceLocalIn(InstanceLocalOut arg0, int arg1, int arg2) { this.this$0 = arg0; this.val$x = arg1; this.val$m = arg2; } public void inPrint() { System.out.println(InstanceLocalOut.access$0(this.this$0)); System.out.println(this.val$x); System.out.println(this.val$m); } }

外部类的内存执行模型:
在这里插入图片描述


在外部类的静态方法中的局部内部类
静态方法中的局部内部类除了不能访问外部类的实例外(因为它没有外部类的实例对象的引用),与实例方法中的局部内部类相同。

按照Java语言语法规定:
在这里插入图片描述
在这里插入图片描述
a) 外部类的静态成员对局部内部类可见。局部内部类可以访问外部类的所有静态成员(包括私有的)。
b) 外部类对局部内部类可见。在局部内部类中可以new生成外部类的实例对象。
c) 外部类的实例成员对内部类不可见。局部内部类的实例对象,没有外部类的实例对象的引用,所以不可以访问外部类实例的所有成员。
在这里插入图片描述
d) 局部内部类对外部类的静态方法可见。因为编译后的内部类至少是包内,其构造器至少是包内,所以在外部类的静态方法中可以new生成内部类的实例对象。
在这里插入图片描述
e) 局部内部类不能使用public、protected或private访问修饰符进行访问控制,它的作用域被限定在声明该局部内部类的方法块中。
其他语法规定:
f) 局部内部类是非静态的。所以在局部内部类中,不能定义静态字段、静态方法和静态内部类;但是可以定义非静态字段、非静态方法和非静态内部类。

class StaticLocalOut { private int age = 12; static public void print(int x) { final int m = 8; class StaticLocalIn { public void inPrint() { //静态方法中的局部内部类不能访问外部类的实例 //System.out.println(age); System.out.println(x); System.out.println(m); } } StaticLocalIn staticLocalIn = new StaticLocalIn(); staticLocalIn.inPrint(); } } public class StaticLocalInnerClass { public static void main(String[] args) { StaticLocalOut.Print(3); } } 

编译器编译后的字节码类文件:

//外部类 import StaticLocalOut.1StaticLocalIn; class StaticLocalOut { private int age = 12; public static void print(int x) { byte m = 8; 1StaticLocalIn staticLocalIn = new 1StaticLocalIn(x, m); staticLocalIn.inPrint(); } } //外部类的静态方法中的局部内部类 class StaticLocalOut$1StaticLocalIn { StaticLocalOut$1StaticLocalIn(int arg0, int arg1) { this.val$x = arg0; this.val$m = arg1; } public void inPrint() { System.out.println(this.val$x); System.out.println(this.val$m); } }

外部类的静态方法内部的局部内部类的内存模型
在这里插入图片描述


匿名内部类(anonymous inner class )
将局部内部类的使用再深入一步,假如只创建这个类的一个对象,就不必命名了。从使用上讲,匿名内部类和局部内部类的区别是一个是匿名的另一个是命名的,其它均相同。(匿名的含义是由编译器自动给内部类起一个内部名称)
在外部类的方法中,定义的非静态的没有类名的内部类,叫匿名内部类。
匿名内部类适合只需要使用一次的类,当创建一个匿名内部类时会立即创建该类的一个实例对象,匿名类不能重复使用。
可以分为:在外部类的实例方法内部的匿名内部类,
在外部类的静态方法内部的匿名内部类。

提示:最好使用lambda表达式来代替匿名内部类。

按照Java语言语法规定:
1.在外部类的实例方法内部的匿名内部类。
在这里插入图片描述
a) 实例方法的形参对匿名内部类可见。匿名内部类的实例对象,可以有外部类的实例方法的形参的字段,可以访问外部类实例方法的形参。这些形参在JDK8之前必须被声明为final,但在JDK8中就不需要了。
b) 实例方法的局部变量对匿名内部类可见。匿名内部类的实例对象,可以有外部类的实例方法的局部变量的字段,可以访问外部类实例方法的局部变量。这些局部变量在JDK8之前必须被声明为final,但在JDK8中就不需要了。
在这里插入图片描述
c) 外部类的实例成员对匿名内部类可见。匿名内部类的实例对象,有外部类的实例对象的引用,所以可以访问外部类实例的所有成员(包括私有的)。
d) 外部类的静态成员对匿名内部类可见。匿名内部类可以访问外部类的所有静态成员(包括私有的)。
e) 外部类对局部内部类可见。在匿名内部类中可以new生成外部类的实例对象。
在这里插入图片描述
f) 匿名内部类对外部类该实例方法可见。因为编译后的内部类至少是包内,其构造器至少是包内,所以在外部类中实例方法可以new生成内部类的实例对象。
在这里插入图片描述
g) 匿名内部类不能使用public、protected或private访问修饰符进行访问控制,它的作用域被限定在声明该匿名内部类的方法块中。
其它语法规定:
h) 由于构造器的名字必须与类名相同,而匿名类无类名,所以匿名内部类不能有构造器。取而代之的是将构造器参数传递给父类构造器。
i) 匿名内部类是非静态的。所以在匿名内部类中,不能定义静态字段、静态方法和静态内部类;但是可以定义非静态字段、非静态方法和非静态内部类。

class FatherofAnonIn { protected int a; FatherofAnonIn(int a){ this.a = a; } } class InstanceAnonOut { private int age = 12; public void Print(int x) { int m = 8; //1. 创建FatherofAnonIn类的派生匿名类 //2. 匿名内部类不能有定义的实例构造器,将实例构造器参数传递给父类实例构造器进行初始化。 //3. 生成匿名内部类的实例对象 //4. 通过匿名内部类的实例对象调用匿名类的方法 (new FatherofAnonIn(10){ public void inPrint() { System.out.println(age); System.out.println(x); System.out.println(m); System.out.println(a); } }).inPrint(); } } public class InstanceAnonInnerClass { public static void main(String[] args) { InstanceAnonOut out = new InstanceAnonOut(); out.Print(3); } } 

编译后的字节码:

 //外部类 import InstanceAnonOut.1; class InstanceAnonOut { private int age = 12; public void Print(int x) { byte m = 8; (new 1(this, 10, x, m)).inPrint(); } //编译器在外部类添加了静态方法 static int access$0(InstanceAnonOut arg0){ return arg0.age; } } //匿名内部类 class InstanceAnonOut$1 extends FatherofAnonIn { InstanceAnonOut$1(InstanceAnonOut arg0, int $anonymous0, int arg2, int arg3) { super($anonymous0); this.this$0 = arg0; this.val$x = arg2; this.val$m = arg3; } public void inPrint() { System.out.println(InstanceAnonOut.access$0(this.this$0)); System.out.println(this.val$x); System.out.println(this.val$m); System.out.println(this.a); } }

外部类的实例方法内部的匿名内部类的内存模型
在这里插入图片描述


在外部类的静态方法中的匿名内部类
静态方法中的匿名内部类除了不能访问外部类的实例外(因为它没有外部类的实例对象的引用),与实例方法中的匿名内部类相同。
在这里插入图片描述
a) 静态方法的形参对匿名内部类可见。匿名内部类的实例对象,可以有外部类的静态方法的形参的字段,可以访问外部类静态方法的形参。这些形参在JDK8之前必须被声明为final,但在JDK8中就不需要了。
b) 静态方法的局部变量对匿名内部类可见。匿名内部类的实例对象,可以有外部类的静态方法的局部变量的字段,可以访问外部类静态方法的局部变量。这些局部变量在JDK8之前必须被声明为final,但在JDK8中就不需要了。
在这里插入图片描述
c) 外部类的静态成员对匿名内部类可见。匿名内部类可以访问外部类的所有静态成员(包括私有的)。
d) 外部类对匿名局部内部类可见。在局部内部类中可以new生成外部类的实例对象。
e) 外部类的实例成员对匿名内部类不可见。匿名内部类的实例对象,没有外部类的实例对象的引用,所以不可以访问外部类实例的所有成员(包括私有的)。

在这里插入图片描述
f) 内部类对外部类的静态方法可见。因为编译后的内部类至少是包内,其构造器至少是包内,所以在外部类的静态方法中可以new生成内部类的实例对象。
在这里插入图片描述
g) 匿名内部类不能使用public、protected或private访问修饰符进行访问控制,它的作用域被限定在声明该匿名内部类的方法块中。

其他语法规定:
h) 匿名内部类不能有构造方法。因为匿名类无类名,所以无法写构造方法。取而代之的是将构造器参数传递给超类构造器。
i) 匿名内部类是非静态的。所以在匿名内部类中,不能定义静态字段、静态方法和静态内部类;但是可以定义非静态字段、非静态方法和非静态内部类。

//源程序: class FatherofAnonIn { protected int a; FatherofAnonIn(int a){ this.a = a; } } class StaticAnonOut { private int age = 12; public static void print(int x) { int m = 8; (new FatherofAnonIn(10){ public void inPrint() { //System.out.println(age); System.out.println(x); System.out.println(m); System.out.println(a); } }).inPrint(); } } public class StaticAnonInnerClass { public static void main(String[] args) { StaticAnonOut.Print(3); } }

编译后的字节码:

//外部类 import StaticAnonOut.1; class StaticAnonOut { private int age = 12; public static void print(int x) { byte m = 8; (new 1(10, x, m)).inPrint(); } } //匿名内部类 class StaticAnonOut$1 extends FatherofAnonIn { StaticAnonOut$1(int $anonymous0, int arg1, int arg2) { super($anonymous0); this.val$x = arg1; this.val$m = arg2; } public void inPrint() { System.out.println(this.val$x); System.out.println(this.val$m); System.out.println(this.a); } }

外部类的静态方法内部的匿名内部类的内存模型
在这里插入图片描述

原文链接:https://yq.aliyun.com/articles/726774
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章