从内存逻辑模型分析Java中的内部类
Copyright©Stonee
还记得那是一个晴空万里的上午,一个举世瞩目的语言诞生在一栋不大不小的房子里。(上述扯淡),在Java诞生之初,是不存在内部类的。但是由于编程语言之间激烈的竞争,内部类在java1.1呼之欲出,所以才登上了时代的舞台。但是需要注意的是,内部类是编译器的语法,这在jvm中是不承认的,jvm依旧只认识普通的外部类,所以编译器就需要把内部类换成jvm可以识别的外部类。从上面我们可以得知, 内部类只是通过编译器方便了程序员 。事实上,我们完全可以不通过内部类去实现内部类的功能,不过那要复杂的多。
内部类中不能有static方法
1. 成员内部类
成员内部类是指不能带static的类,可以访问外部所有的字段(包括private),外部类同样也可以访问内部类的private
过程分为两步:
- 第一步,先让内部类(在jvm看来是一个普通的外部类)指向外部类用来获取外部类的权限
- 第二步,让内部类通过外部类的一个特殊方法去调用外部类的private字段
- 我们先通过一个栗子来观察成员内部类是如何工作的:
//源程序
public class Cow {
private double weight;
public Cow(double weight) {
this.weight = weight;
}
public void test() {
//CowLeg类的实例对象是在Cow类的实例方法中创建的。
//所以,在创建CowLeg内部类的实例对象之前,必先创建Cow外部类的实例对象。
CowLeg cl = new CowLeg(1.12, "黑白相间");
cl.info();
}
//内部类
private class CowLeg {
private double length;
private String color;
public CowLeg(double length, String color) {
this.length = length;
this.color = color;
}
public void info() {
System.out.println("当前牛腿颜色是:" + color + ", 高:" + length);
System.out.println("本牛腿所在奶牛重:" + weight);
}
}
很显然这是一个极为简单的成员内部类栗子,那么我们将通过字节码来观察到Java编译器是如何让内部类可以访问外部的private字段的。
//字节码反编译
public class Cow {
private double weight;
public Cow(double weight) {
this.weight = weight;
}
//java编译器自主加入的静态方法,为了让子类可以通过cow类直接返回weight(如果不是调用private字段可以不要这个方法)
static double access$0(Cow arg0){
return arg0.weight;
}
public void test() {
CowLeg cl = new CowLeg(this,1.12, "黑白相间"); //cow类会在调用内部类时加入this,这个this是cow类型的,此时内部类可以通过this指向外部类
cl.info();
}
}
//内部类
private class CowLeg { //会先改个名字:Cow$Cowleg,下面的构造方法也需要改,不再赘述
private double length;
private String color;
//java编译器自主加入实例域,为了获得外部类的this
final Cow this$0;
//java编译器会在内部构造方法中加入形参
public CowLeg(Cow arg0, double length, String color) { //此处会加入arg0
this.this$0 = arg0; //this$0指向cow类中的this
this.length = length;
this.color = color;
}
public void info() {
System.out.println("当前牛腿颜色是:" + color + ", 高:" + length);
System.out.println("本牛腿所在奶牛重:" + Cow.access$0(this.this$0)); //此处会调用外部类的access代替weight
}
}
要注意,当java编译器编译之后,cow和cowleg是两个独立的类,不存在嵌套关系
- 看下成员内部类的内存模型吧:
- 刚开始学习内部类的时候,我有这么一个疑问,不就是访问另外一个类的private字段吗,用得着这么麻烦吗?譬如下例子:
class a{
private int b =1;
public int getB(){
return b;
}
class Cow{
public static void main(String [] args){
System.out.println(new b().getB());
}
}
我们完全可以通过getB来访问a中的b,为什么在内部类中费那么大力气访问外部类的private呢?
在我个人看来,当我们用getB来实现访问外部类的private的时候,需要new一个a的实例才可以,这样无疑消耗的空间,所以java编译器通过了另外一种方法,即用static的静态方法来直接调用类中的private字段,但是又因为b不是一个静态字段,所以必须要让Cow指向a类才可以,所以语法才会变得复杂化。
另外我们需要知道,内部类的作用不仅仅是可以访问外部类的private字段,内部类还可以对同一个包中的其他类隐藏起来。如果想要定义一个回调函数的话,使用匿名内部类或者lambda表达式(现在已经代替了匿名内部类)会更加方便。
class a{
private int b =1;
Cow stonee = new Cow(this);
static int accessB(a outer){
return outer.b;
}
}
class Cow{
a thiss;
public Cow(){
}
public Cow(a thiss){
this.thiss = thiss;
}
public void ooo(){
System.out.println(a.accessB(thiss));
}
public static void main(String [] args){
new Cow().ooo();
}
}
这个栗子可以和上面使用getB方法的作比较,同样都是调用其他类的私有字段
成员内部类不能定义静态属性或者静态方法
2. 静态内部类
将成员内部类的使用再深入限制一步,假如内部类的实例对象不需要引用外部类的实例对象,只是将一个类隐藏在另外一个类的内部,可将该内部类静态化。在外部类的内部,定义的静态的内部类,叫静态内部类。(或叫嵌套类)
-
静态内部类可以访问外部类包括private的所有静态成员
-
静态内部类不能访问外部类实例成员
-
内部类中可以new生成外部类的实例
-
静态内部类可以定义static方法
-
通过栗子:
//源程序
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();
}
}
- 字节码反编译程序
//字节码反编译程序
public class StaticInnerClassDemo {
private int prop1 = 5;
private static int prop2 = 9;
//如果内部类调用外部类的private会添加下面的程序,否则不用
static int access&0(){
return StaicInnerClassDemo.prop2;
}
public static void main(String[] args) {
StaticInnerClass staticInnerClass = new StaticInnerClass();
staticInnerClass.accessOuterProp();
}
}
static class StaticInnerClass { //换名字StaticInnerClassDeom$StaticInnerClass
public void accessOuterProp() {
System.out.println(StaticInnerClassDemo.access$0()); //换掉prop2
}
}
- 内存逻辑模型
从上面也可以验证我在成员内部类中说的为什么 在我个人看来,当我们用getB来实现访问外部类的private的时候,需要new一个a的实例才可以,这样无疑消耗的空间,所以java编译器通过了另外一种方法,即用static的静态方法来直接调用类中的private字段,但是又因为b不是一个静态字段,所以必须要让Cow指向a类才可以,所以语法才会变得复杂化。 这段话
在静态内部类中,以为引用外部类的字段是静态的,所以就直接通过一个静态方法调用就好了,和getB的作用是一样的
java 内部类和静态内部类的区别
-
静态内部类可以有静态成员(方法,属性),而非静态内部类则不能有静态成员(方法,属性)。
-
静态内部类只能够访问外部类的静态成员,而非静态内部类则可以访问外部类的所有成员(方法,属性)。
-
实例化一个非静态的内部类的方法:
a.先生成一个外部类对象实例
OutClassTest oc1 = new OutClassTest();
b.通过外部类的对象实例生成内部类对象
OutClassTest.InnerClass no_static_inner = oc1.new InnerClass();
- 实例化一个静态内部类的方法:
a.不依赖于外部类的实例,直接实例化内部类对象
OutClassTest.InnerStaticClass inner = new OutClassTest.InnerStaticClass();
b.调用内部静态类的方法或静态变量,通过类名直接调用
OutClassTest.InnerStaticClass.static_value OutClassTest.InnerStaticClass.getMessage()
3. 局部内部类
在外部类的方法中,定义的非静态的命名的内部类,叫局部内部类。(因为内部类可以访问外部类方法的形参和局部变量而得此名)可以分为:
- 在 外部类的实例方法内部 的局部内部类
- 在 外部类的静态方法内部 的局部内部类
1. 实例方法的局部内部类
- 内部类可以访问实例方法的形参,局部变量和外部类的所有成员
- 在jdk8之前,实例方法中的形参必须要写上final,现在写不写均可,编译器会自动加上
看一个源程序
class InstanceLocalOut {
private int age = 12;
public void Print(final int x) {
final int m = 8;
// 在实例方法中定义一个局部内部类
class InstanceLocalIn {
// 局部内部类的实例方法
public void inPrint() {
System.out.println(age); // 直接访问外部类的private修饰的成员变量age
System.out.println(x); // 直接访问外部类实例方法的形参x
System.out.println(m);// 直接访问外部类实例方法的局部变量m
}
}
// InstanceLocalIn类的实例对象是在InstanceLocalOut类的实例方法中创建的。
//所以,在创建InstanceLocalIn局部内部类的实例对象之前,必先创建InstanceLocalOut外部类的实 例对象(外部类Print方法的隐藏形参this)。
InstanceLocalIn instanceLocalIn = new InstanceLocalIn();
instanceLocalIn.inPrint();
}
看一下字节码的反编译文件
class InstanceLocalOut {
private int age = 12;
//编译器添加的静态方法,为了访问外部类的私有字段
static int access$0(InstanceLocalOut arg0){
return arg0.age;
}
public void Print(final int x) {
final int m = 8;
//原来内部类的位置
}
InstanceLocalIn instanceLocalIn = new InstanceLocalIn(this,x,m); //内部类构造方法参数改变,参数传入内部类
instanceLocalIn.inPrint();
}
class InstanceLocalIn { //名字变为InstanceLocalOut$1InstanceLocalIn
final InstanceLocalOut this$0; //增加一个final字段用以链接到外部类
private final int val$x;
private final int val$m; //因为内部类调用来了外部类方法的参数,故还需要两个来链接
InstanceLocalIn(InstanceLocalOut this$0, int val$x, int val$m){
this.this$0 = this$0;
this.val$x = val$x;
this.val$m = val$m;
} //对应外部类调用的构造方法
public void inPrint() {
System.out.println(InstanceLocalOut.access$0(this.this$0));
System.out.println(this.val$x);
System.out.println(this.val$m); //调用外部类成员
}
}
我们可以发现,局部内部类的原理和成员内部类原理相似,所以不再画出内存逻辑模型
2. 静态方法中的局部内部类
- 特点和静态内部类相似
- 内部类可以访问静态方法的形参,局部变量和外部类的静态成员
不再赘述
4. 匿名内部类
将局部内部类的使用再深入一步,假如只创建这个类的一个对象,就不必命名了。从使用上讲,匿名内部类和局部内部类的区别是一个是匿名的另一个是命名的,其它均相同。(匿名的含义是由编译器自动给内部类起一个内部名称)
在外部类的方法中,定义的非静态的没有类名的内部类,叫匿名内部类。匿名内部类适合只需要使用一次的类,当创建一个匿名内部类时会立即创建该类的一个实例对象,匿名类不能重复使用。可以分为:
- 在外部类的实例方法内部的匿名内部类
- 在外部类的静态方法内部的匿名内部类。
但是尽量用lambda表达式代替匿名内部类
- 匿名内部类的语法格式为
new classtype(parm){ ...}
比较一下普通写法,匿名内部类和lambda表达式:
//普通写法
public void start(){
class sonClass implements superInterface{
public void fxxk(){
System.out.println("stonee is so cool");
}
}
superInterface a = new sonClass();
otherClass b = new otherClass( a );
}
//匿名内部类写法
public void start(){
superInterface a = new superInterface(){
public void fxxk(){
System.out.println("stonee is so cool");
}
}
otherClass b = new otherClass( a );
}
//lambda表达式写法
public void start(){
otherClass b = new otherClass( a ->
{
public void fxxk(){
System.out.println("stonee is so cool");
} );
}
在实际工程中,只有成员内部类和匿名内部类用的较多,其余的很少用。
- BZOJ 1115: [POI2009]石子游戏Kam (阶梯nim)
- php性能监测模块XHProf
- BZOJ 1022: [SHOI2008]小约翰的游戏John (Anti-nim)
- 洛谷P2252 取石子游戏(威佐夫博弈)
- HDU 3032 Nim or not Nim?(Multi-Nim)
- POJ 2311 Cutting Game(二维SG+Multi-Nim)
- js去掉html标签和去掉字符串文本的所有的空格
- php操作memcache的使用测试总结
- linux awk命令详解
- php str_split 解决中文
- PHP汉字转拼音函数
- 51NOD 1185 威佐夫游戏 V2(威佐夫博弈)
- HDU 1527 取石子游戏(威佐夫博弈)
- PHP文件操作类
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 使用SAP C4C OData notification实现CRM和C4C的数据同步
- 在nodejs服务器和ABAP服务器上使用jsonp
- 如何在Android平台上创建自定义的Cordova插件并使用SAP UI5消费
- 使用SAP BSP应用运行Vue
- 微信开发系列之六 - 使用微信OAuth2 API读取微信用户信息,显示在SAP UI5里
- 微信开发系列之五 - 将SAP UI5应用嵌入到微信中
- 微信开发系列之四 - 将SAP C4C的数据更改通知发送到微信公众号上
- 微信开发系列之三 - 在微信公众号里发起SAP C4C Account的创建
- 微信开发系列之二 - 在微信公众号里开发一个自动应答的图灵机器人
- 微信开发系列之一 - 微信公众号开发的开发环境搭建
- SAP Commerce开发之如何找到某个页面对应的JSP实现页面
- 如何用代码读取SAP CRM的Categorization Schema
- 如何使用代码创建SAP CRM Service Request subject
- 微信开发系列之八 - 微信公众号的地图集成
- vivo 悟空活动中台 - 栅格布局方案