[关闭]
@TryLoveCatch 2022-05-11T09:53:38.000000Z 字数 5647 阅读 657

Java知识体系之内部类

Java知识体系


内部类

定义在一个类中或者方法中的类称作为内部类。
内部类又可以具体细分为成员内部类、局部内部类、匿名内部类、静态内部类。

成员内部类

  1. public class OutClass2 {
  2. private int i = 1;
  3. public static String str = "outclass";
  4. class InnerClass { // 成员内部类
  5. private int i = 2;
  6. public void innerMethod() {
  7. int i = 3;
  8. System.out.println("i=" + i);
  9. System.out.println("i=" + this.i);
  10. System.out.println("i=" + OutClass2.this.i);
  11. System.out.println("str=" + str);
  12. }
  13. }
  14. }
  15. public class TestClass {
  16. public static void main(String[] args) {
  17. //创建内部类对象
  18. OutClass2.InnerClass in = new OutClass2().new InnerClass();
  19. //内部类对象调用自己的方法
  20. in.innerMethod();
  21. }
  22. }

因为内部类依附于外部类存在,所以需要外部类的实例来创建内部类:

  1. new OutClass2().new InnerClass()

注意:不是new OutClass2.InnerClass()

在内部类中,隐式地持有了外部类的引用,所以成员内部类可以无条件的访问外部类的成员属性和成员方法(包括 private 和 static 类型的成员)。

我们编译上述代码,生成两个 class 文件

OutClass2.class
OutClass2$InnerClass.class

我们来看一下OutClass2$InnerClass.class

  1. package com.example.simon.androidlife.innerclass;
  2. import com.example.simon.androidlife.innerclass.OutClass2;
  3. class OutClass2$InnerClass {
  4. private int i;
  5. OutClass2$InnerClass(OutClass2 var1) {
  6. this.this$0 = var1;
  7. this.i = 2;
  8. }
  9. public void innerMethod() {
  10. byte var1 = 3;
  11. System.out.println("i=" + var1);
  12. System.out.println("i=" + this.i);
  13. System.out.println("i=" + OutClass2.access$000(this.this$0));
  14. System.out.println("str=" + OutClass2.str);
  15. }
  16. }

在这个不完整的反编译字节码中,我们看第8行构造函数,

内部类为什么可以直接访问外部类的内容,因为持有外部类的引用,外部类对象会以参数的形式传进来。

再看第17行,OutClass2.access0)) 是什么意思呢?

为了帮助内部类访问外部类的数据,编译器会生成这个 access方法,参数是外部类的引用,如果外部类有N个成员,编译器会生成多个access方法,参数是外部类的引用,如果外部类有 N 个成员,编译器会生成多个 access 方法, 符号后面的数字会会随着不同的声明顺序而改变,可以理解为一种桥接方法

有一个限制是:成员内部类不能创建静态变量/方法。

静态内部类

使用 static 关键字修饰的内部类就是静态内部类,静态内部类和外部类没有任何关系,可以看作是和外部类平级的类。

  1. public class Outclass3 {
  2. private String name;
  3. private int age;
  4. public static class InnerStaticClass {
  5. private String name;
  6. public String getName() {
  7. return name;
  8. }
  9. public int getAge() {
  10. return new Outclass3().age;
  11. }
  12. }
  13. }

编译后的静态内部类:

  1. package com.example.simon.androidlife.innerclass;
  2. import com.example.simon.androidlife.innerclass.Outclass3;
  3. public class Outclass3$InnerStaticClass {
  4. private String name;
  5. public Outclass3$InnerStaticClass() {
  6. }
  7. public String getName() {
  8. return this.name;
  9. }
  10. public int getAge() {
  11. return Outclass3.access$000(new Outclass3());
  12. }
  13. }

局部内部类

局部内部类是指在代码块或者方法中创建的类。
它和成员内部类的区别就是:局部内部类的作用域只能在其所在的代码块或者方法内,在其它地方是无法创建该类的对象。

  1. public class OutClass4 {
  2. private String className = "OutClass";
  3. {
  4. class PartClassOne { // 局部内部类
  5. private void method() {
  6. System.out.println("PartClassOne " + className);
  7. }
  8. }
  9. new PartClassOne().method();
  10. }
  11. public void testMethod() {
  12. class PartClassTwo { // 局部类内部类
  13. private void method() {
  14. System.out.println("PartClassTwo " + className);
  15. }
  16. }
  17. new PartClassTwo().method();
  18. }
  19. }
  1. package com.example.simon.androidlife.innerclass;
  2. import com.example.simon.androidlife.innerclass.OutClass4;
  3. class OutClass4$1PartClassOne {
  4. OutClass4$1PartClassOne(OutClass4 var1) {
  5. this.this$0 = var1;
  6. }
  7. private void method() {
  8. System.out.println("PartClassOne " + OutClass4.access$000(this.this$0));
  9. }
  10. }
  11. ----------------------------------------------------------
  12. package com.example.simon.androidlife.innerclass;
  13. import com.example.simon.androidlife.innerclass.OutClass4;
  14. class OutClass4$1PartClassTwo {
  15. OutClass4$1PartClassTwo(OutClass4 var1) {
  16. this.this$0 = var1;
  17. }
  18. private void method() {
  19. System.out.println("PartClassTwo " + OutClass4.access$000(this.this$0));
  20. }
  21. }

