内部类解析 深藏阁楼爱情的钟 2022-02-09 11:55 228阅读 0赞 ###### 首先从字节码文件开始讲述: ###### Java语言和字节码的语言是两种语言 相同处: > 都有访问控制符,一个类的私有成员只能被该类的其他成员访问。 不同处: > 1. 字节码中没有内部类命令,JVM对内部类一无所知 > 2. Java中不允许定义方法签名完全相同的两个方法,方法签名和返回值无关,JVM允许定义函数签名相同但是返回值不同的两个方法 再看一下编译的过程: > 有个名为outer的外部类,含有一个内部类名为inner > 编译生成两个class文件,一个是outter.class 一个是outer$inner.class ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70] # 1.成员内部类: # 1. 是在Java类内部,方法外部定义的非静态内部类。 2. 是可以访问外部类所有成员的 3. 外部类也可以访问内部类**所有**成员 (按照常规的类访问方式) 4. 在内部类可以生成外部类的对象,在外部类可以生成内部类的对象 5. 内部类中不能定义静态方法、字段 ###### 问:为什么内部类可以访问外部类的所有成员? ###### > 因为内部类中有一个隐式引用,它创建了当前外部类的实例对象,通过这个引用指针可以访问外部类所有成员(包括私有) 使用限制:创建内部类实例对象的时候,必须存在一个外部类的实例对象。 语法规则: > 1.在内部类中引用外部类的成员 outerClass.this.data; > 2.在外部类中创建内部类 OterObject,new inner(…parameters); > 3.如果内部类是外部类之外可见的 ,使用OuterObject .inner的形式引用内部类 成员内部类代码实现 package test; public class Cow { private double weight; public Cow() { } public Cow(double weight) { this.weight=weight; } private class CowLeg{ private double length; private String color;//内部类不是静态的,所以内部类里面不能创建静态的子段和方法 public CowLeg() { } public CowLeg(double length,String color) { this.length=length; this.color=color; } public void setLength(double length){ this.length=length; } public double getLength() { return length; } public void setColor(String color) { this.color=color; } public String getColor() { return color; } public void info() { System.out.println("牛的体重:"+weight);//没有加this。编译器会自动添加this //等价于:System.out.println("牛的体重:"+Cow.this.weight);内部类引用外部类数据方法的方式,编译器会默认加上的 System.out.println("牛的长度:"+length); System.out.println("牛的颜色:"+color); } } public void test() { //编译器会自动加一个this 等同于 this.new CowLeg(1.15,"黑白相间"); CowLeg cowleg=new CowLeg(1.15,"黑白相间"); cowleg.info(); } public static void main(String[] args) { Cow cow = new Cow(378.9); //如果在类外部定义内部类 需要这样CowLeg c1 = cow.new CowLeg(); //c1.color 字节码文件是这样的CowLeg.access&100(c1); cow.test(); } } /** * 编译器为字节码文件添加的东西 * 1首先会在外部类添加一个静态方法Cow.access&0(Cow arg0):他返回外部类的私有数据,如果内部类不访问外部类将不会添加静态方法 * 2编译器在内部类添加一个成员变量final Cow this$0;是为了在内部类访问外部类的实例对象 * 3编译器在内部类的所有构造方法参数列表添加了一个参数:Cow arg0。并且在内部类的所有构造方法中添加了一个this&0=arg0 */ 字节码代码 编译器编译后的字节码类文件: 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)); } } 内部逻辑: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70 1] 总结: 1. 外部类调用内部类成员的方式是 创建内部类实例 inner = (this.)new inner(); 2. 内部类调用外部类的方式是 在内部类中创建换一个final形式的外部类实例,然后为所有的构造方法添加一个外部类的形参arg0,将形参的值赋值给那个final的外部类实例 ,通过这个实例来引用外部类的私有成员,如果不需要引用外部类的私有成员,上述所有将不会被创建。内部类还创建了一个返回外部类私有成员的方法发access$0(). # 静态成员内部类 # 如果在Java中内部类不需要引用外部类的实例,只需要将一个内部类隐藏在外部类中,可以把内部列静态化, 静态内部类在实际工作中用的不是很多,比如在程序测试时为了避免在每个类中写main,可以使用静态内部类 ###### 权限问题 ###### 1. 内部类:内部类可以访问外部类的所有静态成员(包括私有的),可以new生成外部类的实例,但是不能访问外部类的实例字段和方法,内部类内部可以定义非静态的方法和字段。 2. 外部类:内部类对外部类可见(同一个包内的字节码文件),外部类可以new生成内部类的实例。 ###### 和成员内部类相比 ###### 没有外部实例的final实例,也没有在每个构造方法中加一个外部类的形参。 ##### 字节码: ##### 1. 如果内部类访问外部类私有静态成员,会在外部类中创建一个静态的方法来返回静态私有成员。如果不访问将不会添加。 ##### 代码 ##### package test; public class OutterStatic { private int prop1=1; private static int prop2=2; static class InnerStatic{ private int age ; private int number=28; public void accessOuter() { //System.out.println(prop1);//这段代码是错误的,不能直接访问外部类的实例成员,要通过new创建实例访问 System.out.println(prop2); } } public static void main(String[] args) { InnerStatic inner= new InnerStatic(); inner.accessOuter(); } } /* * 编译后的字节码文件:在外部类中创建了一个静态方法static int access&0(){return prop2;}用来访问外部类的私有静态成员prop2 * 在accessOuter中的输出语句System.out.println(prop2);转换为System.out.println(OutterStatic.access$0()); * */ 字节码: //编译器在外部类添加了静态方法OutterStatic .access$0(),它将返回私有的静态域prop2。通过静态方法访问私有的静态字段。 //如果内部类不访问外部类的静态私有成员,将不会添加静态方法OutterStatic . access$0()。 public class OutterStatic { private int prop1 = 5; private static int prop2 = 9; static int access$0(){ return OutterStatic .prop2; } public static void main(String[] args) { StaticInnerClass staticInnerClass = new StaticInnerClass(); staticInnerClass.accessOuterProp(); } } 2.OutterStatic$InnerStatic.class class OutterStatic$InnerStatic { public void accessOuterProp() { System.out.println(OutterStatic.access$0()); } } 内存模型: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70 2] 成员内部类和静态内部类的区别 1. 如前所述:在使用外部类权限上的区别。 2. 实例化的区别: 成员内部类实例化需要先生成 外部类实例化变量,静态内部类实例化不通过外部类的变量直接实例化outer.inner in = new outer.inner (); 3. 调用内部类字段或者方法是通过类名直接调用 [outer.inner.XXX][](); # 局部内部类 # 局部内部类 是定义在方法内的内部类,作用域很小只能在当前方法。 局部内部类分为静态的和非静态的 ## 非静态局部内部类: ## 1. 可以访问外部实例方法的形参,局部变量,外部类的所有变量,外部类的所有方法,可以new生成外部类 2. 外部类的方法可以看到局部内部类 3. 局部内部类不能使用访问控制符,除了外部方法,局部内部类对所有方法和类不可见 版本差异: 在jdk8以前,外部类的形参,必须加final才能被内部类访问,(final是编译器的语法,字节码中不存在) 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); } } ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70 3] ## 静态方法中的内部类: ## 1. 外部方法的所有的静态的局部变量和形参对内部类可见 2. 外部类中的所有静态字段对内部类可见 3. 外部类对内部类可见,可以new 生成外部类 4. 外部类的实例成员对内部类不可见 5. 外部方法可以访问内部类,外部类不可以, 6. 内部类不能有访问控制符号 代码: package test; class OutterStatic1 { private int age=12; static public void print(final int x) { final int m=8; class Inner{ public void inprint() { //System.out.println(age);//不能访问外部类实例 System.out.println(x); System.out.println(m); }//创建局部内部类的时候,外部类一定已经被创建,在方法中,隐藏加了形参this } Inner inner = new Inner(); inner.inprint();//在方法中使用内部类 } } public class OutterStatic{ public static void main(String[] args) { OutterStatic1.print(3); } } 字节码: import OutterStatic1 .1Inner; class OutterStatic1 { private int age = 12; public static void print(int x) { byte m = 8; 1Inner inner = new 1Inner (x, m); 1Inner .inPrint(); } } 内部类 //外部类的静态方法中的局部内部类 class OutterStatic$1Inner { OutterStatic$1Inner (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); } } ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70 4] # 匿名内部类!! # ### 一,实例方法的匿名内部类 ### 1. 可以访问外部实例方法的形参,局部变量,外部类的所有变量,外部类的所有方法,可以new生成外部类 2. 外部类的方法可以看到局部内部类 3. 局部内部类不能使用访问控制符,除了外部方法,局部内部类对所有方法和类不可见 4. 没有名字的话,,,,也就没有了构造器,吧形参传递给父类的构造器 ##### 也就是说,除了第4条 其他和局部内部类相同。在字节码中,外部类都是有编译器默认添加的方法access来返回私有变量 ,内部类中编译器添加了this$0, val $ X等变量。在方法中默认添加了this ##### 代码: package test; class fa{ int a; fa(int a){ this.a=a; } } class Outer{ private int age=12; public void print(int x) { int m=8; (new fa(10) { public void inprint() { System.out.println(a); System.out.println(age); System.out.println(x); System.out.println(m); } }).inprint();//调用匿名内部类的方法 } } public class OutterStatic{ public static void main(String[] args) { Outer outer =new Outer(); outer.print(3); } } 字节码: class Outer{ private int age = 12; public void Print(int x) { byte m = 8; (new 1(this, 10, x, m)).inPrint(); } //编译器在外部类添加了静态方法 static int access$0(Outer arg0){ return arg0.age; } } //内部类 class Outer$1 extends fa{//是继承关系 Outer$1(Outer 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(Outer.access$0(this.this$0)); System.out.println(this.val$x); System.out.println(this.val$m); System.out.println(this.a); } } ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70 5] ## 静态方法中的匿名内部类 ## 静态方法中的匿名内部类除了不能访问外部类的实例外(因为它没有外部类的实例对象的引用),与实例方法中的匿名内部类相同。 package test; class fa{ int a; fa(int a){ this.a=a; } } class Outer{ private int age=12; static public void print(int x) { int m=8; (new fa(10) { public void inprint() { System.out.println(a); //System.out.println(age);不能访问外部类的实例成员 System.out.println(x); System.out.println(m); } }).inprint();//调用匿名内部类的方法 } } public class OutterStatic{ public static void main(String[] args) { Outer outer =new Outer(); outer.print(3); } } 字节码 import Outer .1; class Outer { private int age = 12; public static void print(int x) { byte m = 8; (new 1(10, x, m)).inPrint(); } } //内部类 class Outer $1 extends fa{ Outer $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); } } [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70]: /images/20220206/69da80463a9441dfb86a1b15254b68ad.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70 1]: /images/20220206/26733586f6c3475582e5253b5ea80147.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70 2]: /images/20220206/7765f312fcf14b6b8c2556da3314a75e.png [outer.inner.XXX]: http://outer.inner.XXX [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70 3]: /images/20220206/9386e6b4ed584eef961e915a0cad01ed.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70 4]: /images/20220206/1af4edce5f1745d4818745593c18608c.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpemlfZ2hx_size_16_color_FFFFFF_t_70 5]: /images/20220206/611f4d3bd6074b5d94c0c01c676ca699.png
还没有评论,来说两句吧...