6 接口与内部类
接口 interface
对象克隆
内部类 inner class
代理 proxy
6.1 接口
public interface Comparable<T>
{
int compareTo(T other);
}
Arrays.sort(Object[] a) 利用的是mergesort
接口也可以被扩展
public interface Moveable
{
void move(double x, double y);
}
public interface Powered extends Moveable
{
double milesPerGallon(); //public abstract
double SPEED_LIMIT = 95; //public static final
}
标准库中的SwingConstants接口只包含NORTH、SOUTH和HORIZOTAL等常量,任何实现此接口的类都自动继承了这些常量。
class Employee implements Cloneable, Comparable
6.2 对象克隆
Object的clone()是protected,默认浅拷贝。
实现Cloneable接口(空接口,标记用)
使用public访问修饰符重新定义clone方法
标记接口的唯一作用,可以使用instanceof进行类型检查
class Employ implements Cloneable
{
public Employ clone() throws CloneNotSupportedException
{
...
}
}
只要在clone中含有未实现Cloneable接口的对象,Object类的clone方法就会抛出一个CloneNotSupportException异常。
如果是final类,也可不抛异常,而是try-catch。
6.3 接口与回调
回调(call back)是一种常见的设计模式。可以指出某个特定事件发生时应该采取的动作。
public interface ActionListener
{
void actionPerformed(ActionEvent event);
}
class TimePrinter implements ActionListener{ }
new Timer(1000, new TimePrinter()).start();
//Timer构造器的第一个参数是发出通告的时间间隔,单位是毫秒;第二个是监听器对象,在时间到后执行actionPerformed();
javax.swing.JOptionPane
static void showMessageDialog(Component parent, Object message)
如果parent为null,则在中央显示
javax.swing.Timer
Timer(int interval, ActionListener listener)
void start()
void stop()
java.awt.Toolkit
void beep()
6.4 内部类
定义在另一个类中的类。
使用原因:
内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
内部类可以对同一个包中的其他类隐藏起来
当想定义一个回调函数时,使用匿名内部类比较便捷
内部类中添加了一个外围类引用的参数 OuterClass.this
在外部类外引用公开内部类OuterClass.InnerClass
内部类是一种编译器现象,与虚拟机无关。
编译器将内部类翻译成用$分隔外部类名与内部类名的常规类文件,虚拟机对此一无所知
OuterClass$InnerClass
编译器为了引用外围类,生成了一个附加的实例域final OuterClass this$0
外围类将添加静态方法 static boolean access$0(OuterClass),InnerClass将调用此方法来访问外部类的私有域。
局部内部类不能用public或private访问说明符进行声明
它的作用域被限定在声明这个局部类的块中
优势:对外部世界可以完全隐藏起来
可以访问外部类,和被声明为final的局部变量。
匿名内部类 anonymous inner class
InnerClass ic = new InnerClass(){ 类的实现 };
InnerClass可以是一个接口,类内实现接口方法
只创建类的一个对象
静态内部类
只是为了把一个类隐藏在另一个类的内部,并不需要内部类引用外围类对象
用static 进行声明
6.5 代理
在运行时创建一个实现了一组给定接口的新类。
这种功能在编译时无法确定需要实现哪个接口时才有必要使用。
应用场景:
一个接口,其确切类型在编译时无法知道。
需要在程序运行状态定义一个实现这些接口的新类。
方案一:
生成代码,将代码放置在一个文件中,调用编译器进行编译,然后再加载结果类文件。
方案二:
代理机制,在运行时创建全新的类。这样的代理类能够实现指定接口。
指定接口有如下方法:
指定接口所需的全部方法;
Object类中的全部方法:toString equals hashcode
方法:
1、定义调用处理器(invocation handler),包装基本类对象。
调用处理器是实现了InvocationHandler接口的类对象。在这个接口中只用一个方法:
Object invoke( Object proxy, Method method, Object[] args)
当调用代理对象的方法时,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数,调用处理器给出处理调用的方式。
2、创建代理对象。
使用Proxy类的newProxyInstance方法,有三个参数:
一个类加载器、一个class对象数组(每个元素都是需要实现的接口)、一个调用处理器
例:
public interface Run
{
void run();
}
public class Animal implements Run
{
@Override
public void run()
{
System.out.println("Animal is running");
}
}
public RunHandler implements InvocationHandler
{
private Object target;
public RunHandler(Object target)
{
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
{
System.out.println("Proxy is running");
return method.invoke(target, args);
}
}
Animal animal = new Animal(); //基本类对象
InvocationHandler handler = new RunHandler(animal); //封装基本类对象
//handler封装的是实现Run接口的类对象。
Object proxy = Proxy.newProxyInstance(Run.class.getClassLoader(), new Class[]{Run.class}, handler);
(Run)proxy.run(); //此时代理对象proxy可以调用Run接口的方法。
代理的特性:
代理类是在运行过程中创建的,一旦被创建,就变成常规类,与虚拟机中的任何其他类没有区别。
所有代理类都扩展于Proxy类。
一个代理类只有一个实例域——调用处理器,它定义在Proxy的超类中。
为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。
代理类覆盖了Object类中的方法toString equals hashCode,这些方法调用了调用处理器的invoke。
Object类的clone和getClass没有被重新定义。
没有定义代理类的名字,虚拟机中的Proxy类将生成一个以字符串$Proxy开头的类名。
对于特定类的加载器和预设的一组接口来说,只能有一个代理类。即,如果使用同一个类加载器和接口数组调用了两次newInstance方法的话,那么只能得到同一个类的两个对象。
可以用Class proxyClass = Proxy.getProxyClass(null, interfaces);
代理类一定是public和final。
可通过Proxy类中的isProxyClass方法检测一个特定的Class对象是否代表一个代理类。