接口技术:主要用来描述类具有什么功能,而并不给出每个功能的具体实现。
一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。
在Java程序语言设计中,接口不是类, 而是对类的一组需求描述。这些类要遵从接口描述的统一格式进行定义。
对象克隆
当拷贝一个变量时,原始变量与拷贝变量引用同一个对象,如果创建一个的对象的新的copy,他的最初状态与original一样,但以后可以各自改变各自的状态,那么就需要使用clone方法。
Employee copy = original.clone();
copy.raiseSalary(10);//ok original unchanged.
不过,clone方法是Object类的一个protected方法。也就是说,在用户编写的代码中不能直接调用他。只有Employee类才能克隆Employee对象。这种限制有一定的道理。由于这个类对具体的类对象一无所知,所以只能将各个域进行对应的拷贝。如果对象中的所有数据域都属于数值或基本类型,这样拷贝域没有任何问题。但是如果在对象中包含了子对象的引用,拷贝的结果会使得两个域引用同一个子对象,因此原始对象与克隆对象共享这部分信息。
默认的克隆操作是浅拷贝,他并没有克隆包含在对象中的内部对象。
对此,如果原始对象与浅克隆对象共享的子对象是不可变的,将不会产生任何问题。也确实存在这种情形。例如,子对象属于像String类这样的不允许改变的类;
也有可能子对象在其生命周期内不会发生变化,既没有更改他们的方法,也没有创建对他引用的方法。
然而,更常见的情形是子对象可变,因此必须重新定义clone方法,以便实现克隆子对象的深拷贝。比如对象中的Date类,这就是一个可变的子对象。
对于每一个类都需要做出下列判断:
1)默认的clone方法是否满足需求。
2)默认的clone方法是否能够通过调用可变子对象的clone得到修补。
3)是否应该不使用clone。
选项3是默认的。如果选择1,2,类必须
1)实现Cloneable接口
2)使用public访问修饰符重新定义clone方法。
在这里Cloneable接口的出现与接口的正常使用没有任何关系,尤其是,他并没有指定clone方法,这个方法是从Object类继承而来的。接口在这里只是作为一个标记。表明类设计者知道要进行克隆处理。
Cloneable接口是Java提供的几个标记接口之一,通常使用接口的目的是为了确保类实现某个特定的方法或一组特定的方法,Comparable接口就是这样的一个例子。
浅拷贝
即使clone的默认实现(浅拷贝)能够满足需求,也应该实现Cloneable接口,将clone重新定义为public,并调用super.clone().
eg:
class Employee implements Cloneable
{
public Employee clone() throws CloneNotSupportedException
{
return (Employee) super.clone();
}
}
并没有在Object.clone提供的浅拷贝基础上增加任何新功能,只是将这个方法声明为public,为了实现深拷贝,必须克隆所有可变的实例域。
深拷贝
class Employee implements Cloneable
{
public Employee clone() throws CloneNotSupportedException
{
// call Object.clone()
Employee cloned = (Employee) super.clone();
// clone mutable fields
cloned.hireDay (Date) hireDay.clone();
return cloned;
}
}
只要在clone中含有没有实现Cloneable接口的对象,Object类的clone方法就会抛出一个CloneNotSupportedException异常,当然。Employee和Date类都实现了Cloneable接口,因此不会抛出异常。但是编译器并不知道这些情况。因此需要声明异常throws CloneNotSupportedException。
最后,在自定义的类中是否实现clone方法,取决于客户需求。
克隆的应用并不像人们想象的那样普遍,在标准类库中,只有不到5%的类实现了clone。
接口与回调
回调(callback)是一种常见的程序设计模式,在这种模式中,可以指出某个特定事件的发生时应该采取的动作。例如,可以指出在按下数鼠标或选择某个菜单项时应该采取什么行动。
内部类
内部类是定义在另一个类中的类。
使用内部类的原因:
1)内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。
2)内部类可以对同一个包中的其他类隐藏起来。
3)当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
代理
利用代理可以在运行时创建一个实现了一组给定接口的新类。
这种功能只要在编译时无法确定需要实现那个接口时才有必要使用。
假设有一个表示接口的Class对象(有可能只包含一个接口),他的确切类型在编译时无法知道。想要构造一个实现这些接口的类,就需要使用newInstance方法或反射找出这个类的构造器。但是不能实例化一个接口,需要在程序处于运行状态时定义一个新类。
为了解决这个问题,有些程序将会生成代码;将这些代码放置在一个文件中;调用编译器;然后再加载结果类文件。这样做速度很慢,并且需要将编译器与程序放在一起。而代理机制则是一种更好的解决方案。代理类可以在运行时创建全新的类。这样的代理类能够实现指定的接口。他具有下列方法:
1)制定接口所需要的全部方法。
2)Object类中的全部方法,例如,toString,equals等。
然而,不能在运行时定义这些方法的新代码。而是要提供一个调用处理器(invocation handler)调用处理器是实现了InvocationHandler接口的类对象,在这个接口中只有一个方法:
Object invoke(Object proxy, Method method ,Object [] args)
无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。调用处理器必须给出处理调用的方式。
要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。这个方法有三个参数:
1)一个类加载器。作为Java安全模型的一部分。对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。
2)一个Class对象数组,每个元素都是需要实现的接口。
3)一个调用处理器。
package com.cht.ProxyTest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
public class ProxyTest {
public static void main(String[] args) {
Object [] elements = new Object [1000];
//fill elements with proxies for the integers 1...1000
for (int i = 0; i < elements.length; i++) {
Integer value = i + 1;
InvocationHandler handler = new TraceHandler(value);
Object proxy =
Proxy.newProxyInstance(
null,
new Class[]{ Comparable.class },
handler);
elements[i] = proxy ;
}
//Construct a random integer
Integer key = new Random().nextInt(elements.length)+1;
//serch for the key
int result = Arrays.binarySearch(elements, key);
// print match if found
if(result >= 0)System.out.println(elements[result]);
}
}
class TraceHandler implements InvocationHandler{
private Object target;
public TraceHandler(Object t){
target = t ;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//print implict argument
System.out.println(target);
//print method name
System.out.println("."+method.getName()+"(");
//print explict arguments
if (args != null){
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
if(i <args.length - 1)System.out.println(".");
}
}
System.out.println(")");
return null;
}
}