第6章 接口与内部类
6.1 接口
一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。
在接口声明中,方法自动public,可以不写修饰符。在实现接口时必须把方法声明为public。
一个接口中可以包含多个方法,还可以定义常量,自动设置public static final
声明在接口中的内部类自动成为static和public类。
接口中不能含有实例域,也不能在接口中实现方法。提供实例域和方法实现的任务应该由实现接口的那个类来完成。
可以将接口看成是没有实例域的抽象类。
在调用方法时编译器会检查这个方法是否存在,如果是实现了接口的对象的数组,接口中提供了该方法,那么就可以确定该方法一定存在。每个实现了接口的类都必须提供接口中定义的所有方法。
为了让类实现一个接口,需要1:将类声明为实现 implements 给定的接口 2:对接口中的所有方法进行定义。
public interface Comparable { int compareTo(Object other); } class Employee implements Comparable { ... public int compareTo(Object other) { Employee e=(Employee)other; return Double.compare(salary,e.salary); } }
6.1.1 接口的特性
l 接口不是类,不能使用new运算符实例化一个接口但是可以声明接口变量,同时接口变量必须引用实现了接口的类对象。
x = new Comparable(...);//Error Comparable x ;// ok x = new Employee(...);
l 使用instanceof检查一个对象是否实现了某个特定的接口
if( anObject instanceof Comparable){...}
l 如同可以继承类,也可以扩展接口,扩展接口不用像实现接口那样实现所有方法
有些接口只定义了常量没有定义方法,任何实现了接口的类都自动继承了这些常量,并且可以直接饮用而不需像静态域那样类名.域名 的形式
public interface Moveable { void move(double x , double y ); } public interface Powered extends Moveable { double milesPerGallon(); double SPEEDLIMIT = 95; // public static final }
l 尽管每个类只能拥有一个超类,但可以实现多个接口。使用逗号将实现的各个接口分隔开。
class Employee implements Cloneable,Comparable
6.1.2 接口与抽象类
抽象类也可以表示通用属性,但存在一个问题:每个类只能扩展于(继承)一个类。接口却可以实现多个。
6.1.3 标记接口
Cloneable接口是Java提供的几个标记接口之一,标记接口没有方法,使用它的唯一目的是可以用instanceof进行类型检查。
自己编写程序时不要使用这种技术。
6.2 对象克隆
拷贝一个变量时,原始变量与拷贝变量引用同一个对象,改变一个变量所引用的对象会对另一个变量产生影响。
clone方法是Object 类的一个protected方法,在用户编写的代码中不能直接调用它。Object类对具体的类对象一无所知,只能将各个域进行对应的拷贝。对于基本类型或数值拷贝没有问题,但如果对象包含了子对象的引用,拷贝的结果还是两个域引用同一个子对象。
所有默认的克隆操作是浅拷贝,并没有克隆包含在对象中的内部对象。
必须重新定义clone方法,以便实现克隆子对象的深拷贝。对每个类都要做出下列判断:
1:默认的clone方法能否满足要求
2:默认的clone方法是否能够通过调用可变子对象的clone得到修补。
3:是否不应该使用clone
如果要使用clone,必须:
1:实现Cloneable接口
2:使用public访问修饰符重新定义clone方法,并声明异常CloneNotSupportedException
即使clone的默认实现能满足要求,也应该实现Cloneable接口,将clone定义为public,然后调用super.clone();
Cloneable接口与接口的正常使用没有任何关系。它并没有制定clone方法。clone方法是从Object类继承而来。接口在这里作为一个标记,表明类设计者制定要进行克隆处理。如果一个对象需要克隆而没有实现Cloneable接口,就会产生一个已检查异常。
class Employee implements Cloneable { ... public Employee clone() throws CloneNotSupportedException { Employee cloned=(Employee)super.clone();//Object.clone() cloned.hireDay=(Date)hireDay.clone();//克隆子对象 return cloned; } }
关于是否应该事先clone方法,如果客户需要深拷贝就应该实现它。克隆的应用也不是很普遍,在标准类库中只有不到5%的类实现了clone。
所有的数组类型都包含一个clone方法,而且被设为了public,可以利用这个方法创建一个包含所有数组元素拷贝的一个新数组。
6.3 接口与回调
回调可以指出某个特定事件发生时应该采取的动作。
以构造定时器为例:
在很多程序设计语言中,可以提供一个函数名,定时器周期性的调用它。但Java标准类库中的类采用的是面向对象方法。将某个类的对象传递给定时器,然后定时器,定时器调用这个对象的方法。由于对象可以携带一些附加的信息,所有传递一个对象比传递一个函数灵活的多。
public interface ActionListener { void actionPerformed(ActionEvent event); } class TimePrinter implenets ActionListener { public void actionPerformed(ActionEvent event) { //do something small } } //构造这个类的对象传递给Timer构造器 ActionListener listener = new TimePrinter(); Timer t = new Timer(10000,listener);
6.4 内部类
为什么需要使用内部类:
1:内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据
2:内部类可以对同一个包中的其他类隐藏起来
3:当想要定义一个回调函数而不想写大量代码时,使用匿名内部类比较便捷
6.4.1 使用内部类访问对象状态
内部类的对象总有一个隐式引用,指向了创建它的外部类对象。将外围类的对象的引用称为outer。
outer不是Java关键字,外围类的引用在构造器中设置,编译器修改了所有内部类的构造器,添加了一个外围类的引用参数。
只有内部类可以是私有类。常规类只可以具有包可见性,或公有可见性。
6.4.2 内部类的特殊语法规则
使用外围类引用:OuterClass.this
if(TalkingCLock.this.beep) ...
编写内部类对象的构造器:outerObject.new InnerClass(contruction parameters)
ActionListener listener = this.new TimerPrinter();
在外围类的作用域之外引用内部类:
OuterClass.InnerClass
6.4.3 内部类安全性
如果内部类访问了私有数据域,就有可能通过附加在外围类梭子包中的其他类访问它们,但做这些事情需要高超的技巧和极大的决心。不可能无意之中就获得对类的访问权限,必须可以构建或修改类文件才有可能达到这个目的。
6.4.4 局部内部类
可以在一个方法中定义局部类,并且不能用public或private访问说明符进行声明,它的作用域被限定在声明这个局部类的块中。
局部类可以对外部世界完全隐藏起来,即使方法所在类中的其他代码也不能访问。除了定义它的方法外,没有任何方法知道它的存在。
局部类的另一个优势:不仅可以访问包含它们的外部类,还可以访问局部变量,但那些局部变量必须被声明为final
public void start(int interval,final boolean beep) { class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { if(beep)//局部类访问局部变量 Toolkit.getDefaultToolkit().beep(); } } ActionListener listener = new TimePrinter(); Time t = new Timer(interval,listener); t.start(); }
6.4.5 匿名内部类
假如只创建一个这个(内部)类的对象,就不必命名了,这种类被称为匿名内部类
匿名类不能有构造器,需要将参数传递给超类构造器;内部类实现接口时不能有任何构造参数。
构造器与类名同名,没有名字的类无法设置构造器。
SuperType可以是接口,内部类就要实现这个接口;也可以是一个类,内部类就要扩展它。
new SuperType(construction parameters) { inner class methods and data } new InterfaceType() { methods and data } public void start(int interval, final boolean beep) { //创建一个实现AL接口的类的新对象 ActionListener listener = new ActionListener() { ... } ... }
6.4.6 静态内部类
有时候使用内部类仅为了把一个类隐藏在另一个类的内部,并不需要内部类引用外围类对象。为此可以将内部类声明为static,以便取消产生的引用。
只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。如果在静态方法中构造内部类,则必须是静态内部类。
声明在接口中的内部类自动称为static和public类。
6.5 代理
在编译时无法确定需要实现哪个接口时,利用代理可以在运行时创建一个实现了一组给定接口的新类。
例如假设有一个表示接口的Class对象,它的确切类型在编译时无法知道。想要构造一个实现这些接口的类,就需要使用newInstance方法或反射找出这个类的构造器。但是不能实例化一个接口,需要在程序处于运行状态时定义一个新类。
为了解决上述问题,有些程序将会生成代码,然后将这些代码放在一个文件中,调用编译器,再加载结果类文件,很明显这样做的速度比较慢,并且需要将编译器与程序放在一起。
代理类则可以在运行时创建全新的类,这样代理类能够实现指定的接口,尤其是具有下列方法:
1:指定接口所需要的全部方法
2:Object类中的全部方法
但是不能在运行时定义这些方法的新代码。而要提供一个调用处理器,它是实现了InvocationHandler接口的类对象,在这个接口中只有一个方法
Object invoke(Object proxy, Method method ,Object[] args)
无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。调用处理器必须给出调用处理的方式。
要创建一个代理对象,需要使用Proxy类的newProxyInstance方法,该方法有三个参数:
1:类加载器。作为Java安全模型的一部分,对于系统类和从其他类,可以使用不同的类加载器,目前使用null表示使用默认的类加载器
2:Class对象数组。每个元素都是需要实现的接口
3:调用处理器。
以及需要解决两个问题:
1:如何定义一个处理器
2:能够使用结果代理对象做些什么
6.6 待补充
内部类、代理 的示例代码