Java编程中的内部类

开发 后端
本文节选自《JAVA编程思想》中关于Java内部类的段落。内部类是在其他类内定义的类。虽然看上去象某种代码隐藏机制,但可以实现更多功能。

内部类:在其它类内定义的类,不同于组合。虽然看上去象某种代码隐藏机制,但可以实现更多功能 -了解包含它的类并可与之交换数据,而且内部类的代码可以更优雅、清晰。

内部类定义:包含在其它类内。使用与非内部类没有太大区别。

典型用法:outer class通过方法返回inner class的引用。

区别之一:内部类名嵌套在外部类(outer class)内,在Out class的non-static方法之外用OuterClassName.InnerClassName的形式定义内部类对象。

注意,非static内部类只能在Out class的non-static方法中生成对象;在其它类中,也必须使用Out class的外部类对象实例。这就保证了下面所说的链接问题。

区别之二:内部类可以为private和protected。

Inner Class只是一种名称隐藏(name-hiding)和组织代码方式?NO。

内部类对象有一个到创建它的外部类对象的链接(link to the enclosing object that made it),因而可以直接的、没有任何限制地访问该外部类对象的成员,而且内部类可以访问outer class的所有成员(包括private)(C++的嵌套类没有这个特性);而outer class访问inner class的成员,必须创建Inner class的对象,可以访问任何成员(包括private)。

内部类对象中隐式包含了一个外部类对象的引用。内部类对象构建需要outer class对象的引用,如果没有,编译报错(非静态inner class)。

.this和.new:前者用来返回Outer class引用,编译期可知道和检查正确类型,无运行时开销;后者用来由outer class对象创建其内部类的对象,OutClassObject.new InnerClassName ()(注意,不能用outClassObj.new OutClassName.InnerClassName())。

嵌套类(nested class):static inner class,其对象创建不需要outer class对象引用,也可在static方法中创建。

内部类与upcasting

类实现了接口(interface),其它方法就可以用该interface作为参数,而不一定必须用该类(包括类对象定义)(类似继承)。可以利用upcasting->interface。

upcasting内部类->基类或者接口(尤其是后者),使内部类有了用武之地。实现接口内部类可以完全不可见、不可用(通过private或protected),所获得的只是基类或接口的引用(通过private,无法进行downcasting,protected,同一个包内,或者继承类可以进行downcasting),方便隐藏实现细节。

接口成员自动为public

private内部类可以阻止任何依赖于类型的代码,进行所有实现细节的隐藏。而且,扩展接口也没有任何意义,因为无法访问pubic接口之外的方法,这可以使JAVA产生更有效率的代码 。

inner class可以在任意作用域内定义(如方法内)。

两个理由:1. 实现一个接口

2. 需要一个不公开的类辅助解决复杂的问题

inner class形式

1. 方法内的内部类; 2. 方法中的一个作用域内的内部类; 3. 实现接口的匿名内部类; 4. 继承的匿名内部类(基类含有参的构造器); 4. 进行成员初始化的匿名内部类; 5. 使用实例初始化块进行构造的匿名内部类(匿名类没有构造器)。

局部内部类(local inner class) :在方法内或方法的一个作用域中定义的内部类。局部内部类在域外不可见并不代表其对象也不可用。条件域内定义的内部类不代表它是条件创建的。

匿名内部类(anonymous inner class) :new T(){...}; {...}为匿名内部类的定义,";"不可少(;只是该语句的结束,而不是用来表示匿名内部类的结束,所以没有什么特殊的地方),创建一个继承自T的匿名类的对象,得到的引用可自动upcast to T。是前面定义内部类的一种简写,只是该类没有名称。

前面是基类构造器为默认构造器的情况,当基类构造器有参数时:new T(args){...};此时会调用基类相应构造器。

匿名内部类初始化 :当需要用到外部定义类的对象时,传递的引用参数必须为final,否则编译报错;匿名类不能有命名的构造器(当然不能,类本身就没有名字),可以通过实例初始化(instance initialization)来完成构造器的功能。由于实例初始化不能重载(不代表只能有一个Instance initialization clause),所以匿名内部类只能有一个构造器。

