Java 核心技术卷 I 学习记录九
- 六、接口、lambda表达式与内部类
- 4、内部类
- 1、使用内部类访问对象状态
- 2、内部类的特殊语法规则
- 3、内部类是否有用、必要和安全
- 4、局部内部类
- 5、由外部方法访问变量
- 6、匿名内部类
- 7、静态内部类
- 5、代理
- 1、何时使用代理
- 2、创建代理对象
- 3、代理类的特性
六、接口、lambda表达式与内部类
4、内部类
内部类(inner class)是定义在另一个类中的类。使用内部类的主要原因如下:
- 内部类方法可以访问该类定义所在的作用域中的数据, 包括私有的数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名 (anonymous)内部类比较便捷。
1、使用内部类访问对象状态
内部类的语法比较复杂。我们使用一个简单例子说明内部类的使用方式。
构造一个语音时钟时需要提供两个参数:发布通告的间隔和开关铃声的标志。
public class TalkingClock {private int interval:private boolean beep;public TalkingClock(int interval, boolean beep) {// automatically generated codethis.interval = interval;this.beep = beep;}public void start() { ActionListener listener = new TimePrinter();Timer t = new Timer(interval, listener);t.start();}public class TimePrinter implements ActionListener {// an inner classpublic void actionPerformed(ActionEvent event) {System.out.println("At the tone, the time is " + new Date());if (beep) Toolkit.getDefaultToolki10.beep();}}
}
内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
2、内部类的特殊语法规则
使用外围类引用的正规语法还要复杂一些。表达式OuterClass.this
,表示外围类引用。
// 编写TimerPrinter内部类的actionPerformed方法
public void actionPerformed(ActionEvent event) {...if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep();
}// 也可以采用下列语法格式更加明确地编写内部对象的构造器
outerObject.new InnerClass(construction parameters)
ActionListener listener = this.new TimePrinter();
3、内部类是否有用、必要和安全
内部类是一种编译器现象,与虚拟机无关。编译器将会把内部类翻译成用$(美元符号)分隔外部类名与内部类名的常规类文件,而虚拟机则对此一无所知。
如果内部类访问了私有数据域,就有可能通过附加在外围类所在包中的其他类访问它们,但做这些事情需要高超的技巧和极大的决心。程序员不可能无意之中就获得对类的访问权限,而必须刻意地构建或修改类文件才有可能达到这个目的。
4、局部内部类
局部类不能用public或private访问说明符进行声明。它的作用域被限定在声明这个局部
类的块中。局部类有一个优势,即对外部世界可以完全地隐藏起来。
5、由外部方法访问变量
与其他内部类相比较,局部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,那些局部变量必须事实上为final。这说明,它们一旦赋值就绝不会改变。
6、匿名内部类
将局部内部类的使用再深人一步。假如只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类(anonymous inner class)。
通常的语法格式为:
new SuperType(construction parameters) {inner class methods and data
}
由于构造器的名字必须与类名相同,而匿名类没有类名,所以,匿名类不能有构造器。取而代之的是,将构造器参数传递给超类(superclass)构造器。尤其是在内部类实现接口的时候,不能有任何构造参数。不仅如此,还要像下面这样提供一组括号:
new InterfaceType() {methods and data
}
7、静态内部类
有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用
外围类对象。为此,可以将内部类声明为static,以便取消产生的引用。
只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所冇内部类完全一样。
5、代理
代理(proxy)。利用代理可以在运行时创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。
1、何时使用代理
要想构造一个实现这些接口的类,就需要使用newlnstance方法或反射找出这个类的构造器。但是,不能实例化一个接口,需要在程序处于运行状态时定义一个新类。
为了解决这个问题,有些程序将会生成代码;将这些代码放置在一个文件中;调用编译器;然后再加载结果类文件。很自然,这样做的速度会比较慢,并且需要将编译器与程序放在一起。而代理机制则是一种更好的解决方案。代理类可以在运行时创建全新的类。这样的代理类能够实现指定的接口。尤其是,它具有下列方法:
- 指定接口所需要的全部方法。
- Object 类中的全部方法,例如, toString、 equals等。
不能在运行时定义这些方法的新代码。而是要提供一个调用处理器(invocation handler)。调用处理器是实现了InvocationHandler接口的类对象。在这个接口中只有一个方法:Object invoke(Object proxy, Method method, Object[] args)
无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。调用处理器必须给出处理调用的方式。
2、创建代理对象
要想创建一个代理对象,需要使用Proxy类的newProxylnstance方法。这个方法有三个
参数:
- 一个类加栽器(class loader)。作为Java安全模型的一部分,对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。目前,用null表示使用默认的类加载器。
- 一个Class对象数组,每个元素都是需要实现的接口。
- 一个调用处理器。
使用代理可能出于很多原因,例如:
- 路由对远程服务器的方法调用。
- 在程序运行期间,将用户接口事件与动作关联起来。
- 为调试, 跟踪方法调用。
3、代理类的特性
代理类是在程序运行过程中创建的,一旦创建就变成常规类,与虚拟机中的任何其他类没有区别。
所有的代理类都扩展于Proxy类。一个代理类只有一个实例域—调用处理器,它定义在Proxy的超类中。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。
所有的代理类都覆盖了Object类中的方法toString、equals和hashCode。如同所有的代理方法一样,这些方法仅仅调用了调用处理器的invoke。Object类中的其他方法(如clone和getClass)没有被重新定义。
对于特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次newProxylustance方法的话,那么只能够得到同一个类的两个象,也可以利用getProxyClass方法获得这个类:Class proxyClass = Proxy.getProxyClass(null, interfaces);
。
代理类一定是public和final。如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则,所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。
可以通过调用Proxy类中的isProxyClass方法检测一个特定的Class对象是否代表一个代理类。
java.Iang.reflect.InvocationHandler 1.3
- Object invoke(Object proxy, Method method, 0bject[] args):定义了代理对象调用方法时希望执行的动作。
java.Iang.reflect.Proxy 1.3
- static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces):返回实现指定接口的代理类。
- static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler):构造实现指定接口的代理类的一个新实例。所有方法会调用给定处理器对象的invoke方法。
- static boolean isProxyClass(Class<?> cl):如果cl是一个代理类则返回 true。