龙空技术网

80后程序员带你深入java核心技术:接口的特性+接口与抽象类

程序员高级码农II 4714

前言:

眼前各位老铁们对“java对接口的理解”大致比较注重,咱们都需要了解一些“java对接口的理解”的相关资讯。那么小编也在网摘上收集了一些有关“java对接口的理解””的相关知识,希望朋友们能喜欢,咱们一起来学习一下吧!

前言

到目前为止,我们已经学习了Java面向对象程序设计的全部基本知识。本章将开始介绍几种常用的高级技术。这些内容可能不太容易理解,但一定要掌握它们,以便完善自己的Java工具箱。

首先,介绍一下接口(interface)技术,这种技术主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现(implement)一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。了解接口以后,再继续看一下克隆对象(有时又被称为深拷贝)。对象的克隆是指创建一个新对象,且新对象的状态与原始对象的状态相同。当对克隆的新对象进行修改时,不会影响原始对象的状态。

接下来,看一下内部类(inner class)机制。内部类被定义在另外一个类的内部,其中的方法可以访问包含它们的外部类的域,这是一项比较复杂的技术。内部类技术主要用于设计具有相互协作关系的类集合。特别是在编写处理GUI事件的代码时,使用它将可以让代码看起来更加简练专业。

在本章的最后还将介绍代理(proxy),这是一种实现任意接口的对象。代理是一种非常专业的构造工具,它可以被用来构建系统级的工具。如果是第一次学习这本书,可以先跳过这一节。

接口

在Java程序设计语言中,接口不是类,而是一组对类的需求描述,这些类要遵从接口描述的统一格式进行定义。

我们经常听到服务提供商这样说:“如果你的类遵从某个特定接口,那么我就履行这项服务”。

下面给出一个具体的例子。Arrays类中的sort方法承诺可以对对象数组进行排序,但要求满足下列前提:对象所属的类必须实现了Comparable接口。

下面是Comparable接口的代码:

public interface Comparable{int compareTo(Object other);}

这就是说,任何实现Comparable接口的类都需要包含compareTo方法,并且这个方法的参数必须是一个Object对象,返回一个整型数值。

注意:在JDK 5.0中,Comparable接口已经改进为泛型类型。

Public interface Comparable<T>{int compareTo(T other); //parameter has type T}

例如,在实现Comparable<Employee>接口的类中,必须提供下列方法:

int compareTo(Employee other)

也可以使用没有类型参数的“原始”Comparable类型,但必须手工地将compareTo方法的参数转换成所希望的类型。

接口中的所有方法自动地属于public。因此,在接口中声明方法时,不必提供关键字public。

当然,接口中还有一个没有明确说明的附加要求:在调用x.compareTo(y)的时候,这个compareTo方法必须确实比较两个对象的内容,并返回比较的结果。当x小于y时,返回一个负数;

当x等于y时,返回0;否则返回一个正数。

上面这个接口只有一个方法,而有些接口可能包含多个方法。稍后可以看到,在接口中还可以定义常量。然而,更为重要的是要知道接口不能提供哪些功能。接口绝不能含有实例域,也不能在接口中实现方法。提供实例域和方法实现的任务应该由实现接口的那个类来完成。因此,可以将接口看成是没有实例域的抽象类。但是这两个概念还是有一定区别的,稍后将给出详细的解释。

现在,假设我们希望使用Arrays类的sort方法对Employee对象数组进行排序,那么Employee类就必须实现Comparable接口。

为了让类实现一个接口,通常需要下面两个步骤:

1)将类声明为实现给定的接口。

2)对接口中的所有方法进行定义。

要将类声明为实现某个接口,需要使用关键字implements:

class Employee implements Comparable

当然,这里的Employee类需要提供compareTo方法。假设我们希望根据雇员的薪水进行比较。

如果第一个雇员的薪水低于第二个雇员的薪水就返回-1;如果相等就返回0;否则返回1。

