Java 接口

简介

接口主要用来描述类具有哪些功能,并不给出每个功能的具体实现方式。一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了响应接口的对象。

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

接口中的所有方法自动属于 public,所以在接口中声明方法可以不用提供关键字 public 。

类实现接口,通常有两个步骤:

  1. 将类声明为实现指定的接口

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

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

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n12" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public interface Comparable{ int compareTo(Object other);}
class Employee implements Comparable{}</pre>

接口特性

接口不是类,尤其不能使用 new 运算符实例化一个接口。但是可以声明接口的变量,接口变量必须引用实现该接口的类对象。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" cid="n15" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">X = new Comparable(...); //Error

Comparable x; // OK
x = new Employee(); // OK</pre>

与可以建立类的继承关系一样,接口也可以被扩展。这里允许存在多条从较高通用性的接口道较高专用型的忌口的链。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n17" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public interface Moveable{ void move(double x,double y);}
public interface Powerd extends Moveable{ double milesPerGallon();}</pre>

虽然在接口中不能包含实例域或静态方法,但可以包含常量。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" cid="n19" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public interface Powerd extends Moveable{
double milesPerGallon();
double SPEED_LIMIT = 95;
}</pre>

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

接口与抽象类

使用抽象类表示通用属性存在一个问题 :每个类只能扩展一个类。但是每个类可以实现多个接口。

静态方法

在 Java SE 8 中,允许在接口中增加静态方法。理论上,没有任何理由认为这是不合适的,只是这有违于将接口最为抽象规范的初衷。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" cid="n25" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public interface Path{
public static Path get(String first,String... more){
return FileSystems.getDefault().getPath(first,more);
}
...
}</pre>

默认方法

可以为接口方法提供一个默认实现。必须用 default 修饰符标记这样的方法。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" cid="n28" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public interface Comparable<T>{
default int compareTo(T other){
return 0; // 默认情况下,所有的数据都相等
}
}</pre>

这样可能没有什么用,因为 Comparable 的每一个实现都要覆盖这个方法。但是在某些情况下,默认方法可能很有用。例如:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" cid="n30" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public interface Collection{
int size();
default boolean isEmpty(){
return size() == 0;
}
...
}</pre>

这样在实现 Collection 的程序员就不用操心实现 isEmpty 方法了。

默认方法的一个重要用法是 “接口演化”。例, Collection 接口作为 Java 的一部分已经很多年了,在 Java SE 8中,为这个接口添加了一个 stream 方法。假设 stream 不是默认方法,那么引用 Collection 接口的类将不能编译,因为没有实现这个新方法。为接口增加一个非默认方法不能保证 “源代码兼容”。

不过,假设不重新编译这个类,而只是使用原先的一个包含这个类的 JAR 文件。这个类仍可以正常加载,尽管没有这个新方法。但是,如果程序在一个该类的实例上调用 stream 方法,就会出现一个 AbstractMethodError。

默认方法可以解决这些问题。如果没有重新编译而直接加载这个类,并在一个实例上调用 stream 方法,将调用 Collection stream 方法。

解决默认方法冲突

如果现在一个接口中将一个方法定义为默认方法,然后又在超类或者另一个接口中定义了同样的方法。会发生什么?Java对应的规则比较简单:

  1. 超类优先。如果超类提供了一个具体方法,接口中同名并且有相同参数类型的默认方法会被忽略。

  2. 接口冲突。如果一个超接口提供了一个某人方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n42" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public interface Named{
default String getName(){
return getClass().getName()+"_"+hashCode();
}
}

Class Student implements Person,Named{
...
}</pre>

类会继承 Person 和 Named 接口提供的两个不一致的 getName 方法。并不是从中选择一个,Java 编译器会报告一个错误,让程序员来解决这个二义性。只需要在 Student 类中提供一个 getName 方法,在这个方法中,可以选择两个冲突方法中的一个。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n44" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">Class Student implements Person,Named{
public String getName(){ return Person.super.getName();}
...
}</pre>

接口示例

接口与回调

回调是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的行动。

例如:程序中有一个时钟,每隔十秒钟报一次时。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n49" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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();
}
}</pre>

对象克隆

Cloneable 接口指示一个类提供了一个安全的 clone 方法。clone 产生的对象是一个新对象,它的初始状态与源对象相同,但是之后他们各自会有自己不同的状态。

不过, clone 方法是 Object 的一个 protected 方法,这说明你的代码不能直接调用这个方法。只有 Employee 类可以克隆 Employee 对象。

默认的克隆操作是“浅拷贝”,并没有克隆对象中引用的的其他对象。如果源对象和浅克隆对象共享的子对象是不可变的,这种共享就是安全的。如果子对象属于一个不可变的类,如 String ,这种共享是安全的。或者在对象的声明周期中,子对象一只包含不变的常量,没有更改器方法会改变它,也没有方法会生成它的引用,这种情况下同样是安全的。

[图片上传失败...(image-f0ed21-1605435431321)]

不过,通常子对象都是可变的,必须重新定义 clone 方法来建立一个深拷贝,同时克隆所有子对象。

对于每一个类,需要确定:

  1. 默认的 clone 方法是否满足要求

  2. 是否可以在可变的子对象上调用 clone 来修补默认的 clone 方法

  3. 是否不改使用 clone

实际上第 3 个选项是默认选项。如果选择第 1 项或第 2 项,类必须:

  1. 实现 Cloneable 接口

  2. 重新定义 clone 方法,并指定 public 访问修饰符

Tips:

Object 类中的 clone 方法声明为 protected,子类只能调用受保护的 clone 方法来克隆自己的对象。必须重新定义 clone 为 public 才能允许所有方法克隆对象。

Cloneable 接口没有指定 clone 方法,这个方法是从 Object 类继承的。这个接口指示作为一个标记,指示类设计者了解克隆过程。标记接口不包含任何方法,它的唯一作用就是允许在类型查询中使用 instanceof。

即使 clone 的默认(浅拷贝)实现能满足要求,还是需要实现 Cloneable 接口,将 clone 重新定义为 public,再调用 super.clone()。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n74" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class Employee implements Cloneable{
public Employee clone() throws CloneNotSupportedException{
return (Employee) super.clone();
}
...
}</pre>

与 Object.clone 提供的浅拷贝相比,这个 clone 方法并没有为它添加任何工嗯呢该。只是让这个方法是公有的。要建立深拷贝,还需要做很多工作,克隆对象中可变的实例域。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n76" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class Employee implements Cloneable{
public Employee clone() throws CloneNotSupportedException{
Employee cloned = (Employee) super.clone();
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
...
}</pre>

如果在一个对象上调用 clone,但这个对象的类并没有实现 Cloneable 接口,Object 类的 clone 方法就会抛出一个 CloneNotSupportedException。

捕捉这个异常是不是更好一些?这非常适合 final 类。否则,还是保留 throws 说明符。这样就允许子类在不支持克隆时选择抛出一个 CloneNotSupportedException。

要不要在自己的类中实现 clone 呢?如果你的客户端需要建立深拷贝,可能就需要实现这个方法。

Tips:

所有数组类型都有一个 public 的 clone 方法,而不是 protected。可以用这个方法建立一个新数据,包含原数组所有元素的副本。

作者:ZZUGeneral
地址:http://www.yanghelong.top

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容