匿名内部类

不创建类对象的引用,而是直接创建的类对象。
一般来说,主要用于方法的参数或者回调。

  1. public class OutClass5 {
  2. private OnClickListener mClickListener;
  3. interface OnClickListener {
  4. void onClick();
  5. }
  6. public OutClass5 setClickListener(final OnClickListener clickListener) {
  7. mClickListener = clickListener;
  8. return this;
  9. }
  10. public void setClickInfo(final String info, int type) {
  11. // 这里!!!!
  12. setClickListener(new OnClickListener() {
  13. @Override
  14. public void onClick() {
  15. System.out.println("click " + info);
  16. }
  17. });
  18. }
  19. }

编译后的匿名内部类

  1. class OutClass5$1 implements OnClickListener {
  2. OutClass5$1(OutClass5 var1, String var2) {
  3. this.this$0 = var1;
  4. this.val$info = var2;
  5. }
  6. public void onClick() {
  7. System.out.println("click " + this.val$info);
  8. }
  9. }

从反编译的代码可以看出:

创建的每个匿名内部类编译器都对应生成一个实现接口的子类,同时创建一个构造函数,构造函数的参数是外部类的引用以及匿名函数中访问的外部类的变量,这个变量还必须final修饰。

小结

外部类会生成一堆access$变量(外部类)的static方法,方便内部类调用。
除了静态内部类,其他三种都持有外部类引用。

应用场景

成员内部类

在 Java 中普通类(非内部类)是不可以设为 private 或者 protected,只能设置成 public、default。
而内部类则可以,因此我们可以利用 private 内部类禁止其他类访问该内部类,从而做到将具体的实现细节完全隐藏。

例如:

  1. public class MyActivity extends AppCompatActivity {
  2. public static final String DATA_VIEW_TYPE = "view_type";
  3. public static final int TYPE_LOGIN = 1;
  4. public static final int TYPE_REGISTER = 2;
  5. private TextView mTitleTv;
  6. private ViewController mViewController;
  7. @Override
  8. protected void onCreate(@Nullable final Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. setContentView(R.layout.activity_my);
  11. }
  12. private class MyAsyncTask extends AsyncTask {
  13. .....
  14. .....
  15. }
  16. }

单例

  1. public class LocationManager{
  2. private static class ClassHolder {
  3. private static final LocationManager instance = new LocationManager();
  4. }
  5. public static LocationManager getInstance() {
  6. return ClassHolder.instance;
  7. }
  8. }

总的来说,内部类一般用于两个场景:

  • 需要用一个类来解决一个复杂的问题,但是又不希望这个类是公共的
  • 需要实现一个接口,但不需要持有它的引用

几个问题

为什么非静态内部类不能创建静态变量/方法

内部类其实也可以认为是外部类的一个成员变量,想要访问内部类,必须先实例化外部类,然后通过外部类才能访问内部类。

1、static类型的属性和方法,在类加载的时候就会存在于内存中。
2、要想使用某个类的static属性和方法,那么这个类必须要加载到虚拟机- 中。
3、非静态内部类并不随外部类一起加载,只有在实例化外部类之后才会加载。

现在考虑这个情况:在外部类并没有实例化,内部类还没有加载,这时候如果调用内部类的静态成员或方法,内部类还没有加载,却试图在内存中创建该内部类的静态成员,这明显是矛盾的。所以非静态内部类不能有静态成员变量或静态方法。

为什么匿名内部类中使用到的外部局部变量必须为final?

一个对象引用被传递给方法时,方法中会创建一份本地临时引用,它和参数指向同一个对象,但却是不同的,所以你在方法内部修改参数的内容,在方法外部是不会感知到的。

而匿名内部类是创建一个对象并返回,这个对象的方法被调用的时机不确定,方法中有修改参数的可能,如果在匿名内部类中修改了参数,外部类中的参数是否需要同步修改呢?

因此,Java 为了避免这种问题,限制匿名内部类访问的变量需要使用 final 修饰,这样可以保证访问的变量不可变。

参考

Java 基础巩固:内部类的字节码学习和实战使用场景
https://www.jianshu.com/p/b6ddf26ac211

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注