匿名内部类只能在继承类和实现接口中2选一,且只能实现一个接口。

prefer classes to interfaces. (宁愿选择类,而不是接口?)

嵌套类(nested class):static inner class。有点类似C++嵌套类的概念,但Java的嵌套类可以访问outer class的所有成员(包括private,当然只能通过外部类对象访问non-static成员)。

1. 不需要通过outer class对象来创建嵌套类对象(.new不可用?);

2. 不能通过嵌套类对象访问non-static outer class对象(意思是像非嵌套类那样直接访问);

3. 嵌套类对象中不包含outer class对象引用(.this不可用)。

4. 非嵌套内部类不能有static成员、方法和嵌套类(fields、methods级别必须与class本身一致,non-static不能含有static,non-static、static内可以含有non-static)。

嵌套类可以位于接口内部,不违反接口的规则(不能定义接口实例?),只代表把嵌套类位于接口的命名空间下,位于接口内部的类自动为public static(public嵌套类),而且嵌套类本身就可以实现该接口,好处在于可以在嵌套类内编写该接口所有实现中都要用到的代码。

嵌套类的另一个用途:编写测试代码。 为每个类编写main函数增加代码长度,可以把main放在嵌套类内,要测试该类运行该嵌套类即可;而在发布的时候只要在打包前简单的删除该嵌套类的.class文件即可。

多重嵌套的类(non-static和static)可以没有限制的访问任何外层类的所有对象。

为什么用内部类?

不是总是直接和接口打交道,有时候需要用的是接口的实现。(可以实现多个接口,但不能继承多个类)

理由:每个内部类可以独立继承自一个实现,不受outer class是否已经继承另一实现的限制。从效果上来说,inner class提供了多继承(multiple-inheritance,继承自多个类)的能力,提供了另一种实现多个接口的方法(相比多继承,这个似乎没那么重要,因为多继承只能通过内部类来实现)。

额外特性:

1. 内部类可以有多个实例,每个实例可以拥有独立于outer class对象的不同信息;

2. 一个outer class可以有多个内部类,每个内部类可以以不同的方式实现同一个接口或者继承同一个类(参见习题22,两个内部类不同方式实现同一个接口,只有内部类才能完成这些);

3. 内部类实例创建时间并不受到外部类对象创建的限制;

4. 用内部类不会制造"is-a"关系的混乱,每个内部类都是个实体。

闭包(closure)和回调(callback)

闭包是一种可调用的对象,它记录了来自创建它的作用域的一些信息。

内部类是一种面向对象的闭包,不仅包含了外部类的信息,而且通过包含一个指向外部类对象的引用,可以操作所有成员,包括private。

回调,通过其它对象携带的信息,可以在稍后的某个时刻调用初始对象。

Java不支持指针类型,不能通过指针来实现回调。但内部类提供的闭包是种比较好的解决方案,更灵活,更安全(参见例callbacks)。

  1. private class Closure implements Incrementable {   
  2.     public void increment() {   
  3.     // Specify outer-class method, otherwise   
  4.     // you’d get an infinite recursion:   
  5.         Callee2.this.increment();   
  6.     }   
  7. }    

回调的价值在于灵活性,可以在运行时决定需要调用的方法。 GUI编程将体现得更明显。

内部类与控制框架(control frameworks)

一个应用程序框架(application framework)是指一个用来解决一个特定类型问题的类或类的集合。典型的应用方法是,继承其中一个或多个类,重写某些方法。重写方法的代码将通用解决方案特殊化,来解决特定问题。例如模板函数模式。 设计模式将不变的和变化的事情分开。

控制框架是用来响应事件的一类特殊的应用程序框架 。主要用来响应事件的系统称为事件驱动系统(event-driven system),如GUI。

内部类使得控制框架的创建和使用变得简单 。控制框架本身不包括要控制的事物的特定信息。这些信息在继承过程中,由算法的action()部分实现时提供。控制框架中变化的事情是各种事件对象的不同action,这通过创建不同event继承类来实现。(例event)

