【Java学习】内部类

关于内部类

内部类(inner class) : 定义在另一个类重的类

为什么需要内部类?

  • 内部类方法可以访问该类定义所在作用域中的数据,包括被 private 修饰的私有数据
  • 内部类可以对同一包中的其他类隐藏起来
  • 内部类可以一定程度修复 java 单继承的缺陷
  • 当我们想要定义一个回调函数却不想写大量代码的时候我们可以选择使用匿名内部类来实现

内部类的分类

内部类可以分为:静态内部类(嵌套类)和非静态内部类。非静态内部类又可以分为:成员内部类、方法内部类、匿名内部类。

静态内部类和非静态内部类的区别

  1. 静态内部类可以有静态成员,而非静态内部类则不能有静态成员。
  2. 静态内部类可以访问外部类的静态变量,而不可访问外部类的非静态变量;
  3. 非静态内部类的非静态成员可以访问外部类的非静态变量。
  4. 静态内部类的创建不依赖于外部类,而非静态内部类必须依赖于外部类的创建而创建。

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ClassOuter {
private int noStaticInt = 1;
private static int STATIC_INT = 2;

public void fun() {
System.out.println("外部类方法");
}

public class InnerClass {
//static int num = 1; 此时编辑器会报错 非静态内部类则不能有静态成员
public void fun(){
//非静态内部类的非静态成员可以访问外部类的非静态变量。
System.out.println(STATIC_INT);
System.out.println(noStaticInt);
}
}

public static class StaticInnerClass {
static int NUM = 1;//静态内部类可以有静态成员
public void fun(){
System.out.println(STATIC_INT);
//System.out.println(noStaticInt); 此时编辑器会报 不可访问外部类的非静态变量错
}
}
}

public class TestInnerClass {
public static void main(String[] args) {
//非静态内部类 创建方式1
ClassOuter.InnerClass innerClass = new ClassOuter().new InnerClass();
//非静态内部类 创建方式2
ClassOuter outer = new ClassOuter();
ClassOuter.InnerClass inner = outer.new InnerClass();
//静态内部类的创建方式
ClassOuter.StaticInnerClass staticInnerClass = new ClassOuter.StaticInnerClass();
}
}

成员内部类

成员内部类也是最普通的内部类,它是外围类的一个成员,所以他是可以无限制的访问外围类的所有 成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。
在成员内部类中要注意两点:

  1. 成员内部类中不能存在任何static的变量和方法,成员内部类里不能有静态内部类
  2. 成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class OuterClass {
private String str;

public void outerDisplay(){
System.out.println("outerClass...");
}

public class InnerClass{
public void innerDisplay(){
//使用外围内的属性
str = "inner...";
System.out.println(str);
//使用外围内的方法
outerDisplay();
}
}

/*推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时 */
public InnerClass getInnerClass(){
return new InnerClass();
}

public static void main(String[] args) {
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.getInnerClass();
inner.innerDisplay();
}
}
--------------------
innner...
outerClass...

局部内部类

定义在方法或作用域中的内部类,出了该方法,类就会失效。
定义在方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Parcel5 {
public Destionation destionation(String str){
class PDestionation implements Destionation{
private String label;
private PDestionation(String whereTo){
label = whereTo;
}
public String readLabel(){
return label;
}
}
return new PDestionation(str);
}

public static void main(String[] args) {
Parcel5 parcel5 = new Parcel5();
Destionation d = parcel5.destionation("chenssy");
}
}

定义在作用域中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Parcel6 {
private void internalTracking(boolean b){
if(b){
class TrackingSlip{
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip(){
return id;
}
}
TrackingSlip ts = new TrackingSlip("chenssy");
String string = ts.getSlip();
}
}

public void track(){
internalTracking(true);
}

public static void main(String[] args) {
Parcel6 parcel6 = new Parcel6();
parcel6.track();
}
}

匿名内部类

匿名类实现接口和抽象类定义的抽象方法,快速创建一个抽象类的实例,可以出现在任何有代码的地方。

  1. 匿名内部类是没有访问修饰符的。
  2. 匿名内部类必须继承一个抽象类或者实现一个接口
  3. 匿名内部类中不能存在任何静态成员或方法
  4. 匿名内部类是没有构造方法的,因为它没有类名。
  5. 与局部内部相同匿名内部类也可以引用局部变量。此变量也必须声明为 final
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class OuterClass {
public InnerClass getInnerClass(final int num,String str2){
return new InnerClass(){
int number = num + 3;
public int getNumber(){
return number;
}
}; /* 注意:分号不能省 */
}

public static void main(String[] args) {
OuterClass out = new OuterClass();
InnerClass inner = out.getInnerClass(2, "chenssy");
System.out.println(inner.getNumber());
}
}

interface InnerClass {
int getNumber();
}

----------------
Output:
5

为什么局部变量需要final修饰呢

原因是:因为局部变量和匿名内部类的生命周期不同。
匿名内部类是创建后是存储在堆中的,而方法中的局部变量是存储在Java栈中,当方法执行完毕后,就进行退栈,同时局部变量也会消失。那么此时匿名内部类还有可能在堆中存储着,那么匿名内部类要到哪里去找这个局部变量呢?
为了解决这个问题编译器为自动地帮我们在匿名内部类中创建了一个局部变量的备份,也就是说即使方法执结束,匿名内部类中还有一个备份,自然就不怕找不到了。
但是问题又来了。如果局部变量中的a不停的在变化。那么岂不是也要让备份的a变量无时无刻的变化。为了保持局部变量与匿名内部类中备份域保持一致。编译器不得不规定死这些局部域必须是常量,一旦赋值不能再发生变化了。所以为什么匿名内部类应用外部方法的域必须是常量域的原因所在了。

特别注意:在Java8中已经去掉要对final的修饰限制,但其实只要在匿名内部类使用了,该变量还是会自动变为final类型(只能使用,不能赋值)。

内部类会引起的问题

  1. 如果一个匿名内部类没有被任何引用持有,那么匿名内部类对象用完就有机会被回收。
  2. 如果内部类仅仅只是在外部类中被引用,当外部类的不再被引用时,外部类和内部类就可以都被GC回收。
  3. 如果当内部类的引用被外部类以外的其他类引用时,就会造成内部类和外部类无法被GC回收的情况,即使外部类没有被引用,因为内部类持有指向外部类的引用)。

参考


【Java学习】内部类
http://liuminxuan.github.io/2020/09/01/Java学习笔记:内部类/
发布于
2020年9月1日
许可协议