第6章 接口与内部类
本章内容:
* 接口
* 对象克隆
* 接口与回调
* 内部类
* 代理
接口技术主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现(implement)一个或多个功能,并在需要接口的地方,随时使用实现了相应接口的对象。 对象的克隆是指创建一个新对象,且新对象的状态和原始对象的状态相同。当对克隆的新对象进行修改时,不会影响原始对象的状态。 内部类定义在另外一个类的内部,其中的方法可以访问包含它们的外部类的域。内部类技术主要用于设计具有相互协作关系的类集合。 代理是一种实现任意接口的对象。代理是一种非常专业的构造工具,他可以用来构建系统级的工具。
6.1 接口
在Java程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。 接口中的所有方法自动地属于public。因此,在接口中声明方法时,不必提供关键字public。 接口绝不能含有实例域,也不能在接口中实现方法。提供实例域和方法实现的任务应该由实现接口的那个类来完成。因此,可以将接口看成是没有实例域的抽象类。 为了让类实现一个接口,通常需要下面两个步骤: (1)将类声明为实现给定的接口。 (2)对接口中的所有方法进行定义。 在实现接口时,必须把方法声明为public;否则,编译器将认为这个方法的访问属性是包可见性,即类的默认访问属性,之后编译器就会给出试图提供更弱的访问权限的警告信息。 Java语言是一种强类型(strongly typed)语言。在调用方法的时候,编译器将会检查这个方法是否存在。 java.lang.Comparable 1.0
java.lang.Comparable 1.0 用这个对象与other进行比较。如果这个对象小于other则返回负值;如果相等则返回0;否则返回正值。 java.util.Arrays 1.2
static void sort(Object[] a) 使用mergesort算法对数组a中的元素进行排序。要求数组中的元素必须属于实现了Comparable接口的类,并且元素之间必须是可比较的。 java.lang.Integer 7
static int compare(int x,int y) 如果x<y返回一个负整数;如果x和y相等,则返回0;否则返回一个负整数。 语言标准规定:对于任意的x和y,实现必须能够保证sgn(x.compareTo(x))=-sgn(y.compareTo(x))。这里的“sgn”是一个数值的符号:如果n是负值,sgn(n)等于-1;如果n是0,sgn(n)等于0;如果n是正值,sgn(n)等于1。简单地讲,如果调换compareTo的参数,结果的符号也应该调换(而不是实际值)。
6.1.1 接口的特性
接口不是类,尤其不能使用new运算符实例化一个接口。然而,尽管不能构造接口的对象,却能声明接口的变量。接口变量必须引用实现了接口的对象。可以使用instance检查一个对象是否实现了某个特定的接口。接口也可以被扩展。允许存在多条从具有较高通用性的接口到较高专用性的接口的链。 接口中不能包含实例域或静态方法,但却可以包含常量。 接口中的方法都自动地被设置为public,接口中的域将被自动设为public static final。 有些接口只定义了常量,而没有定义方法。这种应用接口似乎有点偏离了接口概念的初衷,最好不要这样使用它。
6.1.2 接口与抽象类
使用抽象类表示通用属性存在一个问题:每个类只能扩展于一个类。 接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。
6.2 对象克隆
当拷贝一个变量时,原始变量与拷贝变量引用同一个对象。也就是说,改变一个变量所引用的对象将会对另一个变量产生影响。 如果创建一个对象的新的copy,它的最初状态与original一样,但以后将可以各自改变各自的状态,那就需要使用clone方法。 默认的克隆操作是浅拷贝,它并没有克隆包含在对象中的内部对象。 如果进行浅拷贝会发生什么呢?这要根据具体情况而定。如果原始对象与浅克隆对象共享的子对象是不可变的,将不会产生什么任何问题。例如,子对象属于像String类这样的不允许改变的类;也可能子对象在其生命周期内不会发生改变,既没有更改它们的方法,也没有创建对它引用的方法。然而,更常见的情况是子对象可变,因此必须重新定义clone方法,以便实现克隆子对象的深拷贝。 对于每一个类,都需要做出下列判断: (1)默认的clone方法是否满足要求。 (2)默认的clone方法是否能够通过调用可变子对象的clone得到修补。 (3)是否不应该使用clone。 实际上,选项3是默认的。如果选择1或2,类必须: (1)实现Cloneable接口。 (2)使用public访问修饰符重新定义clone方法。 在这里,Cloneable接口的出现于接口的正常使用没有任何关系,尤其是,它并没有指定clone方法,这个方法是从Object类继承而来的。接口在这里只是作为一个标记,表明类设计者知道用进行克隆处理。如果一个对象需要克隆,而没有实现Cloneable接口,就会产生一个已检验异常(checked exception)。 即使clone的默认实现(浅拷贝)能够满足需求,也应该实现Cloneable接口,将clone重定义为public,并调用susper.clone()。 在Java SE 5.0以前的版本中,clone方法总是返回Object类型,而现在,协变返回类型特性允许克隆方法指定正确的返回类型。
6.3 接口与回调
回调(callback)是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采用的动作。 传递一个对象要比传递一个函数灵活的多。 Java有函数指针的对应物-Method对象。然而,使用起来却比较困难,速度也稍慢一些,并且在编译时不能提供类型的安全性检查。 javax.swing.JOptionPane 1.2
static void showMessageDialog(Component parent,Object message) 显示一个包含一条消息和OK按钮的对话框。这个对话框将位于其parent组件的中央。如果parent为null,对话框将显示在屏幕的中央。 javax.swing.Timer 1.2
Timer(int interval, ActionListener listener) 构造一个定时器,每隔interval毫秒钟通告listener一次。 void start() 启动定时器。一旦启动成功,定时器将调用监听器的actionPerformed。 void stop() 停止定时器。一旦停止成功,定时器将不再调用监听器的actionPerformed。 java.awt.Toolkit 1.0
static Toolkit getDefaultToolkit() 获得默认的工具箱。工具箱包含有关GUI环境的信息。 void beep() 发出一声铃响。
6.4 内部类
内部类(inner class)是定义在另一个类中的类。为什么需要使用内部类呢?其主要原因有以下三点:
内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。 内部类可以对同一个包中的其他类隐藏起来。 当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷。
6.4.1 使用内部类访问对象状态
从传统的意义上讲,一个方法可以引用调用这个方法的对象数据域。内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。 为了能够运行这个程序,内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。 外围类的引用在构造器中设置。编译器修改了所有的内部类的构造器,添加一个外围类引用的参数。
6.4.2 内部类的特殊语法规则
通过,this限定词是多余的。不过,可以通过显式地命名将外围类引用设置为其他的对象。
6.4.3 内部类是否有用、必要和安全
如果内部类访问了私有数据域,就有可能通过附加在外围所在包中的其他类访问它们,但做这些事情需要高超的技巧和极大的决心。
6.4.4 局部内部类
局部类不用用public或private访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。 局部类有一个优势,即对外部世界可以完全地隐藏起来。
6.4.5 由外部方法访问final变量
局部类还有一个优点。它们不仅能够访问它们的外部类,还可以访问布局变量。不过,那些局部变量必须被声明为final。 final关键字可以应用于局部变量‘实例变量和静态变量。在所有这些情况下,它们的含义都是:在创建这个变量之后,只能够为之赋值一次。此后,再也不能修改它的值了,这就是final。 在定义final变量的时候,不必进行初始化。定义时没有初始化的final变量通常被称为空final(blank final)变量。
6.4.6 匿名内部类
假设只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类(anonymous inner class)。 由于构造器的名字必须与类名相同,而匿名类没有类名,所以,匿名类不能有构造器。取而代之的是,将构造器参数传递给超类(superclass)构造器。尤其是内部类实现接口的时候,不能有任何构造参数。 调用getClass时调用的是this.getClasss(),而静态方法没有this。所以应该使用以下表达式:new Object(){}.getClass().getEnclosingClass()//get class of static method
,在这里,new Object(){}会建立Object的一个匿名子类的一个匿名对象,getEnclosingClass则得到其外围类,也就是包含这个静态方法的类。
6.4.7 静态内部类
有时候,使用内部类只是为了把一个类隐藏在另一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便消除产生的引用。 只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。 声明在接口中的内部类自动成为static和public类。
6.5 代理
利用代理可以在运行时创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定需要实现哪个接口才有必要使用。 代理类可以在运行时创建全新的类。这样的代理类能够实现指定的接口。尤其是,它具有下列方法:
指定接口所需要的全部方法。 Object类中的全部方法,例如,toString、equals等。 然后,不能在运行时定义这些方法的新代码。而是要提供一个调用处理器(invocaton handler)。调用处理器是实现了InvocationHandler接口的类对象。在这个接口中只有一个方法:Object invoke(Object proxy,Method method,Object[] args)
。 无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。调用处理器必须给出处理调用的方式。
要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。这个方法有三个参数:
一个类加载器(class loader)。作为Java安全模型的一部分,对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。目前,用null表示使用默认的类加载器。 一个Class对象数组,每个元素都是需要实现的接口。 一个调用处理器。
使用代理可能处于很多原因,例如:
路由对远程服务器的方法调用。 在程序运行期间,将用户接口事件与动作关联起来。 为调试,跟踪方法调用。
代理类是在程序运行过程中创建的,然而,一旦被创建,就变成了常规类,与虚拟机中的任何其他类没有什么区别。 所有的砝码类都扩展于Proxy类。一个代理类只有一个实例域—调用处理器,它定义在roxy的超类中。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。 所有的代理来都覆盖了Object类中的方法toString、equals和hashCode。如同所有的代理方法一样,这些方法仅仅调用了调用处理器的invoke。Object类中的其他方法(如clone和getClass)没有被重新定义。 没有定义代理类的名字,Sun虚拟机中的Proxy类将生成一个以字符串$Proxy开头的类名。 没有特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说,如果使用同一个类加载器和接口数组调用两次newProxyInstance方法的话,那么只能够得到同一个类的两个对象。也可以利用getProxyClass方法获得这个类:Class proxyClass = Proxy.getProxyClass(null,interfaces);
。 代理类一定是public和final。如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则,所有非公有的接口都必须属于同一个包,同时,代理类也属于这个包。 可以通过调用Proxy类中的isProxyClass方法检测一个特定的Class对象是否代表一个代理类。 java.lang.reflect.InvocationHandler 1.3
Object invoke(Object proxy,Method method,Object[] args) 定义了代理对象调用方法时希望执行的动作。 java.lang.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 c) 如果c是一个代理类返回true。
转载于:https://www.cnblogs.com/zhangmiao14/p/9630205.html