java 内类和外类的关系

内类有哪些

内类(inner class)是相对于外部类(outer class)来说的,包括:内部类/接口,内部匿名类/接口。

内部类和外部类的关系

内部类是可以访问外部类的成员变量的,包括私有变量(private)、静态变量(static):

public class OuterClass {
    private int privateMem = 1;
    static String staticMem = "staticMem";

    class InnerClass {
        void print() {
            System.out.println("innerClass access " + privateMem + "and " + staticMem);
        }
    }
}

代码成功访问到了“privateMem” 和 “staticMem”,编译器没有报错。
但是外部类却访问不了内部类:
在这里插入图片描述

它是怎么做到呢?

好,当然我们在刚接触java的时候,课本上就已经很清楚的告诉我们这样的关系了。but,why?大家都是类,凭什么内部类就能访问外部类的属性,反过来却不行?它们内在究竟靠什么维护这个关系?
我们把OuterClass.java 用javac 做编译:
在这里插入图片描述
发现编译后生成了两个.class文件,OuterClass 和 OuterClass$InnerClass 从文件名来看,OuterClass$InnerClass 描述了两者的关系,我们看看里面是啥:
OuterClass.class:

public class OuterClass {
    private int privateMem = 1;
    static String staticMem = "staticMem";

    public OuterClass() {
    }

    class InnerClass {
        InnerClass() {
        }

        void print() {
            System.out.println("innerClass access " + OuterClass.this.privateMem + "and " + OuterClass.staticMem);
        }
    }
}

和我们最初的代码,就是多了两个空构造方法。
OuterClass$InnerClass:

class OuterClass$InnerClass {
    OuterClass$InnerClass(OuterClass var1) {
        this.this$0 = var1;
    }

    void print() {
        System.out.println("innerClass access " + OuterClass.access$000(this.this$0) + "and " + OuterClass.staticMem);
    }
}

OuterClass$InnerClass有了很大不同!
首先,OuterClass$InnerClass的构造方法变成了有参的,注意这个参数是个OuterClass的实例var1。并在构造方法里出现了“this.this$0 = var1”。大名鼎鼎的“this$0”出现了,做android的同学在学习内存泄漏的时候经常听到,“this$0”指向了外部类的引用,这句话说的就是这里。“this$0” 是虚拟机为每个类预留的“0”号指针(引用)。

再看print函数,对于变量privateMem 和 staticMem的访问变成了“ OuterClass.access$000(this.this$0) “ 和”OuterClass.staticMem“。
怎么会这样?privateMem哪去了?为啥要这样访问?
OK,这时候就要回到一个问题:private 的限制域
在这里插入图片描述
这里明确说了内部类是可以访问的,其他统统不行。对于OuterClass$InnerClass 来说,OuterClass 是同包下的,OuterClass$InnerClass当然访问不到OuterClass中的任何一个private。OuterClass$InnerClass 是 InnerClass 在jvm运行时的实际替换者,从这个角度,private并不是对内部类可见的,只是jvm用了个小手段“欺骗”了我们,通过了其他方式拿到了private变量。

然而,OuterClass.access$000(this.this$0) 为什么能代表privateMem这个变量呢?

static Type access$xyz(Outer); 是JAVA编译器自动生成的十分重要的方法(该方法的个数由你的内部类要访问的外部类的变量个数相关),目的是:用于内部类访问外部类的数据成员时使用。XYZ为数字。X是按照私有成员在内部类出现的顺序递增的。YZ为02的话,标明是基本变量成员;YZ为00的话标明是对象成员或者函数。

privateMem是OuterClass的第一个private属性的变量,所以OuterClass.access$000(this.this$0)。

而对于staticMem,对于一个同包下的类来讲,访问OuterClass的static变量,当然只需要OuterClass.staticMem就可以。

题外话:
1、OuterClass$InnerClass 的构造函数参数是个实例而不是class,这说明,如果要实例它,必须要有指定的一个外部类的实例,所以,你不能new OuterClass.InnerClass()这样写,因为这个时候不知道它外部类的具体实例是哪个,只能

OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();

这样实例InnerClass。
2、access$XYZ 是虚拟机生成的,开发人员并不能在语言层面调用它,但是可以通过ASM等字节码工具生成类似的代码达到访问的目的,这个角度讲,是不安全的。
3、凭什么说OuterClass$InnerClass 就是InnerClass的替代者?

public class OuterClass {
    private int privateMem = 1;
    private static String staticMem = "staticMem";

    void visit(){
        new InnerClass().print();
    }
    class InnerClass {
        void print() {
            System.out.println("innerClass access " + privateMem +
                    "and " + staticMem);
        }
    }
}

加一个访问方法,javac一下,然后对OuterClass.class javap -v一下:

Classfile /Users/yueshaojun/IdeaProjects/learn/src/main/java/com/media/learn/service/OuterClass.class
  Last modified 2019-5-19; size 774 bytes
  MD5 checksum 3785ea3206188c250d045ba1a200a3a3
  Compiled from "OuterClass.java"
