0%

Java - 接口

学习Java中有关接口的概念。

参考资料

《Java核心技术 卷1(第十一版)》

《Java基础核心总结》

菜鸟教程


接口的概念

在 Java 中,接口不是类,而是对希望符合这个接口的类的一组需求。下面以 Comparable 接口为例。

1
2
3
4
public interface Comparable
{
int compareTo(Object other);
}

Array 类中的 sort 方法承诺可以对对象数组进行排序,要是排序对象所属的类必须实现 Comparable 接口。任何实现 Comparable 的类都必须包含一个 compareTo 方法。

如果给 sort 传递了一个没实现接口的类的对象数组,sort会将其强制类型转换;如果这个对象数组成员不属于任何实现了 Comparable 的类,编译时就会报错。

接口中的方法默认均为 public 方法,声明时不需要提供关键字(接口中的方法修饰符仅限于 public, private, abstract, default, static and strictfp)。

接口中不能有实例字段,但是 Java 8 之后可以在接口中实现简单的方法(前提是不引用任何实例字段)。

将类声明为实现某个接口的语句为 class 类名 implements 接口1(,接口2,接口3,...)。在接口声明中,需要被实现的方法可以不写 public 修饰符,但是在实现接口的类中声明这些方法时,必须写上 public

接口的属性

接口不是类,不能使用 new 实例化一个接口。

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

但是可以声明接口的变量,接口类型的变量必须引用实现了这个接口的类对象。

1
2
Comparable x;
x = new XXX(...); // XXX is the name of a class that has implemented this interface.

instanceof 可以检测一个对象是否实现了某个特定的接口。

1
if(anObject instanceof Comparable) {...}

接口可以扩展接口,语法同类继承。
接口中不能包含实例字段,但是可以包含常量。与接口中的方法都默认是 public 一样,接口中的字段默认是 public static final

1
public interface A {int ACONSTANT = 1; }

尽管每个类只能有一个超类,但是可以实现多个接口。

1
class Employee implements Comparable, Cloneable {...}

虽然类不能多继承,但是接口可以同时继承多个接口。

1
interface C extends A, B {...}	// OK

接口方法

静态和私有方法

Java 8 中允许在接口中添加静态方法(虽然通常情况下相关的静态方法会被存放在接口的伴随类中,例如 Collection/Collections 和 Path/Paths)。
在Java 9 中,可以为接口添加私有方法,私有方法可以是静态或者实例方法,一般作为接口中的辅助方法来使用。

默认方法

可以为接口中的方法提供一个默认的实现,例如:

1
2
3
4
5
public interface Comparable
{
default int compareTo(T other) {return 0; }
// by default, all the elements are the same
}

默认方法可以调用其他方法,例如:

1
2
3
4
5
6
public interface Collection
{
int size(); //an abstruct method
default boolean isEmpty() {return size() == 0; }
...
}

默认方法的一个重要作用是 “接口演化”

以 Collection 接口为例,假如之前你为其提供了一个 Bag 类。

1
public class Bag implements Collection {...}

后来,在 Java 8 中,又为其提供了一个 stream 方法。如果该方法不是一个默认方法,那么此时 Bag 类将不能编译成功,因为它没有实现这个方法。如果将方法实现为一个默认方法的话,Bag 就能正常编译了。另外,如果没有重新编译 Bag 类,而是直接加载并在一个 Bag 实例上调用 stream 方法,将会调用 Collection.stream 默认方法。

解决默认方法的冲突

如果先在一个接口中定义了一个默认方法,然后又在另一个超类或者接口中定义了一个同样的方法,某个类在继承超类且实现接口时可能会产生二义性的问题。Java 中对应的规则如下:

  1. 超类优先。
    如果超类中提供了一个具体的方法,且与实现接口中的某个默认方法同名同参数,默认方法会被覆盖。
  2. 接口冲突。
    如果一个接口中提供了一个默认方法,另一个接口也定义了同名同参数类型的方法(无论是不是默认方法),必须要覆盖这个方法来解决冲突。
    以下面的 getName 方法为例,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface Person
    {
    default String getName() {return ""; }
    }

    interface Named
    {
    default String getName() {return getClass().getName() + "_" + hashCode(); }
    }

    class Student implements Person, Named {...}
    Student 类会继承两个接口提供的不一致的 getName 方法。并不是在其中任选一个,Java 编译器会报错。解决方法是在 Student 类中再提供一个 getName 方法进行覆盖,可以选择两个冲突方法中的其中一个,例如,
    1
    public String getName() {return Person.super.getName();}

接口如何产生冲突并不重要,只要两个接口中提供了同名同参数类型的方法,且至少一个接口中为其提供了默认实现,Java 编译器就会报错。比如,就算 Named 接口没有提供 getName 的默认实现,还是会产生冲突问题。

相反,如果两个接口都没有为共享方法提供默认实现,就不会发生冲突。实现类可以选择实现这个方法或者不实现,不实现的时候这个类就是一个抽象类。

假如上述代码中的 Person 是 Student 继承的超类,那么就会采取规则 1, Student 会继承超类中的方法,Named 中的相同默认方法会被忽略。

Cloneable接口与对象克隆

Java 中对引用类型变量作等号赋值的操作是浅拷贝,如果需要对对象进行深拷贝,就需要用到类的 clone 方法。

clone 是类 Object 中的一个 protected 方法,只对包 java.lang. 以及其子类中可见,直接使用的话,就只有对应类中可以克隆对应类的对象。

Object 中的克隆方法的操作是对对象中的字段逐一拷贝,对于只包含基本数据类型或者不可变类的对象的类来说没有问题;但是如果类中有其他子对象的引用,该部分字段就仍是浅拷贝。所以一般情况下自定义的类需要重新定义 clone 方法来实现完全的深拷贝。

重定义时需要注意两点:

  1. 类必须注明实现 Cloneable 接口。
    这个接口仅作为标记——毕竟 clone 方法是从 Object 中继承来的。如果一个对象请求克隆,但是没有实现这个接口,编译器会生成一个 CloneNotSupportedException 异常。
    Cloneable 接口是 Java 提供的少数标记接口之一,这种接口中没有定义任何方法,只是用来允许在类查询中使用关键字 instanceof。一般建议自己的程序中不要使用标记接口。
  1. 重定义 clone 方法时应声明为 public 方法。
    由于 clone 原来是 protected 方法,重新定义时就需要定义其为 public 才能保证自己类以外的方法可以克隆自己类的对象。

当然,即使 clone 的默认实现(浅拷贝)能够满足要求,韩式需要实现 Cloneable 接口,然后将其重定义为 public 方法包装一下,在方法中调用 super.clone()。例如:

1
2
3
4
5
6
class Employee implements Cloneable
{
public Employee clone() throws CloneNotSupportedException {
return (Employee) super.clone();
}
}

要重定义深拷贝的克隆,需要对类实例中的对应引用类型字段也进行深拷贝,如果包含没有实现深拷贝版 clone 的类,同时就需要为其又定义一次 clone();如果当前的类有被其它类继承,就还得考虑子类中的克隆。

听起来很麻烦,但是实际上克隆确实并没有想象中那么常用,标准库中只有不到 5% 的类实现了 clone …… 尽量使用默认 clone 或者不使用为好。

-------------本文结束感谢您的阅读-------------