首页 > 编程语言 >(半原创)java-内部类

(半原创)java-内部类

时间:2023-02-26 16:34:27浏览次数:55  
标签:原创 内部 Utf8 public Inner java Outter class

前言

本章节使用 JDK 环境版本如下 :

C:\Users\chenjz20>java -version
java version "1.8.0_192"
Java(TM) SE Runtime Environment (build 1.8.0_192-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.192-b12, mixed mode)

内部类的分类

  • 成员内部类
  • 局部内部类
  • 匿名内部类
  • 静态内部类

成员内部类

public class Circle {
    private double radius = 0;

    public Circle(double radius) {
        this.radius = radius;
        getDrawInstance().drawSahpe();   //1. 外部类返回内部类
    }

    public Draw getDrawInstance() {
        return new Draw();
    }

    class Draw {     //内部类
        public void drawSahpe() {
            System.out.println(radius);  //2. 外部类访问外部类的变量
        }
    }


    public static void main(String[] args) {
        Circle circle = new Circle(12.5D);
        // 3. 创建对象返回内部类方法
        circle.getDrawInstance().drawSahpe();

    }


}


成员内部类和外部类返回调用之间的关系从上面的代码可以看到

局部内部类

class People{
    public People() {
         
    }
}
 
class Man{
    public Man(){
         
    }
     
    public People getWoman(){
        class Woman extends People{   //局部内部类
            int age =0;
        }
        return new Woman();
    }
}

在方法中创建一个类 ,额 ,还没见过这种~

匿名内部类

匿名内部类大家就熟了

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        test.test1();

    }


    public  void  test1(){
        final int i = 5;
        new Thread(() -> {
            int a = i + 1;
            System.out.println("a: "+ a);
        }).start();


        final MyClass myClass  = new  Test.MyClass();
        new Thread(() -> {
             myClass.myInt++;
            System.out.println("myInt: "+ myClass.myInt);
        }).start();
    }


    class  MyClass {
        private  int myInt = 10;

    }
}


上面的例子用到了上篇文章讲的 final变量 , 假如要让 MyClass成为一个线程安全的类这样肯定是不行的 , 即使在传参数的时候定义了 final

回到我们今天的内部类
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

静态内部类

    静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

内部类是为了隐藏, 现在又通过 static 暴露出来, 那么静态内部类存在的动机是啥呢 ?

public class Test {
    public static void main(String[] args)  {
        Outter.Inner inner = new Outter.Inner();
    }
}
 
class Outter {
    public Outter() {
         
    }
     
    static class Inner {
        public Inner() {
             
        }
    }
}

深入内部类

为什么成员内部类可以无条件访问外部类的成员?

原因是内部类的构造函数持有了一个外部对象的引用
我们以下面这段代码为例子 , 代码来源 : https://www.cnblogs.com/dolphin0520/p/3811445.html

public class Outter {
    private Inner inner = null;
    public Outter() {
         
    }
     
    public Inner getInnerInstance() {
        if(inner == null)
            inner = new Inner();
        return inner;
    }
      
    protected class Inner {
        public Inner() {
             
        }
    }
}

编译以后我们可以看到 , 内部类也会生成一个 class 文件
img

然后我们查看一下内部类的字节码 , 命令是

 javap -v Outter$Inner

看一下输出

PS E:\java_project\my\test\justjava\target\classes> javap -v Outter$Inner
Classfile /E:/java_project/my/test/justjava/target/classes/Outter.class
  Last modified 2022-11-25; size 531 bytes
  MD5 checksum e501b1ac0e245a8ccec8e300609f28f1
  Compiled from "Outter.java"
