内部类基础
在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。下面就先来了解一下这四种内部类的用法
成员内部类 :类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
/**
* 成员内部类
*
*/
public class Circle {
private double radius = 0;
public static int count = 1;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}
private Draw getDrawInstance() {
return new Draw();
}
//内部类
class Draw {
private double radius = 0;
public void drawSahpe() {
System.out.println(this.radius); //外部类的private成员
//如果重名的话 外部类.this.成员变量 外部类.this.成员方法
System.out.println(Circle.this.radius); //外部类的private成员
System.out.println(count); //外部类的静态成员
}
}
}
image.png
-
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
image.png
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
局部内部类是定义在一个方法或者是一个作用域里面的类,他和成员内部类的区别在于局部内部类的访问权限仅限于方法内或者是该作用域里面 局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。
匿名内部类
匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护
匿名内部类也是不能有访问修饰符和 static 修饰符的。
history_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
匿名内部类是唯一一种没有构造器的类
image.png因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调
静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
静态内部类不能够访问非静态的变量,这样会出错
为什么成员内部类可以无条件访问外部类的成员?
image.png-
编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件
image.png
编译器会默认为成员内部类添加了一个指向外部类对象的引用,那么这个引用是如何赋初值的呢?下面接着看内部类的构造器:
- 通过:javap -v Outter$Inner 去反编译
PS D:\Program Files\Android\WorckSpace\app\src\main\java\com\example\myapplication> javac OutterText.java
PS D:\Program Files\Android\WorckSpace\app\src\main\java\com\example\myapplication> javap -v OutterText$Inner.class
Classfile /D:/Program Files/Android/WorckSpace/app/src/main/java/com/example/myapplication/OutterText.class
Last modified 2020-4-1; size 480 bytes
MD5 checksum d20a1abc945e5a7ce4f5e59a846a42be
Compiled from "OutterText.java"
public class OutterText
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #5.#21 // OutterText.inner:LOutterText$Inner;
#3 = Class #22 // OutterText$Inner
#4 = Methodref #3.#23 // OutterText$Inner."<init>":(LOutterText;)V
#5 = Class #24 // OutterText
#6 = Class #25 // java/lang/Object
#7 = Utf8 Inner
#8 = Utf8 InnerClasses
#9 = Utf8 inner
#10 = Utf8 LOutterText$Inner;
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 getInnerInstance
#16 = Utf8 ()LOutterText$Inner;
#17 = Utf8 StackMapTable
#18 = Utf8 SourceFile
#19 = Utf8 OutterText.java
#20 = NameAndType #11:#12 // "<init>":()V
#21 = NameAndType #9:#10 // inner:LOutterText$Inner;
#22 = Utf8 OutterText$Inner
#23 = NameAndType #11:#26 // "<init>":(LOutterText;)V
#24 = Utf8 OutterText
#25 = Utf8 java/lang/Object
#26 = Utf8 (LOutterText;)V
{
public OutterText();
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:LOutterText$Inner;
9: return
LineNumberTable:
line 4: 0
line 3: 4
line 6: 9
public OutterText$Inner getInnerInstance();
descriptor: ()LOutterText$Inner;
flags: ACC_PUBLIC
Code:
stack=4, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field inner:LOutterText$Inner;
4: ifnonnull 19
7: aload_0
8: new #3 // class OutterText$Inner
11: dup
12: aload_0
13: invokespecial #4 // Method OutterText$Inner."<init>":(LOutterText;)V
16: putfield #2 // Field inner:LOutterText$Inner;
19: aload_0
20: getfield #2 // Field inner:LOutterText$Inner;
23: areturn
LineNumberTable:
line 9: 0
line 10: 7
line 11: 19
StackMapTable: number_of_entries = 1
frame_type = 19 /* same */
}
SourceFile: "OutterText.java"
InnerClasses:
protected #7= #3 of #5; //Inner=class OutterText$Inner of class OutterText
内部类class的文件有一个指向外部类对象的指针,看到这里想必大家豁然开朗了。也就是说编译器会默认为成员内部类添加了一个指向外部类对象的引用
为什么局部内部类和匿名内部类只能访问局部final变量?
想必这个问题也曾经困扰过很多人,在讨论这个问题之前,先看下面这段代码:
public class Test {
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();
}
}
上段代码中,如果把变量 a 和 b 前面的任一个 final 去掉,这段代码都编译不过。我们先考虑这样一个问题:
当 test 方法执行完毕之后,变量a的生命周期就结束了,而此时 Thread 对象的生命周期很可能还没有结束,那么在 Thread 的 run 方法中继续访问变量 a 就变成不可能了,但是又要实现这样的效果,怎么办呢?Java 采用了复制的手段来解决这个问题
主要的原因是:会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java 编译器就限定必须将变量 a 限制为 final 变量,不允许对变量 a 进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。
到这里,想必大家应该清楚为何 方法中的局部变量和形参都必须用 final 进行限定了。
终极问题:为什么需要内部类?Thinking in Java 。。。
如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?,答案是:如果这能满足需求,那么久应该这样做。
那么内部类实现一个接口与外围类实现一个接口有什么区别呢?答案是:后者不是总能享受到接口所带来的方便。
-
所以使用内部类最大的原因是:每个内部类都能够独立的继承一个接口的实现,所以无论外部类是否已经继承了某个接口的实现,对内部类其实都没有影响,内部类使得多继承变得完美,或者是更加完整吧
方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
方便编写事件驱动程序。
方便编写线程代码。
完成。。。。。
网友评论