参考文献

Nested Classes

Terminology: Nested classes are divided into two categories: non-static and static. Non-static nested classes are called inner classes. Nested classes that are declared static are called static nested classes.

术语:嵌套类分为两类:非静态类静态类.

  • 非静态嵌套类称为内部类(inner class).

  • 声明为static的嵌套类称为静态嵌套类(static Nested class).

1
2
3
4
5
6
7
8
9
class OuterClass {
...
class InnerClass {
...
}
static class StaticNestedClass {
...
}
}
  • 嵌套类是其封闭类的成员.非静态嵌套类(内部类)可以访问封闭类的其他成员,即使它们被声明为私有.静态嵌套类无权访问封闭类的其他成员.作为 的成员,OuterClass可以将嵌套类声明为privatepublicprotectedpackage private.(外部类只能声明public封装 private.)
  • 静态内部类不持有外部类的引用.
  • 非静态内部类持有外部类的引用.

内部类

与实例方法和变量一样,内部类与其封闭类的实例相关联,并且可以直接访问该对象的方法和字段.此外,由于内部类与实例相关联,它本身不能定义任何静态成员.

内部类的分类

  • 非静态成员内部类( a non-static member class )
  • 局部内部类(a local class)
  • 匿名内部类(an anonymous class)

内部类的特点

  • 它体现了一种代码的隐藏机制和访问控制机制,内部类与所在外部类有一定的关系,往往只有该外部类调用此内部类,所以没有必要专门用一个Java文件存放这个类.
  • 一般的类是不允许用private修饰符的,但是内部类却可以,该实例中,类Inner只对Outer可见,其他的类无法访问的到Inner类.
  • 注意,这里有个例外,如果内部类的访问修饰符被设定为public,那么它是可以被其他类使用的,但是必须经由外部类来实例化内部类.

内部类的注意事项

  • 如果内部类声明了静态初始化程序,则这是编译时错误.

    1
    2
    3
    4
    5
    6
    class External2 {
    class Inner {
    private static final int innerAge =10; // √
    private static int innerAgeNotFinal =10; // × 编译失败
    }
    }
  • 如果内部类声明了显式或隐式声明为静态的成员,除非该成员是常量变量,否则会导致编译时错误.

非静态成员内部类

定义在另一个类中

示例
1
2
3
4
5
6
7
// 外部类
class External{
// 内部类
class Inner{

}
}
特点
  • 成员内部类可以无条件访问外部类的属性和方法;

  • 外部类想要访问内部类的属性或方法时,必须要创建一个内部类对象,然后通过该对象访问内部类的属性或方法;

    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
    // 外部类
    @Slf4j
    class External {
    private int age =20;
    private String name="External";
    public void externalMethod() {
    log.info("外部类的方法externalMethod");
    // 外部类无法直接调用内部类的方法/获取内部类的属性,需要创建对象来访问
    Inner inner = new Inner();
    log.info("内部类的属性age: {}", inner.innerAge);
    log.info("内部类的属性name: {}", inner.innerName);
    inner.innerMethod();
    }
    // 内部类
    class Inner {
    private int innerAge =10;
    private String innerName="Inner";
    public void innerMethod() {
    // 无条件获取外部类的属性和方法
    log.info("内部类的方法innerMethod");
    log.info("外部类的属性age: {}", age);
    log.info("外部类的属性name: {}", name);
    }
    public void innerMethod2() {
    log.info("内部类的方法innerMethod2");
    externalMethod();
    }
    }
    }
    // 输出
    22:44:10.053 [main] INFO com.holelin.sundry.test.common.External - 外部类的方法externalMethod
    22:44:10.056 [main] INFO com.holelin.sundry.test.common.External - 内部类的属性age:10
    22:44:10.057 [main] INFO com.holelin.sundry.test.common.External - 内部类的属性name:Inner
    22:44:10.057 [main] INFO com.holelin.sundry.test.common.External - 内部类的方法innerMethod
    22:44:10.057 [main] INFO com.holelin.sundry.test.common.External - 外部类的属性age:20
    22:44:10.057 [main] INFO com.holelin.sundry.test.common.External - 外部类的属性name:External
  • 如果成员内部类的属性或者方法与外部类的同名,将导致外部类的这些属性与方法在内部类被隐藏,也可按照该格式调用:外部类.this.属性/方法.

    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
    38
    39
    40
    41
    42
    43
    44
    45
    46
    // 外部类
    @Slf4j
    class External {
    private int age = 20;
    private String name = "External";
    private Date time;

    public void externalMethod() {
    log.info("外部类的方法externalMethod");
    Inner inner = new Inner();
    log.info("内部类的属性age: {}", inner.innerAge);
    log.info("内部类的属性name: {}", inner.innerName);
    inner.innerMethod();
    }

    public void repeatMethod() {
    log.info("外部类的方法repeatMethod");
    }

    // 内部类
    class Inner {
    private int innerAge = 10;
    private String innerName = "Inner";
    private Date time;

    public void innerMethod() {
    // 无条件获取外部类的属性和方法
    log.info("内部类的方法innerMethod");
    log.info("外部类的属性age: {}", age);
    log.info("外部类的属性name: {}", name);
    }

    public void innerMethod2() {
    log.info("内部类的方法innerMethod2");
    externalMethod();
    }

    public void repeatMethod() {
    log.info("内部类的方法repeatMethod");
    // 内部类和外部类方法同名 需要按照`外部类.this.同名方法名称`
    External.this.repeatMethod();
    // 内部类和外部类方法同名 需要按照`外部类.this.属性名称`
    Date tempTime = External.this.time;
    }
    }
    }
  • 内部类只是一个编译时的概念,一旦编译成功,就会成为完全不同的两个类.对于一个名为Outer的外部类和其内部定义的名为Inner的内部类,编译完后会生成External.classExternal$Inner.class两个类

    img

类的创建
  • 成员内部类是寄生于外部类,创建内部类对象就必须先创造外部类对象.之后创建内部类有两种方式

    • 外部类实例.new 内部类()
    • 通过外部类间接创建内部类
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class InnerTest {
public static void main(String[] args) {
External external = new External();
external.externalMethod();
external.repeatMethod();
// 方法一: `外部类实例.new 内部类()`
External.Inner inner1 = external.new Inner();
// 方法二: 通过外部类间接创建内部类
External.Inner inner2 = external.getInner();
}
}

// 外部类
@Slf4j
class External {
private int age = 20;
private String name = "External";
private Date time;

public void externalMethod() {
log.info("外部类的方法externalMethod");
Inner inner = new Inner();
log.info("内部类的属性age: {}", inner.innerAge);
log.info("内部类的属性name: {}", inner.innerName);
inner.innerMethod();
}

public void repeatMethod() {
log.info("外部类的方法repeatMethod");
}

public Inner getInner() {
return new Inner();
}

// 内部类
class Inner {
private int innerAge = 10;
private String innerName = "Inner";
private Date time;

public void innerMethod() {
// 无条件获取外部类的属性和方法
log.info("内部类的方法innerMethod");
log.info("外部类的属性age: {}", age);
log.info("外部类的属性name: {}", name);
}

public void innerMethod2() {
log.info("内部类的方法innerMethod2");
externalMethod();
}

public void repeatMethod() {
log.info("内部类的方法repeatMethod");
// 内部类和外部类方法同名 需要按照`外部类.this.同名方法名称`
External.this.repeatMethod();
// 内部类和外部类方法同名 需要按照`外部类.this.属性名称`
Date tempTime = External.this.time;
}
}
}
访问权限
1
2
3
4
5
6
7
8
9
10
11
// 外部类
class External{
// 内部类
private class PrivateInner{}
// 内部类
public class PublicInner{}
// 内部类
class DefaultInner{}
// 内部类
protected class ProtectedInner {}
}

img

使用场景
  • 不可能有其他类使用该内部类
  • 该内部类不能被其他类使用,可能会导致错误.这是内部类使用比较多的一个场景.

局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,

  • 它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内.
  • 局部内部类就像方法里面的局部变量一样,是不能有public、protected、private及static修饰符的.
示例
1
2
3
4
5
6
7
class External {
public void externalMethod() {
class Inner {

}
}
}
特点
  • 和成员内部类的区别在于局部内部类的访问权限仅限于方法或作用域内.
  • 注意事项:局部内部类就像局部变量一样,前面不能访问修饰符以及static修饰符.

匿名内部类

通过接口来实现

匿名内部类本质上是一个重写或实现了父类或接口的子类对象.

示例
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 InnerTest {
public static void main(String[] args) {
new AnonymousInner(){
@Override
public void doSomething() {

}
};
new External(){
@Override
public void externalMethod() {

}
};
}
}

interface AnonymousInner {
void doSomething();
}

class External{
public void externalMethod() {}
}

特点

  • 匿名内部类在编译的时候由系统自动起名为InnerTest$1.class.
  • 匿名内部类用于集成其他类或者实现接口,并不需要增加额外的方法,只是对集成方法的实现或是重写
使用场景
  • 当类或接口类型作为参数传递时,可以直接使用匿名内部类方式创建对应的对象(接口回调)

静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static

示例
1
2
3
4
class External {
static class Inner {
}
}
特点
  • 静态内部类是不需要属于外部类的对象(它不持有指向外部类对象的引用this),属于外部类,并且它不能使用外部类的非静态成员或方法;
  • 因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体对象.它唯一的作用就是随着类的加载(而不是随着对象的产生)而产生.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class External {
private static int age = 10;
private String name = "name";

public void externalMethod() {
log.info("外部类的方法externalMethod");
}
public static void externalStaticMethod() {
log.info("外部类的方法externalMethod");
}
static class Inner {
public void innerMethod() {
log.info("外部类私有静态成员变量: {}", age);
externalStaticMethod();
// 报错,静态内部类无法引用非外部类的非静态属性/方法
log.info("外部类私有成员变量: {}", name);
externalMethod();

}
}
}
类的创建
1
2
3
4
5
6
public class InnerTest {
public static void main(String[] args) {
External.Inner inner = new External.Inner();
inner.innerMethod();
}
}