控制事件用abstract类代替接口?

内部类在控制框架中两个作用:

1. 用来表示解决问题所需的各种不同的action()。

2. 内部类可以直接访问外部类的所有成员,因而使得实现变得更灵活。

参见greenhouse(温室)的例子。

内部类的继承

内部类指向outer class object的引用必须初始化,而在它的继承类中并不存在要联接的缺省对象,必须使用特殊的语法明确指出这种关联。

继承自内部类的类构造器不能是默认构造器,要有个outer class的引用作为参数,而且必须加上enclosingClassReference.super();语句,编译才能通过。

内部类能override?继承outer class,像重写方法一样重写内部类并不起作用,此时两个内部类只是两个独立的实体。可以显式指定内部类的继承关系,然后通过复写base inner class的方法,来实现多态。 参见例BigEgg2.

  1. //: innerclasses/BigEgg2.java   
  2. // Proper inheritance of an inner class.   
  3. import static net.mindview.util.Print.*;   
  4. class Egg2 {   
  5.     protected class Yolk {   
  6.       public Yolk() { print("Egg2.Yolk()"); }   
  7.       public void f() { print("Egg2.Yolk.f()");}   
  8. }   
  9.     private Yolk y = new Yolk();   
  10.     public Egg2() { print("New Egg2()"); }   
  11.     public void insertYolk(Yolk yy) { y = yy; }   
  12.     public void g() { y.f(); }   
  13. }   
  14. public class BigEgg2 extends Egg2 {   
  15.     public class Yolk extends Egg2.Yolk {   
  16.         public Yolk() { print("BigEgg2.Yolk()"); }   
  17.         public void f() { print("BigEgg2.Yolk.f()"); }   
  18. }   
  19.     public BigEgg2() { insertYolk(new Yolk()); }   
  20.     public static void main(String[] args) {   
  21.         Egg2 e2 = new BigEgg2();   
  22.         e2.g();   
  23. }   
  24. /* Output:   
  25. Egg2.Yolk()   
  26. New Egg2()   
  27. Egg2.Yolk()   
  28. BigEgg2.Yolk()   
  29. BigEgg2.Yolk.f()   
  30. *///:~  
  31.  

局部内部类

局部内部类(local inner class)不能有访问限定符;有访问局部final变量和outer class所有类的权限;可以有命名的构造器;在方法外不能访问。

绝大部分情况下,可以用匿名类来替代局部内部类,除非:

1. 需要命名的构造器,或者需要重载构造器

2. 需要多个内部类的对象

此时就要用Local Inner class。

【编辑推荐】

  1. Java的局部内部类以及final类型的参数和变量
  2. 没有原生数据类型,Java会更好吗?
  3. 20个开发人员非常有用的Java功能代码
  4. 走进Java 7中的模块系统
  5. 2009年十大Java技术解决方案
责任编辑:yangsai 来源: 《JAVA编程思想》
相关推荐

2020-01-15 11:14:21

Java算法排序

2011-07-21 15:44:33

Java内部类

2020-12-14 10:23:23

Java内部类外部类

2010-02-05 15:32:33

Java内部类

2011-03-29 14:11:15

内部类

2023-10-19 13:24:00

Java工具

2023-03-06 07:53:36

JavaN种内部类

2015-12-08 09:05:41

Java内部类

2009-07-29 09:18:49

Java内部类

2019-12-23 14:32:38

Java内部类代码

2010-08-26 10:41:45

C#内部类

2020-09-21 07:00:42

Java内部类接口

2012-04-17 11:21:50

Java

2020-01-12 19:10:30

Java程序员数据

2009-08-26 18:00:07

C#内部类

2009-06-11 11:07:25

Java局部内部类Final类型

2020-10-29 08:31:15

Java同步回调编程语言

2011-11-23 10:59:18

Javafinal

2011-03-15 10:41:05

内部类

2009-07-22 16:13:40

iBATIS用法SqlMapTempl
点赞
收藏

51CTO技术栈公众号