警告:在接口声明中,没有将compareTo方法声明为public,这是因为在接口中的所有方法都自动地是public。不过,在实现接口时,必须把方法声明为public;否则,编译器将认为这个方法的访问属性是包可见性,即类的默认访问属性,之后编译器就会给出试图提供更弱的访问权限的警告信息。

在JDK 5.0中,可以做得更好一些。可以将上面的实现替换为对Comparable<Employee>接口的实现。

请注意,将参数Object进行类型转换总是让人感觉不太顺眼,现在已经不见了。

提示:Comparable接口中的compareTo方法将返回一个整型数值。如果两个对象不相等,则返回一个正值或者一个负值。在对两个整数域进行比较时,这点非常有用。

例如,假设每个雇员都有一个唯一整数id,并希望根据ID对雇员进行重新排序,那么就可以返回id - other.id。如果第一个ID小于另一个ID,则返回一个负值;如果两个ID相等,则返回0;否则,返回一个正值。但有一点需要注意:整数的范围不能过大,以避免造成减法运算的溢出。如果能够确信ID为非负整数,或者它们的绝对值不会超过 (Integer.MAX_VALUE-1)/2,就不会出现问题。

当然,这里的相减技巧不适用于浮点值。因为在salary和other.salary很接近但又不相等的时候,它们的差经四舍五入后有可能变成0。

现在,我们已经看到,要让一个类使用排序服务必须让它实现compareTo方法。这是理所当然的,因为要向sort方法提供对象的比较方式。但是为什么不能直接提供一个compareTo方法,而必须实现Comparable接口呢?

主要原因在于Java程序设计语言是一种强类型(strongly typed)语言。在调用方法的时候,编译器将会检查这个方法是否存在。在sort方法中可能存在下面这样的语句:

为此,编译器必须确认a[i]一定有compareTo方法。如果a是一个Comparable对象的数组,就可以确保拥有compareTo方法,因为每个实现Comparable接口的类都必须提供这个方法的定义。

注意:有人认为,将Arrays类中的sort方法定义为接收一个Comparable[ ]数组,就可以在使用元素类型没有实现Comparable接口的数组作为参数调用sort方法时,由编译器给出错误报告。但事实并非如此。在这种情况下,sort方法可以接受一个Object[ ]数组,并对其进行笨拙的类型转换:

如果a[i]不属于实现了Comparable接口的类,虚拟机就会抛出一个异常。

例6-1给出了实现雇员数组排序的全部代码。

例6-1 EmployeeSortTest.java

java.lang.Comparable 1.0

• int compareTo(Object otherObject)

用该对象与otherObject进行比较。如果该对象小于otherObject则返回负整数;如果相等则返回0;否则返回正整数。

java.lang.Comparable<T> 5.0

• int compareTo(T other)

用该对象与other进行比较。如果该对象小于other则返回负整数;如果相等则返回0;否则返回正整数。

java.util.Arrays 1.2

• static void sort(Object[ ] a)

使用mergesort算法对数组a中的元素进行排序。要求数组中的元素必须属于实现了Comparable接口的类,并且元素之间必须是可比较的。

注意:语言标准规定:对于任意的x和y,实现必须能够保证sgn(x.compareTo(y)) =-sgn(y.compareTo(x))。(也就是说,如果y.compareTo(x) 抛出一个异常,那么x.compareTo(y) 也应该抛出一个异常。)这里的“sgn”是一个数值的符号:如果n是负值,sgn(n)等于-1;如果n是0,sgn(n)等于0;如果n是正值,sgn(n)等于1。

简单地讲,如果调换compareTo的参数,结果的符号也应该调换(而不是实际值)。

与equals方法一样,在继承过程中有可能会出现问题。

这是因为Manager扩展了Employee,而Employee实现的是Comparable<Employee>,而不是Comparable<Manager>。如果Manager覆盖了compareTo,那么就必须要做好经理与雇员进行比较的思想准备,绝不能仅仅将雇员转换成经理。

这不符合“反对称”的规则。如果x是一个Employee对象,y是一个Manager对象,那么调用x.compareTo(y)不会抛出异常,它只是将x和y都作为雇员进行比较。但是反过来,y.compareTo(x)将会抛出一个ClassCastException。

