内部类
将一个类定义在另一个类的内部,这就是内部类。
定义言简意赅 ,内涵丰富多彩。
1public class Flight2 {
2 class Comp {
3 private String name;
4
5 public String getName() {
6 return name;
7 }
8
9 public void setName(String name) {
10 this.name = name;
11 }
12 }
13
14 class Dest {
15 private String to;
16
17 public Dest(String to) {
18 this.to = to;
19 }
20
21 public String showDest() {
22 return to;
23 }
24 }
25
26 // use method to get inner class instance
27 public Comp comp() {
28 return new Comp();
29 }
30
31 public Dest dest(String to) {
32 return new Dest(to);
33 }
34
35 public void ship() {
36 Comp c = comp();
37 c.setName("China Air");
38 Dest d = dest("HK");
39 System.out.println("Flight from " + c.getName()
40 + " to " + d.showDest());
41 }
42
43 public static void main(String[] args) {
44 Flight2 f2 = new Flight2();
45 f2.ship();
46
47 Flight2 f2s = new Flight2();
48 Flight2.Comp comp = f2s.comp();
49 Flight2.Dest d2 = f2s.dest("New York");
50 }
51}
通过new
关键字实例化内部类和使用普通类并没有什么区别。
需要说明的是:当创建一个内部类的引用时,需要使用OuterClassName.InnerClassName
这样的格式指明内部类的类型。
访问外部类 #
当生成一个内部类对象时,此对象与制造它的外围对象( enclosing object )就形成了某种联系,内部类能够访问外围对象的所有成员,而不需要任何特殊条件。
考虑如下示例:
1// 内部类实现的接口
2interface Selector {
3 boolean end();
4
5 Object current();
6
7 void next();
8}
9
10public class Sequence {
11 private Object[] items;
12 private int next = 0;
13
14 public Sequence(int size) {
15 items = new Object[size];
16 }
17
18 public void add(Object o) {
19 if (next < items.length) {
20 items[next++] = o;
21 }
22 }
23
24 // 内部类实现自接口
25 // 这是一个private修饰的内部类
26 private class SequenceSelector implements Selector {
27 private int i = 0;
28
29 // 内部类直接访问了外围类的私有域
30 @Override
31 public boolean end() {
32 return i == items.length;
33 }
34
35 @Override
36 public Object current() {
37 return items[i];
38 }
39
40 @Override
41 public void next() {
42 if (i < items.length)
43 i++;
44 }
45 }
46
47 // 获取内部类实例
48 public Selector selector() {
49 return new SequenceSelector();
50 }
51
52 public static void main(String[] args) {
53 Sequence s = new Sequence(10);
54 for (int i = 0; i < 10; i++) {
55 s.add(Integer.toString(i));
56 }
57 // Selector selector = s.selector();
58 // equals to
59 // 私有内部类在外部类可见
60 Sequence.SequenceSelector selector = (SequenceSelector) s.selector();
61 while (!selector.end()) {
62 System.out.printf("%s, ", selector.current());
63 selector.next();
64 }
65 }
66}
这是一个简单的“迭代器”的例子,这个说明的是,在内部类里,无需任何说明,即可访问外围类的私有域。这是由于内部类在实例化时,必定捕获一个创建此内部类的外围类对象的引用(静态内部类除外),当在内部类访问外围类成员时,就使用那个引用访问。
当使用private
来修饰内部类时,情况有一些特殊。结论是当使用private
修饰内部类时,内部类仅在外围类作用域中可用,在其他作用域内不可用,我们将上面的例子试图作如下修改:
1interface Selector{
2 boolean end();
3 Object current();
4 void next();
5}
6
7class SequenceL {
8 private Object[] items;
9 private int next = 0;
10
11 public SequenceL(int size) {
12 items = new Object[size];
13 }
14 public void add(Object o){
15 if (next < items.length){
16 items[next++] = o;
17 }
18 }
19 // private inner class
20 private class SequenceSelector implements Selector{
21 private int i = 0;
22
23 @Override
24 public boolean end() { return i == items.length; }
25
26 @Override
27 public Object current() { return items[i]; }
28
29 @Override
30 public void next() { if (i < items.length) i++; }
31 }
32
33 public SequenceSelector selector(){ return new SequenceSelector(); }
34
35
36}
37
38public class PSequence {
39 public static void main(String[] args) {
40 SequenceL s = new SequenceL(10);
41 for (int i = 0; i <10 ; i++) {
42 s.add(Integer.toString(i));
43 }
44 Selector selector = s.selector();
45 // 私有内部类在此处不可见
46 while (!selector.end()){
47 System.out.printf("%s, ", selector.current());
48 selector.next();
49 }
50 }
51}
可以看到,当试图在另一个类中访问内部类时,编译器会给出错误信息——不能访问私有内部类,它被隐藏了。
这是一种保护机制,除了内部类的外部类之外,任何人无法访问内部类,这样可以将内部类的实现(甚至其他不属于接口的实现)隐藏起来,这给Java编译器提供了生成高效代码的机会。
.this和.new #
在拥有外部类的引用之前,是不能创建内部类对象的,因为内部类总是和外部类建立着联系
当然,这个规则不适用于静态内部类(嵌套类)
如果需要在内部类中生成对外部类对象的引用,就需要用到.this
。
如果创建一个内部类对象,可以使用.new
。
正如前面提到的,此例中的内部类不能使用private
修饰。
1public class ThisNew {
2
3 public static void main(String[] args) {
4 DotThis d = new DotThis();
5 DotThis.Inner ti = d.inner();
6 ti.outer().f();
7
8 DotNew n = new DotNew();
9 // 使用 .new 获取内部类引用
10 DotNew.Inner ni = n.new Inner();
11 ni.f();
12 }
13}
14
15class DotThis {
16 void f() {
17 System.out.println("DotThis.f()");
18 }
19
20 class Inner {
21 // 在内部类中生成外部类对象引用
22 public DotThis outer() {
23 return DotThis.this;
24 }
25 }
26
27 Inner inner() {
28 return new Inner();
29 }
30}
31
32class DotNew {
33 class Inner {
34 void f() {
35 System.out.println("DotNew.Inner.f()");
36 }
37 }
38}
局部内部类 #
之前提到的示例中,内部类都是一个“单独的作用域”,那些内部类看起来都很容易理解,直观上都是把一个普通的Java类“放置”在另一个Java类内部
然而 ,内部类可以定义在一个方法里甚至任意的作用域内。
方法中的内部类 #
1interface Dest {
2 String showDest();
3}
4
5public class Flight3 {
6 private final int C = 100;
7
8 public Dest dest(String t) {
9 /** 局部内部类 */
10 class PDest implements Dest {
11 private String to;
12 private int y;
13
14 private PDest(String dest) {
15 this.to = dest;
16 // 使用.this访问外围类域
17 this.y = Flight3.this.C;
18 }
19
20 @Override
21 public String showDest() {
22 // not allowed!
23 // t = t.concat("xxx");
24 System.out.println(y);
25 return to;
26 }
27 }
28 return new PDest(t);
29 }
30
31 public static void main(String[] args) {
32 Flight3 f = new Flight3();
33 Dest d = f.dest("Macao");
34 System.out.println(d.showDest());
35 }
36}
37
38/*
39 * 100
40 * Macao
41 */// :~
内部类PDest在dest()
方法体内,只能在dest()
中能够访问,其他地方无法访问,引出几个内涵:
- PDest类使用权限修饰符没有意义,编译器也警告你不能使用任何修饰符
- PDest类的构造器是私有的,实际上由于作用域的限制,其访问权限无论是私有还是公有意义不大
任意域中的内部类 #
参考如下例子
1public class Flight4 {
2 private void flight(boolean fly){
3 if (fly) {
4 // inner class scope
5 class InternalFlight {
6 private String to;
7 InternalFlight(String to) { this.to = to; }
8 String showDest(){return to;}
9 }
10 // instance can only initialized here(in scope)
11 InternalFlight f = new InternalFlight("TaiPei");
12 System.out.println(f.showDest());
13 }
14 // illegal access! out of scope
15 // InternalFlight f = new InternalFlight("TaiPei");
16 }
17 public static void main(String[] args) {
18 Flight4 f = new Flight4();
19 f.flight(true);
20 }
21}
22/*
23TaiPei
24*///:~
内部类InternalFlight
定义在if
语句的作用域内,无法在if
语句的作用域之外创建对内部类的引用。
同样地,定义在语句作用域的内部类也可以继承自接口,参考下例:
1class FlightShip {
2 Dest flight(boolean fly) {
3
4 if (fly) {
5 // inner class scope
6 class InternalFlight implements Dest {
7 private String to;
8
9 InternalFlight(String to) {
10 this.to = to;
11 }
12
13 @Override
14 public String showDest() {
15 return to;
16 }
17 }
18 InternalFlight f = new InternalFlight("TaiPei");
19 System.out.println(f);
20 System.out.println(f.showDest());
21 return f;
22 }
23 return null;
24 // illegal access!
25 // InternalFlight f = new InternalFlight("TaiPei");
26 }
27}
28
29public class Flight4 {
30 public static void main(String[] args) {
31 FlightShip f = new FlightShip();
32 Dest dest = f.flight(true);
33 System.out.println(dest.showDest());
34 }
35}
36
37/*
38 * oo.innerclass.FlightShip$1InternalFlight@8bcc55f
39 * TaiPei
40 * TaiPei
41 */
上例中,定义在if语句块的内部类继承了Dest接口,并且包含内部类的方法返回了一个Dest引用(实际上是内部类对象引用的向上转型)。
它像不像一个工厂方法?
不像2025-05-08。
匿名内部类 #
不负责任地说,匿名内部类应该是实际应用中使用最多的内部类了。
实现接口 #
当你需要“创建一个接口的匿名类的对象”时,通过new
表达式返回的引用被自动向上转型为接口的引用。
参考下例:
1public class Flight5 {
2 public static void main(String[] args) {
3 Flight5 f5 = new Flight5();
4 AirInfo airInfo = f5.airInfo("BY2020", "HK", 3.5);
5 AnonFlight af = new AnonFlight();
6 Dest hk = af.flight(airInfo);
7 System.out.println(hk.showDest());
8 }
9
10 AirInfo airInfo(String no, String dest, Double cost){
11 return new AirInfo(no, dest,cost);
12 }
13}
14
15class AnonFlight {
16 Dest flight(AirInfo info) {
17 return new Dest() {
18 private String to = info.getDest();
19
20 @Override
21 public String showDest() {
22 // not allowed!
23 // info = new AirInfo("JJ","JJ",1.0);
24 return to;
25 }
26 };
27 }
28}
29
30class AirInfo{
31 private String no;
32 private String dest;
33 private double timeCost;
34
35 public AirInfo(String no, String dest, double timeCost) {
36 this.no = no;
37 this.dest = dest;
38 this.timeCost = timeCost;
39 }
40
41 public String getDest() {
42 return dest;
43 }
44}
匿名内部类常见的语法格式为:
1new SuperType(construction parameters){
2 inner class method and data
3}
上例中,我们传递“构造器参数”,实际上它不是构造器参数,只是用于内部类的字段初始化。
继承超类 #
匿名内部类还可以继承自某个普通类,此种情况下,内部类还可能有一些特殊的行为——调用构造器实现实例初始化。
实际上匿名内部类没有名字,也不可能有构造器。
1public class Flight6 {
2 public static void main(String[] args) {
3 Pistol p = new Pistol();
4 Wrap wrap = p.wrap(3);
5 System.out.println(wrap.value());
6
7 String[] strings = { "ali", "google", "amazon" };
8
9 // {{}} initialization
10 List<String> list = new ArrayList<String>() {
11 {
12 add("ali");
13 add("google");
14 add("amazon");
15 }
16 };
17 }
18}
19
20class Pistol {
21 Wrap wrap(int x) {
22 return new Wrap(x) {
23 int v;
24 // 构造代码块
25 {
26 System.out.println("extended initialized");
27 v = super.value() * 3;
28 }
29
30 @Override
31 int value() {
32 return v;
33 }
34 };
35 }
36}
37
38class Wrap {
39 private int i;
40
41 public Wrap(int i) {
42 this.i = i;
43 System.out.println("base constructor");
44 }
45
46 int value() {
47 return i;
48 }
49}
50
51/*
52 * base constructor
53 * extended initialized
54 * 9
55 */// :~
上例中,匿名内部类new Wrap(x)
中由wrap(int x)
传递来的参数x
作为了基类的构造器参数,在构造内部类的时候首先调用了基类的构造器,这是可以预想的结果。同时,在匿名内部类中使用了{}
语句来模拟匿名内部类的构造器行为——初始化字段信息, super.value()
的返回值也说明了基类已经在构造内部类之前就已经实例化成功了。
双花括号语法:{{...}}
其实上例给了一个启示:在内部类中使用
{}
进行了字段初始化,那么{{}}
是否可以用来实例初字段始化呢?答案是肯定的,比如在初始化一个数组列表时1List<String> list = new ArrayList<String>(){ 2 { 3 add("ali"); 4 add("google"); 5 add("amazon"); 6 } 7 };
这样有一个便利,当一个对象只需要使用一次的时候,可以使用匿名数组列表
再论工厂方法 #
在讨论接口的过程中,讨论了利用接口使代码与实现分离的 工厂模式,下例中使用匿名内部类优化代码时,会发现代码变得更加优雅。
下例展示了如何使用静态工厂方法获取实例:
1public class SimpleFactory2 {
2
3 public static void main(String[] args) {
4 NameService.getFactory.getService().service_a();
5 NameService.getFactory.getService().service_b();
6 AgeService.getFactory.getService().service_a();
7 AgeService.getFactory.getService().service_b();
8 }
9}
10
11interface Service {
12 void service_a();
13
14 void service_b();
15}
16
17interface ServiceFactory {
18 Service getService();
19}
20
21class NameService implements Service {
22 private NameService() {}
23
24 @Override
25 public void service_a() {System.out.println("NameService.service_a()");}
26
27 @Override
28 public void service_b() {System.out.println("NameService.service_b()");}
29
30 public static ServiceFactory getFactory = new ServiceFactory() {
31 @Override
32 public Service getService() {
33 return new NameService();
34 }
35 };
36 // same as lambda expression below
37 // public static ServiceFactory factory = () -> new NameService();
38}
39
40
41class AgeService implements Service {
42 private AgeService() { }
43
44 @Override
45 public void service_a() {System.out.println("AgeService.service_a()");}
46
47 @Override
48 public void service_b() {System.out.println("AgeService.service_b()");}
49
50 public static ServiceFactory getFactory = new ServiceFactory() {
51 @Override
52 public Service getService() {
53 return new AgeService();
54 }
55 };
56 // same as lambda expression below
57 // public static ServiceFactory factory = () -> new AgeService();
58}
- 服务类的构造器私有,使得无法从外部实例化构造器
- 静态字段获取工厂,实际上也只能使用静态字段
- 匿名内部类可以等价转换为lambda表达式
与lambda表达式 #
Java SE 8引入lambda表达式之后,匿名内部类与lambda表达式的关系变得亲密起来,匿名内部类可以等价转换为lambda表达式,但这不是绝对的。
lambda表达式的本质是一个函数,在一般来讲,lambda表达式“实例化接口”时,该接口一般为 函数式接口,只需覆盖一个抽象方法,这是lambda能做的全部。
而匿名内部类则要复杂的多,它是一个完整的类,除了没有构造器之外,其可以有字段利用参数进行初始化,其可以有{}
statement实现类实例化操作等等,这种形式的匿名内部类,是无法等价为lambda表达式的。
考虑lambda表达式体中 自由变量的几个约束,除了变量命名规则之外 ,其他规则同样在匿名内部类中适用,一些较老的资料(Java SE 7或之前)甚至常有这样的描述:
Java编译器要求传入内部类(包括lambda表达式)的的参数必须显示使用
final
修饰
就像这样:
1class AnonFlight {
2 Dest flight(final int dest) {
3 return new Dest() {
4 private int to = dest;
5 @Override
6 public String showDest() {
7 // dest--; // not allowed!
8 return to;}
9 };
10 }
11}
事实上,之前的示例中我们从来没有将内部类的参数引用声明为final
,不过,尽管可以不用声明为final
,约束依旧是存在的,你不能在内部类中修改参数(的引用)。
编译器给出的信息像这样:
Variable 'variable' is accessed from within inner class, needs to be final or effectively final
值得一提的是,上述约束对于局部内部类的参数引用同样适用
参考如下局部内部类的示例:
1public Dest dest(String t) {
2 class PDest implements Dest {
3 private String to;
4 private PDest(String dest) {this.to = dest;}
5 @Override
6 public String showDest() {
7 // t = t.concat("xxx"); // not allowed!
8 return to;
9 }
10 }
11 return new PDest(t);
12 }
静态内部类 #
非静态内部类编译的时候,会提供一个对外部类的引用,这是在内部类中使用.this
的前提。
如果不需要这种内部类和外部类的联系,可以将内部类声明为static
,这通常称为静态内部类或嵌套类。
如果使用静态内部类,那么意味着:
不需要外围类即可创建静态内部类对象
静态内部类无法访问外围类的非静态对象
静态内部类可以有
static
字段和数据普通内部类也能有静态字段,但是必须配合
final
声明为静态常量。原因很简单,Java希望静态字段只有一个实例,但是对每个外围类对象,会分别有一个内部类实例,如果这个字段不是final
,那它可能不是唯一的普通内部类不能有
static
方法(静态方法在类加载时初始化,而普通内部类依赖外部类实例才能创建)静态内部类可以嵌套
静态内部类内部类普通内部类也可以嵌套,这种嵌套关系变得愈发复杂
1public class Flight7 {
2 public static void main(String[] args) {
3 // out of scope! private inner class must upcast
4 // StaticFlight.InnerComp comp = StaticFlight.comp();
5 Comp comp = StaticFlight.comp();
6 StaticFlight.InnerDest dest = StaticFlight.dest("GZ");
7 // cannot access!
8 // StaticFlight.InnerComp.g();
9 System.out.println("StaticFlight.InnerDest.x = " + StaticFlight.InnerDest.x);
10 System.out.println(comp.showComp());
11 System.out.println(dest.showDest());
12 StaticFlight.InnerDest.AnotherLevel.f();
13 StaticFlight.InnerDest.AnotherLevel l = StaticFlight.InnerDest.anotherLevel();
14 System.out.println("StaticFlight.InnerDest.AnotherLevel.p(): " + l.p());
15 }
16}
17
18interface Dest {
19 String showDest();
20}
21
22interface Comp {
23 String showComp();
24}
25
26class StaticFlight {
27
28 private int constant = 10;
29 private static int constant_b = 10;
30
31 private static class InnerComp implements Comp {
32 private String comp = "AIR CHINA";
33
34 // cannot access non-static filed of outer class
35 // private int a = constant;
36 @Override
37 public String showComp() {
38 return comp;
39 }
40
41 static void g() {
42 System.out.println("g()");
43 }
44 }
45
46 static class InnerDest implements Dest {
47 private String to;
48
49 private InnerDest(String to) {
50 this.to = to;
51 }
52
53 @Override
54 public String showDest() {
55 return to;
56 }
57
58 // static elements
59 static int x = constant_b;
60
61 static class AnotherLevel {
62 static void f() {
63 System.out.println("StaticFlight.InnerDest.AnotherLevel.x = " + y);
64 }
65
66 int p() {
67 return ++x;
68 }
69
70 static int y = constant_b;
71 // same as
72 // static int y = x
73 }
74
75 static AnotherLevel anotherLevel() {
76 return new AnotherLevel();
77 }
78
79 }
80
81 static InnerComp comp() {
82 return new InnerComp();
83 }
84
85 static InnerDest dest(String s) {
86 return new InnerDest(s);
87 }
88}
89
90/*
91 * StaticFlight.InnerDest.x = 10
92 * AIR CHINA
93 * GZ
94 * StaticFlight.InnerDest.AnotherLevel.x = 10
95 * StaticFlight.InnerDest.AnotherLevel.p(): 1
96 */
上面的实例展示了上述静态内部类的性质。
外围类的comp()
和dest()
方法被声明为静态的(非必须),说明不需要外围类实例即可创建内部类实例,在main
方法里也是这么做的,而这是普通内部类无法完成的,如果试图这样做,会得到错误消息。
'xx.xx.x.outerClass.this' cannot be referenced from a static context
外围类定义了非静态字段constant
和静态字段constan_b
,在静态内部类中无法访问constant
,却可以访问constant_b
。
静态内部类可以嵌套,嵌套的内部类可以访问嵌入其的所有外围类的。
静态内部类除了static
数据和域存在特殊性之外,其他的使用和普通内部类无异。
接口中的内部类 #
接口中的任何类都是public
和static
的,因此将静态内部类置于接口中并不违反接口的规则。
甚至可以使接口中的内部类实现自其外围接口,参考下例:
1public interface InnerClassInterface {
2
3 void m();
4
5 /**
6 * 此类默认为静态内部类
7 */
8 class Test implements InnerClassInterface {
9
10 @Override
11 public void m() {
12 System.out.println("man!");
13 }
14 }
15
16 /**
17 * 接口中可以包含静态方法
18 *
19 * @return InnerClassInterface
20 */
21 static Test test() {
22 return new Test();
23 }
24
25 class Main {
26 public static void main(String[] args) {
27 Test test = InnerClassInterface.test();
28 test.m();
29 }
30 }
31}