public class Outter
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#23         // java/lang/Object."<init>":()V
   #2 = Fieldref           #5.#24         // Outter.inner:LOutter$Inner;
   #3 = Class              #25            // Outter$Inner
   #4 = Methodref          #3.#26         // Outter$Inner."<init>":(LOutter;)V
   #5 = Class              #27            // Outter
   #6 = Class              #28            // java/lang/Object
   #7 = Utf8               Inner
   #8 = Utf8               InnerClasses
   #9 = Utf8               inner
  #10 = Utf8               LOutter$Inner;
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               LOutter;
  #18 = Utf8               getInnerInstance
  #19 = Utf8               ()LOutter$Inner;
  #20 = Utf8               StackMapTable
  #21 = Utf8               SourceFile
  #22 = Utf8               Outter.java
  #23 = NameAndType        #11:#12        // "<init>":()V
  #24 = NameAndType        #9:#10         // inner:LOutter$Inner;
  #25 = Utf8               Outter$Inner
  #26 = NameAndType        #11:#29        // "<init>":(LOutter;)V
  #27 = Utf8               Outter
  #28 = Utf8               java/lang/Object
  #29 = Utf8               (LOutter;)V
{
  public Outter();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: aconst_null
         6: putfield      #2                  // Field inner:LOutter$Inner;
         9: return
      LineNumberTable:
        line 3: 0
        line 2: 4
        line 5: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   LOutter;

  public Outter$Inner getInnerInstance();
    descriptor: ()LOutter$Inner;
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field inner:LOutter$Inner;
         4: ifnonnull     19
         7: aload_0
         8: new           #3                  // class Outter$Inner
        11: dup
        12: aload_0
        13: invokespecial #4                  // Method Outter$Inner."<init>":(LOutter;)V
        16: putfield      #2                  // Field inner:LOutter$Inner;
        19: aload_0
        20: getfield      #2                  // Field inner:LOutter$Inner;
        23: areturn
      LineNumberTable:
        line 8: 0
        line 9: 7
        line 10: 19
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      24     0  this   LOutter;
      StackMapTable: number_of_entries = 1
        frame_type = 19 /* same */
}
SourceFile: "Outter.java"
InnerClasses:
     protected #7= #3 of #5; //Inner=class Outter$Inner of class Outter

其中最后

InnerClasses:
     protected #7= #3 of #5; //Inner=class Outter$Inner of class Outter

对应 #7=#3可以看到从常量池中传来 Outter 的一个引用 ,即传过来一个父类的引用 . 所以说成员内部类必须依赖与外部类的存在

为什么局部内部类和匿名内部类只能访问局部final变量?

我们先思考一下 , 当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了复制的手段来解决这个问题。将这段代码的字节码反编译可以得到下面的内容:

public class Test1 {
    public static void main(String[] args)  {

    }

    public void test(final int b) {
        final int a = 10;
        new Thread(){
            public void run() {
                System.out.println(a);
                System.out.println(b);
            };
        }.start();
    }
}

benjious@DESKTOP-UKPC3DN MINGW64 /e/my_project/back_project/java_project/java草稿项目/benjious-justjava-master/justjava/target/classes/test
$ javap -v Test1$1.class
Classfile /E:/my_project/back_project/java_project/java▒ݸ▒▒▒Ŀ/benjious-justjava-master/justjava/target/classes/test/Test1.class
  Last modified 2022-11-27; size 594 bytes
  MD5 checksum 6b02b1709048c50cb67d3167e97bf097
  Compiled from "Test1.java"