public class com.media.learn.service.OuterClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Fieldref           #8.#28         // com/media/learn/service/OuterClass.staticMem:Ljava/lang/String;
   #2 = Fieldref           #8.#29         // com/media/learn/service/OuterClass.privateMem:I
   #3 = Methodref          #9.#30         // java/lang/Object."<init>":()V
   #4 = Class              #31            // com/media/learn/service/OuterClass$InnerClass
   #5 = Methodref          #4.#32         // com/media/learn/service/OuterClass$InnerClass."<init>":(Lcom/media/learn/service/OuterClass;)V
   #6 = Methodref          #4.#33         // com/media/learn/service/OuterClass$InnerClass.print:()V
   #7 = String             #14            // staticMem
   #8 = Class              #34            // com/media/learn/service/OuterClass
   #9 = Class              #35            // java/lang/Object
  #10 = Utf8               InnerClass
  #11 = Utf8               InnerClasses
  #12 = Utf8               privateMem
  #13 = Utf8               I
  #14 = Utf8               staticMem
  #15 = Utf8               Ljava/lang/String;
  #16 = Utf8               <init>
  #17 = Utf8               ()V
  #18 = Utf8               Code
  #19 = Utf8               LineNumberTable
  #20 = Utf8               visit
  #21 = Utf8               access$000
  #22 = Utf8               (Lcom/media/learn/service/OuterClass;)I
  #23 = Utf8               access$100
  #24 = Utf8               ()Ljava/lang/String;
  #25 = Utf8               <clinit>
  #26 = Utf8               SourceFile
  #27 = Utf8               OuterClass.java
  #28 = NameAndType        #14:#15        // staticMem:Ljava/lang/String;
  #29 = NameAndType        #12:#13        // privateMem:I
  #30 = NameAndType        #16:#17        // "<init>":()V
  #31 = Utf8               com/media/learn/service/OuterClass$InnerClass
  #32 = NameAndType        #16:#36        // "<init>":(Lcom/media/learn/service/OuterClass;)V
  #33 = NameAndType        #37:#17        // print:()V
  #34 = Utf8               com/media/learn/service/OuterClass
  #35 = Utf8               java/lang/Object
  #36 = Utf8               (Lcom/media/learn/service/OuterClass;)V
  #37 = Utf8               print
{
  public com.media.learn.service.OuterClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #3                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field privateMem:I
         9: return
      LineNumberTable:
        line 3: 0
        line 4: 4

  void visit();
    descriptor: ()V
    flags:
    Code:
      stack=3, locals=1, args_size=1
         0: new           #4                  // class com/media/learn/service/OuterClass$InnerClass
         3: dup
         4: aload_0
         5: invokespecial #5                  // Method com/media/learn/service/OuterClass$InnerClass."<init>":(Lcom/media/learn/service/OuterClass;)V
         8: invokevirtual #6                  // Method com/media/learn/service/OuterClass$InnerClass.print:()V
        11: return
      LineNumberTable:
        line 8: 0
        line 9: 11

  static int access$000(com.media.learn.service.OuterClass);
    descriptor: (Lcom/media/learn/service/OuterClass;)I
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field privateMem:I
         4: ireturn
      LineNumberTable:
        line 3: 0

  static java.lang.String access$100();
    descriptor: ()Ljava/lang/String;
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #1                  // Field staticMem:Ljava/lang/String;
         3: areturn
      LineNumberTable:
        line 3: 0

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #7                  // String staticMem
         2: putstatic     #1                  // Field staticMem:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 5: 0
}
SourceFile: "OuterClass.java"
InnerClasses:
     #10= #4 of #8; //InnerClass=class com/media/learn/service/OuterClass$InnerClass of class com/media/learn/service/OuterClass

这里就是jvm运行时的指令表。只看visit方法的:

 void visit();
    descriptor: ()V
    flags:
    Code:
      stack=3, locals=1, args_size=1
         0: new           #4                  // class com/media/learn/service/OuterClass$InnerClass
         3: dup
         4: aload_0
         5: invokespecial #5                  // Method com/media/learn/service/OuterClass$InnerClass."<init>":(Lcom/media/learn/service/OuterClass;)V

发现根本没有InnerClass什么事,都是OuterClass$InnerClass在跑,这就是jvm的小手段。

变一变

在这里插入图片描述
把InnerClass 加上static修饰,发现访问不了OuterClass的private变量了。把标红的去掉查看编译结果

class OuterClass$InnerClass {
    OuterClass$InnerClass() {
    }

    void print() {
        System.out.println("innerClass access and " + OuterClass.access$000());
    }
}

构造方法没有入参了!这意味着,OuterClass$InnerClass 和 OuterClass完全没有了“联系”,InnerClass写在OuterClass内部和单独写成一个文件,效果是一摸一样的。
所以,android的同鞋是不是有种莫名的熟悉?解决内存泄漏常用的手段之一,声明为静态内部类。

内部匿名类和外部类的关系

非静态的方法实例匿名内部类

public class OuterClass {
    private int privateMem = 1;
    static String staticMem = "staticMem";

    void visit() {
        new Runnable() {
            @Override
            public void run() {
                System.out.println("i have accessed " + privateMem + "and " + staticMem);
            }
        }.run();
    }
}

在visit内部跑一个匿名Runnable。编译后
在这里插入图片描述
发现变成了OuterClass${num}这样的形式,jvm按顺序增加为匿名内部类生成class。
OuterClass$1.class

class OuterClass$1 implements Runnable {
    OuterClass$1(OuterClass var1) {
        this.this$0 = var1;
    }

    public void run() {
        System.out.println("i have accessed " + OuterClass.access$000(this.this$0) + "and " + OuterClass.staticMem);
    }
}

这个和内部类的情况一样。

静态的方法实例匿名内部类

在这里插入图片描述
发现privateMem也不给访问了。去掉标红的部分编译后
OuterClass$1.class

final class OuterClass$1 implements Runnable {
    OuterClass$1() {
    }

    public void run() {
        System.out.println("i have accessed and " + OuterClass.staticMem);
    }
}

OuterClass$1 加了final修饰符,并且也不给构造方法加参了。所以访问不到privateMem。
So,Why?
static 的方法是属于类级别的,内部匿名类初始化的时候,如果需要外部类的实例,这个时候外部类的实例是没有的。所以构造方法不会有外部类的实例作为入参。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值