内部类

内部类

将一个类定义在另一个类的内部,这就是内部类。

定义言简意赅 ,内涵丰富多彩。

 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()中能够访问,其他地方无法访问,引出几个内涵:

  1. PDest类使用权限修饰符没有意义,编译器也警告你不能使用任何修饰符
  2. PDest类的构造器是私有的,实际上由于作用域的限制,其访问权限无论是私有还是公有意义不大

任意域中的内部类 #

参考如下例子

更新于 2025-05-08

开发中还没见过这样的代码呢~

 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数据和域存在特殊性之外,其他的使用和普通内部类无异。

接口中的内部类 #

接口中的任何类都是publicstatic的,因此将静态内部类置于接口中并不违反接口的规则。

甚至可以使接口中的内部类实现自其外围接口,参考下例:

 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}