简介
接口主要用来描述类具有哪些功能,并不给出每个功能的具体实现方式。一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了响应接口的对象。
在 Java 程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵循接口描述的统一格式进行定义。
接口中的所有方法自动属于 public,所以在接口中声明方法可以不用提供关键字 public 。
类实现接口,通常有两个步骤:
- 将类声明为实现指定的接口
- 对接口中的所有方法进行定义
要将类声明为实现某个接口,需要使用关键字 implements :
public interface Comparable{ int compareTo(Object other);}
class Employee implements Comparable{}
接口特性
接口不是类,尤其不能使用 new 运算符实例化一个接口。但是可以声明接口的变量,接口变量必须引用实现该接口的类对象。
X = new Comparable(...); //Error
Comparable x; // OK
x = new Employee(); // OK
与可以建立类的继承关系一样,接口也可以被扩展。这里允许存在多条从较高通用性的接口道较高专用型的忌口的链。
public interface Moveable{ void move(double x,double y);}
public interface Powerd extends Moveable{ double milesPerGallon();}
虽然在接口中不能包含实例域或静态方法,但可以包含常量。
public interface Powerd extends Moveable{
double milesPerGallon();
double SPEED_LIMIT = 95;
}
与接口中的方法都自动地被设置为 public 一样,接口中的域将自动设为 public static final 。
接口与抽象类
使用抽象类表示通用属性存在一个问题 :每个类只能扩展一个类。但是每个类可以实现多个接口。
静态方法
在 Java SE 8 中,允许在接口中增加静态方法。理论上,没有任何理由认为这是不合适的,只是这有违于将接口最为抽象规范的初衷。
public interface Path{
public static Path get(String first,String... more){
return FileSystems.getDefault().getPath(first,more);
}
...
}
默认方法
可以为接口方法提供一个默认实现。必须用 default 修饰符标记这样的方法。
public interface Comparable<T>{
default int compareTo(T other){
return 0; // 默认情况下,所有的数据都相等
}
}
这样可能没有什么用,因为 Comparable 的每一个实现都要覆盖这个方法。但是在某些情况下,默认方法可能很有用。例如:
public interface Collection{
int size();
default boolean isEmpty(){
return size() == 0;
}
...
}
这样在实现 Collection 的程序员就不用操心实现 isEmpty 方法了。
默认方法的一个重要用法是 “接口演化”。例, Collection 接口作为 Java 的一部分已经很多年了,在 Java SE 8 中,为这个接口添加了一个 stream 方法。假设 stream 不是默认方法,那么引用 Collection 接口的类将不能编译,因为没有实现这个新方法。为接口增加一个非默认方法不能保证 “源代码兼容”。
不过,假设不重新编译这个类,而只是使用原先的一个包含这个类的 JAR 文件。这个类仍可以正常加载,尽管没有这个新方法。但是,如果程序在一个该类的实例上调用 stream 方法,就会出现一个 AbstractMethodError。
默认方法可以解决这些问题。如果没有重新编译而直接加载这个类,并在一个实例上调用 stream 方法,将调用 Collection stream 方法。
解决默认方法冲突
如果现在一个接口中将一个方法定义为默认方法,然后又在超类或者另一个接口中定义了同样的方法。会发生什么?Java 对应的规则比较简单:
- 超类优先。如果超类提供了一个具体方法,接口中同名并且有相同参数类型的默认方法会被忽略。
- 接口冲突。如果一个超接口提供了一个某人方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突。
public interface Named{
default String getName(){
return getClass().getName()+"_"+hashCode();
}
}
Class Student implements Person,Named{
...
}
类会继承 Person 和 Named 接口提供的两个不一致的 getName 方法。并不是从中选择一个,Java 编译器会报告一个错误,让程序员来解决这个二义性。只需要在 Student 类中提供一个 getName 方法,在这个方法中,可以选择两个冲突方法中的一个。
Class Student implements Person,Named{
public String getName(){ return Person.super.getName();}
...
}
接口示例
接口与回调
回调是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的行动。
例如:程序中有一个时钟,每隔十秒钟报一次时。
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;
import javax.swing.*;
public class TimerTest {
public static void main(String[] args) {
ActionListener listener = new TimePrinter();
Timer t = new Timer(10000, listener);
t.start();
JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
}
class TimePrinter implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("At the tone, the time is " + new Date());
Toolkit.getDefaultToolkit().beep();
}
}
对象克隆
Cloneable 接口指示一个类提供了一个安全的 clone 方法。clone 产生的对象是一个新对象,它的初始状态与源对象相同,但是之后他们各自会有自己不同的状态。
不过, clone 方法是 Object 的一个 protected 方法,这说明你的代码不能直接调用这个方法。只有 Employee 类可以克隆 Employee 对象。
默认的克隆操作是“浅拷贝”,并没有克隆对象中引用的的其他对象。如果源对象和浅克隆对象共享的子对象是不可变的,这种共享就是安全的。如果子对象属于一个不可变的类,如 String ,这种共享是安全的。或者在对象的声明周期中,子对象一只包含不变的常量,没有更改器方法会改变它,也没有方法会生成它的引用,这种情况下同样是安全的。

不过,通常子对象都是可变的,必须重新定义 clone 方法来建立一个深拷贝,同时克隆所有子对象。
对于每一个类,需要确定:
- 默认的 clone 方法是否满足要求
- 是否可以在可变的子对象上调用 clone 来修补默认的 clone 方法
- 是否不改使用 clone
实际上第 3 个选项是默认选项。如果选择第 1 项或第 2 项,类必须:
- 实现 Cloneable 接口
- 重新定义 clone 方法,并指定 public 访问修饰符
Tips:
Object 类中的 clone 方法声明为 protected,子类只能调用受保护的 clone 方法来克隆自己的对象。必须重新定义 clone 为 public 才能允许所有方法克隆对象。
Cloneable 接口没有指定 clone 方法,这个方法是从 Object 类继承的。这个接口指示作为一个标记,指示类设计者了解克隆过程。标记接口不包含任何方法,它的唯一作用就是允许在类型查询中使用 instanceof。
即使 clone 的默认(浅拷贝)实现能满足要求,还是需要实现 Cloneable 接口,将 clone 重新定义为 public,再调用 super.clone()。
class Employee implements Cloneable{
public Employee clone() throws CloneNotSupportedException{
return (Employee) super.clone();
}
...
}
与 Object.clone 提供的浅拷贝相比,这个 clone 方法并没有为它添加任何工嗯呢该。只是让这个方法是公有的。要建立深拷贝,还需要做很多工作,克隆对象中可变的实例域。
class Employee implements Cloneable{
public Employee clone() throws CloneNotSupportedException{
Employee cloned = (Employee) super.clone();
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
...
}
如果在一个对象上调用 clone,但这个对象的类并没有实现 Cloneable 接口,Object 类的 clone 方法就会抛出一个 CloneNotSupportedException。
捕捉这个异常是不是更好一些?这非常适合 final 类。否则,还是保留 throws 说明符。这样就允许子类在不支持克隆时选择抛出一个 CloneNotSupportedException。
要不要在自己的类中实现 clone 呢?如果你的客户端需要建立深拷贝,可能就需要实现这个方法。
Tips:
所有数组类型都有一个 public 的 clone 方法,而不是 protected。可以用这个方法建立一个新数据,包含原数组所有元素的副本。
标题:Interface 接口详解
作者:General
地址:https://ZZUGEneral.github.io/articles/2020/10/27/1603809416593.html
一个没有得到Java开发工作的测试员!