Java 内部类

嵌套类(Nested Classes)

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.

–来自 Oracle 官方Java指南

嵌套类分为两种,静态和非静态。非静态的称为 内部类,静态的称为静态嵌套类(也是我们说的 静态内部类

简单理解就是:如果把类比喻成鸡蛋,内部类为蛋黄,外部类是蛋壳。那么静态类相当于熟鸡蛋,就算蛋壳破碎(外部类没有实例化),蛋黄依然完好(内部类可以实例化);而非静态类相当于生鸡蛋,蛋壳破碎(无实例化),蛋黄也会跟着xx(不能实例化)。

  • Non-static nested classes (inner classes) have access to other members of the enclosing class, even if they are declared private.

  • Static nested classes do not have access to other members of the enclosing class. 问题:不能使用 static 修饰的变量/方法?看完试一下。

为什么使用嵌套类?

内部类:

  1. 是一种逻辑分组:当一个类仅在另一个类中使用时,嵌套在一起,成为 “助手类”;
  2. 可以更好的封装:B嵌套在A中,B可以访问A的所有成员变量和方法(包括定义为 private 的),同时 B 自身可以对外界隐藏起来,只给 A 用;
  3. 增加代码可读性和可维护性;

静态嵌套类,就是一个独立的类。更多的知识表明类结构和命名空间,表明静态嵌套类和外部类是强关联的,专用于外部类。

  • 比如有A,B两个类,B有点特殊,虽然可以独立存在,但只被A使用。
    这时候怎么办?如果把B并入A里,复杂度提高,搞得A违反单一职责。如果B独立,又可能被其他类(比如同一个包下的C)依赖,不符合设计的本意。所以不如将其变成A.B,等于添加个注释,告诉其他类别使用B了,它只跟A玩。– 参考知乎铁心男回答
  • 例如:ThreadLocalThreadLocalMap

静态嵌套类和非静态内部类最大的区别是:非静态内部类编译后隐式保存着外部类的引用(就算外部类对象没用了也GC不掉),但是静态内部类没有。

内部类(Inner Classes)

特性:

  1. 与其外部类的实例相关联,就像是实例方法和实例参数一样;
  2. 可以直接(direct)访问外部类的方法和字段;
  3. 因为与实例相关联,不能在内部类中定义任何 static 成员;
    • an inner class is associated with an instance, it cannot define any static members itself.
  4. 隐式保存着外部类的引用;

使用:

  • 需要先实例化外部类,在实例化内部类。
1
2
OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();

注意:本地类(在方法中定义的类)和 匿名类 都属于内部类

静态嵌套类(Static Nested Classes)

特性:

  1. 与外部类相关联,与类方法和类变量(被 static 修饰的 method 和 field);
  2. 使用静态嵌套类与使用其他顶级类(top-level class)一样(定义静态,非静态成员),与外部类的实例交互就像任何其他顶级类一样;
    • 只是为了方便打包,嵌套在一个顶级类中的顶级类;(仅表明结构上是归属外部类的;)
  3. 不能直接访问外部类中非静态成员(因为这些属于类对象的);
    • 但是可以通过实例化外部类的方式访问;(参考下面的样例)

使用:

  • 实例化静态嵌套类的方法与顶级类一样。
1
StaticNestedClass staticNestedObject = new StaticNestedClass();

内部类和静态嵌套类的结合样例:

  • 请注意,静态嵌套类与外部类的实例成员进行交互,就像任何其他顶级类一样。
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
public class OuterClass {

String outerField = "Outer field";
static String staticOuterField = "Static outer field";

class InnerClass {
void accessMembers() {
System.out.println(outerField);
System.out.println(staticOuterField);
}
}

static class StaticNestedClass {
// 这里告诉我们,静态嵌套类中可以像其他顶级类一样使用外部类对象,并访问其成员
void accessMembers(OuterClass outer) {
// Compiler error: Cannot make a static reference to the non-static
// field outerField
// System.out.println(outerField);
// 这样就可以访问
System.out.println(outer.outerField);
System.out.println(staticOuterField);
}
}

public static void main(String[] args) {
System.out.println("Inner class:");
System.out.println("------------");
OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
innerObject.accessMembers();

System.out.println("\nStatic nested class:");
System.out.println("--------------------");
StaticNestedClass staticNestedObject = new StaticNestedClass();
staticNestedObject.accessMembers(outerObject);

System.out.println("\nTop-level class:");
System.out.println("--------------------");
TopLevelClass topLevelObject = new TopLevelClass();
topLevelObject.accessMembers(outerObject);
}
}

public class TopLevelClass {

void accessMembers(OuterClass outer) {
// Compiler error: Cannot make a static reference to the non-static
// field OuterClass.outerField
// System.out.println(OuterClass.outerField);
System.out.println(outer.outerField);
System.out.println(OuterClass.staticOuterField);
}
}

什么是 package private

类成员(Class Members)

  • Class variables
  • Class methods

static 修饰的 field 称为 static field or class variables,它们属于类,而不是类生成的 object,

位置:

  • 类的实例(对象)共享 static field,这个变量的位置在内存中是固定的(fixed)。

  • 普通的 field,属于 object,所以每个对象的field都存在不同的内存中。

使用:

  • 通常使用 Class.static_field 的形式,
  • (可以但是最好不要用的形式:)<Object>.static_field,因为这样无法体现 static_field 是类变量。

静态变量/方法与非静态变量/方法的使用组合:

  • non-static 可以使用 static,反过来不行;
  • non-static 可以使用 non-static 和 static;
  • static 仅可以使用 static;
  • static 修饰的方法中,不能使用 this关键字(因为没有可供 this 引用的实例)

主要因为生成的时间不一样,static修饰的在类加载完就存在了,non-static 的则是需要创建对象后才存在,所以 static 修饰的不能使用 non-static,因为使用内存中没有的对象。

this 关键字

在实例方法或构造函数中,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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
举例:
1. 与 field 一起使用:
通常是发生了遮蔽(特定作用域中(例如在构造函数中)出现了与外部作用域中相同名称的变量名(如下例子),导致在这个特定作用域中定义的同名变量隐藏了外部的同名变量,这时如果要使用外部变量就不能直接使用变量名,需要用 this 关键字)
// 原因是 构造函数的参数与field重名,导致被遮蔽(shadow),所以通过 this,表明是要赋值给 Point 类中的field。
public class Point {
public int x = 0;
public int y = 0;

//constructor
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

public class ShadowTest {

public int x = 0;

class FirstLevel {

public int x = 1;

void methodInFirstLevel(int x) {
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
// 通过所属的类名引用包含较大作用域的成员变量
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
}
}
public static void main(String... args) {
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
运行结果:
x = 23
this.x = 1
ShadowTest.this.x = 0

2. 与 构造函数一起使用:
// 这里希望提供有默认值的构造函数,使用时根据参数个数选择合适的构造函数;
// 注意,使用时 this(xxx) 需要写在构造函数的第一行
public class Rectangle {
private int x, y;
private int width, height;

public Rectangle() {
this(0, 0, 1, 1);
}
public Rectangle(int width, int height) {
this(0, 0, width, height);
}
public Rectangle(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
...
}

3. ThreadLocal 中的例子:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 这里的 this 是调用get() 方法的对象的引用
ThreadLocalMap.Entry e = map.getEntry(this);
...
}

// 3中的具体使用,this 就是 threadLocalName 对象的引用
ThreadLocal<String> threadLocalName = ThreadLocal.withInitial(() -> Thread.currentThread().getName());
threadLocalName.get()
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2023 ligongzhao
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信