public class test.Test1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#26         // java/lang/Object."<init>":()V
   #2 = Class              #27            // test/Test1$1
   #3 = Methodref          #2.#28         // test/Test1$1."<init>":(Ltest/Test1;I)V
   #4 = Methodref          #2.#29         // test/Test1$1.start:()V
   #5 = Class              #30            // test/Test1
   #6 = Class              #31            // java/lang/Object
   #7 = Utf8               InnerClasses
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Ltest/Test1;
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               args
  #18 = Utf8               [Ljava/lang/String;
  #19 = Utf8               test
  #20 = Utf8               (I)V
  #21 = Utf8               b
  #22 = Utf8               I
  #23 = Utf8               a
  #24 = Utf8               SourceFile
  #25 = Utf8               Test1.java
  #26 = NameAndType        #8:#9          // "<init>":()V
  #27 = Utf8               test/Test1$1
  #28 = NameAndType        #8:#32         // "<init>":(Ltest/Test1;I)V
  #29 = NameAndType        #33:#9         // start:()V
  #30 = Utf8               test/Test1
  #31 = Utf8               java/lang/Object
  #32 = Utf8               (Ltest/Test1;I)V
  #33 = Utf8               start
{
  public test.Test1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ltest/Test1;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  args   [Ljava/lang/String;

  public void test(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=3, args_size=2
         0: bipush        10
         2: istore_2
         3: new           #2                  // class test/Test1$1
         6: dup
         7: aload_0
         8: iload_1
         9: invokespecial #3                  // Method test/Test1$1."<init>":(Ltest/Test1;I)V
        12: invokevirtual #4                  // Method test/Test1$1.start:()V
        15: return
      LineNumberTable:
        line 9: 0
        line 10: 3
        line 15: 12
        line 16: 15
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   Ltest/Test1;
            0      16     1     b   I
            3      13     2     a   I
}
SourceFile: "Test1.java"
InnerClasses:
     #2; //class test/Test1$1

线程的这个内部类名称为 Test1$1.class ,
我们先看第一处 :
从下面找到 Test1$1 构造函数相关的字节码在

   #2 = Class              #27            // test/Test1$1
   #3 = Methodref          #2.#28         // test/Test1$1."<init>":(Ltest/Test1;I)V

再看这一句

bipush        10

其中 bipush 代表的是入栈的指令 , 将 10 入栈

这两个地方需要注意一下 .我先说一下结论 :
如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。
而上面指出的字节码分别对弈上面结论的前者和后者, 先看 如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值 , 我们可以看到第一处的字节码 ,内部类的构造方法传进来了一个变量 ,然后如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝 ,也即是 bipush 10 .

复制过去到内部类中, 试想一下,传给线程知道, 这个值被修改了 ,而线程又不知道, 肯定会带来数据不一致的情况 , 为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。

静态内部类有特殊的地方吗?

从前面可以知道,静态内部类是不依赖于外部类的,也就说可以在不创建外部类对象的情况下创建内部类的对象。另外,静态内部类是不持有指向外部类对象的引用的,这个读者可以自己尝试反编译class文件看一下就知道了,是没有Outter this&0引用的。

题外话

看到海子的文章为什么成员内部类可以无条件访问外部类的成员?这个章节中代码Outter$Inner打出的字节码和本文的不同 (应该是 JDK 版本不同导致的),这里贴出来 ,他的字节码 :

E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner
Compiled from "Outter.java"
public class com.cxh.test2.Outter$Inner extends java.lang.Object
  SourceFile: "Outter.java"
  InnerClass:
   #24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/tes
t2/Outter
  minor version: 0
  major version: 50
  Constant pool:
const #1 = class        #2;     //  com/cxh/test2/Outter$Inner
const #2 = Asciz        com/cxh/test2/Outter$Inner;
const #3 = class        #4;     //  java/lang/Object
const #4 = Asciz        java/lang/Object;
const #5 = Asciz        this$0;
const #6 = Asciz        Lcom/cxh/test2/Outter;;
const #7 = Asciz        <init>;
const #8 = Asciz        (Lcom/cxh/test2/Outter;)V;
const #9 = Asciz        Code;
const #10 = Field       #1.#11; //  com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/t
est2/Outter;
const #11 = NameAndType #5:#6;//  this$0:Lcom/cxh/test2/Outter;
const #12 = Method      #3.#13; //  java/lang/Object."<init>":()V
const #13 = NameAndType #7:#14;//  "<init>":()V
const #14 = Asciz       ()V;
const #15 = Asciz       LineNumberTable;
const #16 = Asciz       LocalVariableTable;
const #17 = Asciz       this;
const #18 = Asciz       Lcom/cxh/test2/Outter$Inner;;
const #19 = Asciz       SourceFile;
const #20 = Asciz       Outter.java;
const #21 = Asciz       InnerClasses;
const #22 = class       #23;    //  com/cxh/test2/Outter
const #23 = Asciz       com/cxh/test2/Outter;
const #24 = Asciz       Inner;
 
{
final com.cxh.test2.Outter this$0;
 
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   putfield        #10; //Field this$0:Lcom/cxh/test2/Outter;
   5:   aload_0
   6:   invokespecial   #12; //Method java/lang/Object."<init>":()V
   9:   return
  LineNumberTable:
   line 16: 0
   line 18: 9
 
  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      10      0    this       Lcom/cxh/test2/Outter$Inner;
 
 
}

可以看到同样是 Inner 内部类, 但是他引入父类引用的方式是这样的

final com.cxh.test2.Outter this$0;

引入一个变量 .

参考资料

标签:原创,内部,Utf8,public,Inner,java,Outter,class
From: https://www.cnblogs.com/Benjious/p/16929396.html

相关文章