首先从字节码文件开始讲述:
Java语言和字节码的语言是两种语言
相同处:
都有访问控制符,一个类的私有成员只能被该类的其他成员访问。
不同处:
- 字节码中没有内部类命令,JVM对内部类一无所知
- Java中不允许定义方法签名完全相同的两个方法,方法签名和返回值无关,JVM允许定义函数签名相同但是返回值不同的两个方法
再看一下编译的过程:
有个名为outer的外部类,含有一个内部类名为inner
编译生成两个class文件,一个是outter.class 一个是outer$inner.class
1.成员内部类:
- 是在Java类内部,方法外部定义的非静态内部类。
- 是可以访问外部类所有成员的
- 外部类也可以访问内部类所有成员 (按照常规的类访问方式)
- 在内部类可以生成外部类的对象,在外部类可以生成内部类的对象
- 内部类中不能定义静态方法、字段
问:为什么内部类可以访问外部类的所有成员?
因为内部类中有一个隐式引用,它创建了当前外部类的实例对象,通过这个引用指针可以访问外部类所有成员(包括私有)
使用限制:创建内部类实例对象的时候,必须存在一个外部类的实例对象。
语法规则:
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));
}
}
内部逻辑:
总结:
- 外部类调用内部类成员的方式是 创建内部类实例 inner = (this.)new inner();
- 内部类调用外部类的方式是 在内部类中创建换一个final形式的外部类实例,然后为所有的构造方法添加一个外部类的形参arg0,将形参的值赋值给那个final的外部类实例 ,通过这个实例来引用外部类的私有成员,如果不需要引用外部类的私有成员,上述所有将不会被创建。内部类还创建了一个返回外部类私有成员的方法发access$0().
静态成员内部类
如果在Java中内部类不需要引用外部类的实例,只需要将一个内部类隐藏在外部类中,可以把内部列静态化, 静态内部类在实际工作中用的不是很多,比如在程序测试时为了避免在每个类中写main,可以使用静态内部类
权限问题
- 内部类:内部类可以访问外部类的所有静态成员(包括私有的),可以new生成外部类的实例,但是不能访问外部类的实例字段和方法,内部类内部可以定义非静态的方法和字段。
- 外部类:内部类对外部类可见(同一个包内的字节码文件),外部类可以new生成内部类的实例。
和成员内部类相比
没有外部实例的final实例,也没有在每个构造方法中加一个外部类的形参。
字节码:
- 如果内部类访问外部类私有静态成员,会在外部类中创建一个静态的方法来返回静态私有成员。如果不访问将不会添加。
代码
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());
}
}
内存模型:
成员内部类和静态内部类的区别
- 如前所述:在使用外部类权限上的区别。
- 实例化的区别:
成员内部类实例化需要先生成 外部类实例化变量,静态内部类实例化不通过外部类的变量直接实例化outer.inner in = new outer.inner (); - 调用内部类字段或者方法是通过类名直接调用 outer.inner.XXX();
局部内部类
局部内部类 是定义在方法内的内部类,作用域很小只能在当前方法。
局部内部类分为静态的和非静态的
非静态局部内部类:
- 可以访问外部实例方法的形参,局部变量,外部类的所有变量,外部类的所有方法,可以new生成外部类
- 外部类的方法可以看到局部内部类
- 局部内部类不能使用访问控制符,除了外部方法,局部内部类对所有方法和类不可见
版本差异:
在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);
}
}
静态方法中的内部类:
- 外部方法的所有的静态的局部变量和形参对内部类可见
- 外部类中的所有静态字段对内部类可见
- 外部类对内部类可见,可以new 生成外部类
- 外部类的实例成员对内部类不可见
- 外部方法可以访问内部类,外部类不可以,
- 内部类不能有访问控制符号
代码:
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);
}
}
匿名内部类!!
一,实例方法的匿名内部类
- 可以访问外部实例方法的形参,局部变量,外部类的所有变量,外部类的所有方法,可以new生成外部类
- 外部类的方法可以看到局部内部类
- 局部内部类不能使用访问控制符,除了外部方法,局部内部类对所有方法和类不可见
- 没有名字的话,,,,也就没有了构造器,吧形参传递给父类的构造器
也就是说,除了第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);
}
}
静态方法中的匿名内部类
静态方法中的匿名内部类除了不能访问外部类的实例外(因为它没有外部类的实例对象的引用),与实例方法中的匿名内部类相同。
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);
}
}