第6章 接口与内部类
首先介绍 接口(interface)技术,这种技术主要用来描述类具有什么功能,而并不给出每个功能的具体实现.一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象.了解接口以后,再继续看一下克隆对象. 对象的克隆是指创建一个新对象,且新对象的状态与原始对象的状态相同.当对克隆的新对象进行修改时,不会影响原始对象的状态.接下来,看一下内部类(inner class)机制,内部类定义在另外一个类的内部,其中的方法可以访问包含它们的外部类的域,这是一项比较复杂的技术. 内部类技术主要用于设计具有相互协作关系的类集合.
最后介绍 代理(proxy),这是一种实现任意接口的对象.代理是一种非常专业的构造工具,它可以用来构建系统级的工具.
6.1 接口
在Java程序设计语言中, 接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义.如果类遵从某个特定接口,那么就履行这项服务.下面给出一个具体的示例,Array类中的sort方法承诺可以对对象数组进行排序,但要求满足下列前提:对象所属的类必须实现了Comparable接口.
下面是Comparable接口的代码:
public interface Comparable
{
int compareTo(Object other);
}
这样就是说,任何实现Comparable接口的类都需要包含compareTo方法,并且这个方法的参数必须是一个Object对象,返回一个整型数值.
注释:Comparable接口已经改进为泛型类型
public interface Comparable<T>
{
int compareTo(T other); // paremeter has type T
}
例如,在实现Comparable<Employee>接口的类中,必须提供下面方法
int compareTo(Employee other);
接口中的所有方法自动地属于 public,因此,在接口声明方法时,不必提供关键字 public .
接口还有一个没有明确说明的附加要求:在调用x.compareTo(y)的时候,这个compareTo方法必须确实比较两个对象的内容,并返回比较的结果.
接口绝不能含有实例域,也不能在接口中实现方法.提供实例域和方法实现的任务应该由实现接口的那个类来完成.因此,可以将接口看成是没有实例域的抽象类.
假设希望使用Arrays类的sort方法对Employee对象数组进行排序,Employee类就必须实现Comparable接口.
为了让类实现一个接口,通常需要下面两个步骤:
1.将类声明为实现给定的接口
2.对接口中的所有方法进行定义
要 将类声明为实现某个接口,需要使用关键字 implements:
class Employee implements Comparable
这里的Employee需要提供compareTo方法.
public int compareTo(Object otherObject)
{
Employee other = (Employee)otherObject;
return Double.compare(salary, other.salary);
}
这里使用了静态Double.compare方法.
警告:在接口声明中,没有将compareTo方法声明为 public,这是因为在接口中的所有方法都自动地是 public,不过在实现接口时,必须把方法声明为 public .
现在已经看到,要让一个类使用排序服务必须让它实现compareTo方法.这是理所当然的,因为要向sort方法提供对象的比较方式.但是为什么不能在Employee类直接提供一个compareTo方法,而必须实现Comparable接口呢?
主要原因在于Java程序设计语言是一种强类型(strongly typed)语言(有点疑惑).在调用方法的时候,编译器将会检查这个方法是否存在.在sort方法中可能存在下面这样的语句:
if (a[i].compareTo(a[j]) > 0)
{
// rearrange a[i] and a]j\
...
}
为此,编译器必须确认a[i]一定有compareTo方法,如果a是一个Comparable对象的数组,就可以确保拥有compareTo方法,因为这个实现Comparable接口的类都必须提供这个方法的定义.
程序6-1给出了对一个Employee类实例数组进行排序的完成代码.
interfaces/EmployeeSortTest.java如下所示:
package interfaces;
import java.util.Arrays;
/**
* This program denonstrates the use of the Comparable interface
*/
public class EmployeeSortTest
{
public static void main(String[] args)
{
Employee[] staff = new Employee[3];
staff[0] = new Employee("Carl", 75000);
staff[1] = new Employee("Hacker", 35000);
staff[2] = new Employee("Tommy", 50000);
System.out.println("Before sort:");
for (Employee e : staff)
System.out.println("name = " + e.getName() + ", salary = " + e.getSalary());
Arrays.sort(staff);
System.out.println("After sort:");
for (Employee e : staff)
System.out.println("name = " + e.getName() + ", salary = " + e.getSalary());
}
}
interfaces/Employee.java如下所示:
package interfaces;
public class Employee implements Comparable<Employee>
{
private String name;
private double salary;
public Employee(String n, double s)
{
name = n;
salary = s;
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
/**
* Compares employees by salary
* @param other another Employee object
* @return a negative value if this employee has a lower salary than
* otherObject, 0 if the salaries are the same, a positive value otherwise
*/
public int compareTo(Employee other)
{
return Double.compare(salary, other.salary);
}
}
运行结果如下所示:
java.lang.Comparable<T>方法如下所示:
int compareTo(T other);
用这个对象与other进行比较.小于other返回负值;等于other返回0;大于other返回正值.
java.util.Arrays方法如下所示:
static void sort(Object[] a);
使用mergesort算法对数组a中的元素进行排序,要求数组中的元素必须实现了Comparable接口的类,并且元素之间是可比较的.
6.1.1 接口的特性
接口不是类,尤其不能使用 new 运算符实例化一个接口:x = new Comparable(...); // error
然而,尽管
不能构造接口的对象,却能声明接口的变量:
Comparable x; // ok
接口变量必须引用实现了接口的类对象:
x = new Employee(...); // ok provided Employee implements Comparable
接下来,如同使用 instanceof 检查一个对象是否属于某个特定类一样,也可以使用 instance 检查一个对象是否实现了某个特定的接口:
if (anObject instanceof Comparable) { ... }
与可以建立类的继承关系一样,接口也可以被扩展.这里允许存在多条从具有较高通用性的接口到较高专用性的接口的链.例如,假如有一个称为Moveable的接口:
public interface Moveable
{
void move(double x, double y);
}
然后,可以以它为基础扩展一个叫做Powered的接口:
public interface Powered extends Moveable
{
double milesPerGallon();
}
虽然在接口中不能包含实例或静态方法,但却可以包含常量.例如:
public interface Powered extends Moveable
{
double milePerGallon();
double SPEED_LIMIT = 95; // a public static final constant
}
与接口中的方法都自动地被设置为 public 一样,接口中的域将被自动地设为 public static final .
尽管 每个类只能够拥有一个超类,但可以实现多个接口.例如,Java程序设计语言有一个非常重要的内置接口,称为Cloneable,如果某个类实现了这个Cloneable接口,Object类中的clone方法就可以创建类对象的一个拷贝.如果希望自己设计的类拥有克隆和比较的能力,只要实现这两个接口就可以了.
class Employee implements Cloneable, Comparable
使用逗号将实现的各个接口分隔开.
6.1.2 接口与抽象类
为什么Java程序设计语言还要不辞辛苦地引入接口概念? 为什么不将Comparable直接设计成如下所示的抽象类.abstract class Comparable
{
public abstract int compareTo(Object other);
}
然后,Employee再直接扩展这个抽象类,并提供compareTo方法的实现:
class Employee extends Comparable
{
public int compareTo(Object other) { ... }
}
非常遗憾,使用抽象类表示通用属性存在这样一个问题:
每个类只能扩展于一个类.假设Employee类已经扩展于一个类,例如Person,它就不能再像下面这样扩展第二个类了:
class Employee extends Person, Comparable // error
但每个类都可以像下面这样实现多个接口:
class Employee extends Person implements Comparable // ok
有些程序设计语言允许一个类有多个超类,例如C++.称此特性为多继承.而
Java的设计者选择了不支持多继承,其主要原因是多继承会让语言本身变得非常复杂.
实际上,接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性.
注释:C++具有多继承,随之带来了一些诸如虚基类,控制规则和横向指针类型转换等复杂特性.很少有C++程序员使用多继承,有些程序员建议只对"混合"风格的继承使用多继承.在"混合"风格中,一个主要的基类描述父对象,其他的基类(因此称为混合)扮演辅助的角色.这种风格类似于Java类中从一个基类派生,然后实现若干个辅助接口.然而,在C++中,"混合"类可以添加默认的行为,而Java的接口则不行.
关于接口与抽象类有一篇非常好的文章, 深入理解Java的接口和抽象类.关键部分截图如下: