【译】java8之接口的默认静态方法

众所周知,我们应该使用接口编程,接口使得在交互时不需要关注具体的实现细节,从而保持程序的松散耦合。在API的设计中,设计简约而清晰的接口非常重要。被称作固定定律的接口分离定律,其中有一条就讲到了应该设计更小的特定客户端接口而不是一个通用目的的接口。良好的接口设计是让应用程序和库的API保持简洁高效的关键。

如果你曾有过接口API设计的经验,那么有时候你会感觉到为API增加方法的必要。但是,如果API一旦发布了,几乎无法在保证已接口类的代码不变的情况下来增加新的方法。举个例子,假设你设计了一个简单的API Calculator,里面有add、subtract、devide和multiply函数。我们可以如下申明。简便起见,我们使用int类型。

public interface Calculator {

  int add(int first, int second);

  int subtract(int first, int second);

  int divide(int number, int divisor);

  int multiply(int first, int second);
}

为了实现Calculator这个接口,需要写如下一个BasicCalculator类

public class BasicCalculator implements Calculator {

  @Override
  public int add(int first, int second) {
    return first + second;
  }

  @Override
  public int subtract(int first, int second) {
    return first - second;
  }

  @Override
  public int divide(int number, int divisor) {
    if (divisor == 0) {
      throw new IllegalArgumentException("divisor can't be           zero.");
    }
    return number / divisor;
  }

  @Override
  public int multiply(int first, int second) {
    return first * second;
  }
}

静态工厂方法

乍一看,Calculator这个API还是非常简单实用,其他开发者只需要创建一个BasicCalculator就可以使用这个API。比如下面这两段代码:

Calculator calculator = new BasicCalculator();
int sum = calculator.add(1, 2);

BasicCalculator cal = new BasicCalculator();
int difference = cal.subtract(3, 2);

然而,事实上给人的感觉却是此API的用户并不是面向这个接口进行编程,而是面向这个接口的实现类在编程。因为你的API并没有强制要求用户把BasicCalculator当着一个public类来对接口进行编程,如果你把BasicCalculator类变成protected类型你就需要提供一个静态的工厂类来谨慎的提供Calculator的实现,代码如下:

class BasicCalculator implements Calculator {
  // rest remains same
}

接下来,写一个工厂类来提供Calculator的实例,代码如下:

public abstract class CalculatorFactory {

  public static Calculator getInstance() {
  return new BasicCalculator();
  }
}

这样,用户就被强制要求对Calculator接口进行编程,并且不需要关注接口的详细实现。

尽管我们通过上面的方式达到了我们想要的效果,但是我们却因增加了一个新的CalculaorFactory类而增加了API的表现层次。如果其他开发者要有效的利用这个Calculator API,就必须要先学会一个新的类的使用。但在java8之前,这是唯一的实现方式。

java8允许用户在接口中定义静态的方法,允许API的设计者在接口的内部定义类似getInstance的静态工具方法,从而保持API简洁。静态的接口方法可以用来替代我们平时专为某一类型写的helper类。比如,Collections类是用来为Collection和相关接口提供各种helper方法的一个类。Collections类中定义的方法应该直接添加到Collection及其它相关子接口上。

下面的代码就是Java8中直接在Calculator接口中添加静态getInstance方法的例证:

public interface Calculator {

  static Calculator getInstance() {
  return new BasicCalculator();
  }

  int add(int first, int second);

  int subtract(int first, int second);

  int divide(int number, int divisor);

  int multiply(int first, int second);

}

随着时间来演进API

有些用户打算通过新建一个Calculator子接口并在子接口中添加新方法或是通过重写一个继承自Calculator接口的实现类来增加类似remainder的方法。与他们沟通之后你会发现,他们可能仅仅是想向Calculator接口中添加一个类似remainder的方法。感觉这仅仅是一个非常简单的API变化,所以你加入了一个方法,代码如下:
public interface Calculator {

  static Calculator getInstance() {
    return new BasicCalculator();
  }

  int add(int first, int second);

  int subtract(int first, int second);

  int divide(int number, int divisor);

  int multiply(int first, int second);

  int remainder(int number, int divisor); // new method added to API
}

在接口中增加方法会破坏代码的兼容性,这意味着那些实现Calculator接口的用户需要为remainder方法添加实现,否则,代码无法编译通过。这对API设计者来说非常麻烦,因为它让代码演进变得非常困难。Java8之前,开发者不能直接在API中实现方法,这也使得在一个API中添加一个或者多个接口定义变得十分困难。

为了让API的演进变得更加容易,Java8允许用户对定义在接口中的方法提供默认的实现。这就是通常的default或者defender方法。接口的实现类不需要再提供接口的实现方法。如果接口的实现类提供了方法,那么实现类的方法会被调用,否则接口中的方法会被调用。List接口有几个定义在接口中的默认方法,比如replaceAll,sort和splitIterator。

default void replaceAll(UnaryOperator<E> operator) {
  Objects.requireNonNull(operator);
  final ListIterator<E> li = this.listIterator();
  while (li.hasNext()) {
    li.set(operator.apply(li.next()));
  }
}

我们可以向下面的代码一样定义一个默认的方法来解决API的问题。默认方法通常用在使用那些已经存在的方法,比如remainder是用来定义使用subtract,multiply和divice的一个方法。

default int remainder(int number, int divisor) {
  return subtract(number, multiply(divisor, divide(number, divisor)));
}

多重继承

一个类只能继承自一个父类,但可以实现多个接口。既然在接口中实现方法是可行的,接口方法的多实现也是可行的。之前,Java已经在类型上支持多重继承,如今也支持在表现阶段的多重继承。下面这三条规则决定了哪个方法会被调用。

规则一:定义在类中的方法优先于定义在接口的方法:

interface A {
  default void doSth(){
  System.out.println("inside A");
  }
}
class App implements A{

  @Override
  public void doSth() {
    System.out.println("inside App");
  }

  public static void main(String[] args) {
    new App().doSth();
  }
}

运行结果:inside App。调用的是在类中定义的方法。

规则二:否则,调用定制最深的接口中的方法。

interface A {
  default void doSth() {
    System.out.println("inside A");
  }
}
interface B {}
interface C extends A {
  default void doSth() {
    System.out.println("inside C");
  } 
}
class App implements C, B, A {

  public static void main(String[] args) {
    new App().doSth();
  }
}

运行结果:inside C

规则三:否则,直接调用指定接口的实现方法

interface A {
  default void doSth() {
    System.out.println("inside A");
  }
}
interface B {
  default void doSth() {
    System.out.println("inside B");
  }
}
class App implements B, A {

  @Override
  public void doSth() {
    B.super.doSth();
  }

  public static void main(String[] args) {
      new App().doSth();
  }
}

运行结果: inside B

水平有限,欢迎大家指点和建议,-

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,522评论 25 707
  • Streams 原文链接: Streams 原文作者: shekhargulati 译者: leege100 状态...
    忽来阅读 5,516评论 3 32
  • 这两天经历了一些事情 做了一些决定 补了考 退了部 也不再为那些琐碎的事情所羁绊 曾经也想过大学四年都不要补考 但...
    张空空阅读 241评论 0 0
  • 《阿房宫赋》是晚唐著名诗人杜牧创作的一篇散文,遣词用字无比华美,思想深刻见骨,是脍炙人口的经典古文之一,下面我们一...
    wv橙子阅读 618评论 0 1
  • 《理想国》解读的第一期,将首先介绍《理想国》的创作背景,以及柏拉图的政治实践与这部作品的关系;随后进入这部作品本身...
    赵清炳阅读 695评论 0 0