这种情况与第5章中讨论的equals方法一样,修改的方式也一样。有两种不同的情况。

如果子类之间的比较含义不一样,就属于不同类对象的非法比较。每个compareTo方法都应该在开始时进行下列检测:

if (getClass( )! = other.getClass( )) throw new ClassCastException( );

如果存在这样一种通用算法,它能够对两个不同的子类对象进行比较,就应该在超类中提供一个compareTo方法,并将这个方法声明为final。

例如,假设不管薪水的多少,我们都想让经理大于雇员。那么,像Executive和Secretary这样的子类又该怎么办呢?如果一定要按照职务排列的话,就应该在Employee类中提供一个rank方法。每个子类覆盖rank,并实现一个比较rank值的compareTo方法。

接口的特性

接口不是类,尤其是不能使用new运算符实例化一个接口:

x = new Comparable(. . .); //ERROR

然而,尽管不能构造接口对象,却能声明接口变量。

Comparable x; //OK

接口变量必须引用实现了接口的类对象:

x = new Employee(. . .); //OK provided Employee implements Comparable

接下来,与使用instanceof检查一个对象是否属于某个特定类一样,也可以使用instance检查一个对象是否实现了某个特定的接口:

if (anObject instanceof Comparable){. . .}

与可以建立类的继承关系一样,也可以扩展接口。这里允许存在多条从具有较高通用性的接口到较高专用性的接口的链。例如,假设有一个称为Moveable的接口。

可以以它为基础扩展一个叫做Powered的接口:

尽管在接口中不能包含实例域或静态方法,但是可以包含常量。例如:

与接口中的方法一样,接口中的域也被自动地设为public static final。

注意:可以将接口方法标记为public,将域标记为public static final。有些程序员出于习惯或高清晰度的考虑,愿意这样做。但Java语言规范却建议不要书写这些多余的关键字,本书也采纳了这个建议。

有些接口只定义了常量,而没有定义方法。例如,在标准库中有一个SwingConstants就是这样一个接口,其中只包含NORTH、SOUTH、HORIZONTAL等常量。任何实现SwingConstants接口的类都自动地继承了这些常量,并可以在方法中直接地引用NORTH,而不必采用SwingConstants.NORTH这样的繁琐书写形式。然而,这样应用接口似乎有点偏离了接口概念的初衷,最好不要这样使用它。

尽管每个类只能够拥有一个超类,但却可以实现多个接口。这就为定义类的行为提供了极大的灵活性。例如,Java程序设计语言有一个非常重要的内置接口,叫做Cloneable(将在下一节中给予详细的讨论)。如果某个类实现了这个Cloneable接口,Object类中的clone方法就可以创建类对象的一个拷贝。如果希望自己设计的类拥有克隆和比较的能力,只要实现这两个接口就可以了。

class Employee implements Cloneable, Comparable

使用逗号将实现的各个接口分隔开。

接口与抽象类

如果阅读了第5章中有关抽象类的内容,就可能会产生这样一个疑问:为什么Java程序设计语言还要不辞辛苦地引入接口概念?为什么不将Comparable直接设计成如下所示的抽象类?

然后,Employee类再直接扩展这个抽象类并提供compareTo方法的实现:

非常遗憾,使用抽象类表示通用属性存在这样一个问题:每个类只能扩展于一个类。假设Employee类已经扩展于一个类,比如Person,那么它就不能再像下面这样扩展第二个类了。

class Employee extends Person, Comparable //ERROR

但每个类可以像下面这样实现多个接口:

class Employee extends Person implements Comparable //OK

有些程序设计语言允许一个类有多个超类,比如C++。我们将此特性称为多继承(multipleinheritance)。而Java的设计者选择了不支持多继承,其主要原因是多继承会让语言本身变得非常复杂(如同C++),效率也会降低(如同Eiffel)。

为了避免这类问题的出现,Java语言利用接口机制来实现多继承的大部分功能。

觉得文章还不错的话,可以转发关注小编,每天更新干货文章!

标签: #java对接口的理解