内部类解析

首先从字节码文件开始讲述:

Java语言和字节码的语言是两种语言
相同处:

都有访问控制符,一个类的私有成员只能被该类的其他成员访问。

不同处:

  1. 字节码中没有内部类命令,JVM对内部类一无所知
  2. Java中不允许定义方法签名完全相同的两个方法,方法签名和返回值无关,JVM允许定义函数签名相同但是返回值不同的两个方法

再看一下编译的过程:

有个名为outer的外部类,含有一个内部类名为inner
编译生成两个class文件,一个是outter.class 一个是outer$inner.class

在这里插入图片描述

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));
	}
}

内部逻辑:
在这里插入图片描述
总结:

  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());
	}
}

内存模型:
在这里插入图片描述
成员内部类和静态内部类的区别

  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);
	}
}
	

在这里插入图片描述

静态方法中的内部类:

  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);
	}
}

在这里插入图片描述

匿名内部类!!

一,实例方法的匿名内部类

  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);
	}
}

在这里插入图片描述

静态方法中的匿名内部类

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

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);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值