Java默认方法用法


13

几十年来,它一直是接口那样的话 (只)指定的方法签名。我们被告知这是“正确的做事方式™”。

然后Java 8出来并说:

好吧,嗯,现在您可以定义默认方法了。再见,再见。

我对有经验的Java开发人员和最近(过去几年)开始开发它的人如何消化它感到好奇。我也想知道这如何适合Java正统和实践。

我正在构建一些实验代码,并且在进行一些重构时,最终得到了一个接口,该接口简单地扩展了标准接口(Iterable)并添加了两个默认方法。老实说,我对此感到非常好。

我知道这有点开放,但是现在Java 8已在实际项目中使用了一段时间,围绕默认方法的使用是否存在正统观念?在讨论它们时,我最常看到的是关于如何在不破坏现有使用者的情况下向接口添加新方法。但是像我上面给出的示例一样从一开始就使用它。是否有人在其界面中提供实现时遇到任何问题?


我也会对此观点感兴趣。在.Net世界中工作了6年后,我将重返Java。在我看来,这可能是Java对C#扩展方法的回答,而Ruby的模块方法对此产生了一些影响。我没有玩过,所以不能确定。
Berin Loritsch

1
我觉得他们之所以添加默认方法,很大程度上是因为它们可以扩展集合接口而不必创建完全不同的接口
Justin

1
@Justin:java.util.function.Function有关在全新界面中使用默认方法的信息,请参见。
约尔格W¯¯米塔格

@Justin我的猜测是,这是主要驱动程序。我真的应该开始再次关注该过程,因为他们开始真正进行更改。
JimmyJames

Answers:


12

一个很好的用例是我所谓的“杠杆”接口:这些接口只有少量的抽象方法(理想情况下为1),但是却提供了很多“杠杆”,因为它们为您提供了许多功能:仅需要在您的类中实现1个方法,但“免费”获得许多其他方法。想的集合接口,例如,具有单个抽象foreach方法和default方法,如mapfoldreducefilterpartitiongroupBysortsortBy,等。

这里有几个例子。让我们从开始java.util.function.Function<T, R>。它只有一个抽象方法R apply<T>。它具有两个默认方法,使您可以在之前或之后以两种不同的方式将函数与另一个函数组成。这两种组合方法都是使用以下方法实现的apply

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    return (V v) -> apply(before.apply(v));
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    return (T t) -> after.apply(apply(t));
}

您还可以为类似对象创建一个接口,如下所示:

interface MyComparable<T extends MyComparable<T>> {
  int compareTo(T other);

  default boolean lessThanOrEqual(T other) {
    return compareTo(other) <= 0;
  }

  default boolean lessThan(T other) {
    return compareTo(other) < 0;
  }

  default boolean greaterThanOrEqual(T other) {
    return compareTo(other) >= 0;
  }

  default boolean greaterThan(T other) {
    return compareTo(other) > 0;
  }

  default boolean isBetween(T min, T max) {
    return greaterThanOrEqual(min) && lessThanOrEqual(max);
  }

  default T clamp(T min, T max) {
    if (lessThan(   min)) return min;
    if (greaterThan(max)) return max;
                          return (T)this;
  }
}

class CaseInsensitiveString implements MyComparable<CaseInsensitiveString> {
  CaseInsensitiveString(String s) { this.s = s; }
  private String s;

  @Override public int compareTo(CaseInsensitiveString other) {
    return s.toLowerCase().compareTo(other.s.toLowerCase());
  }
}

或者是一个极其简化的集合框架,其中所有集合操作都将返回Collection,无论原始类型是什么:

interface MyCollection<T> {
  void forEach(java.util.function.Consumer<? super T> f);

  default <R> java.util.Collection<R> map(java.util.function.Function<? super T, ? extends R> f) {
    java.util.Collection<R> l = new java.util.ArrayList();
    forEach(el -> l.add(f.apply(el)));
    return l;
  }
}

class MyArray<T> implements MyCollection<T> {
  private T[] array;

  MyArray(T[] array) { this.array = array; }

  @Override public void forEach(java.util.function.Consumer<? super T> f) {
    for (T el : array) f.accept(el);
  }

  @Override public String toString() {
    StringBuilder sb = new StringBuilder("(");
    map(el -> el.toString()).forEach(s -> { sb.append(s); sb.append(", "); } );
    sb.replace(sb.length() - 2, sb.length(), ")");
    return sb.toString();
  }

  public static void main(String... args) {
    MyArray<Integer> array = new MyArray<>(new Integer[] {1, 2, 3, 4});
    System.out.println(array);
    // (1, 2, 3, 4)
  }
}

与lambda结合使用时,这变得非常有趣,因为这样的“杠杆”接口可以由lambda(它是SAM接口)实现。

这与在C♯中添加扩展方法的用例相同,但是默认方法具有一个明显的优势:它们是“适当的”实例方法,这意味着它们可以访问接口的私有实现详细信息(private接口方法即将到来)在Java 9中),而扩展方法只是静态方法的语法糖。

如果Java获得接口注入,它也将允许类型安全的,作用域化的,模块化的猴子修补程序。对于JVM上的语言实现者来说,这将是非常有趣的:例如,目前,JRuby继承自Java类或包装Java类以为其提供其他Ruby语义,但是理想情况下,他们希望使用相同的类。随着接口注入和默认的方法,他们可以注入例如RubyObject界面进入java.lang.Object,这样一个Java Object和Ruby的Object同样的事情


1
我没有完全遵循。必须根据接口上的其他方法或对象中定义的方法来定义接口上的默认方法。您能否举一个示例,说明如何使用默认方法创建有意义的单一方法接口?如果您需要Java 9语法进行演示,那很好。
JimmyJames

例如:一个Comparable带有一个抽象接口compareTo方法,以及默认lessThanlessThanOrEqualgreaterThangreaterThanOrEqualisBetween,和clamp方法中,所有在以下方面实现的compareTo。或者,看看java.util.function.Function:它有一个抽象apply方法和两个默认的合成方法,都用来实现apply。我尝试给出一个Collection接口的示例,但是要使所有这些都成为类型安全的是棘手的,而且对于这个答案来说太长了–我将尝试给出一个非类型安全,不保留类型的版本。敬请关注。
约尔格W¯¯米塔格

3
这些示例有帮助。谢谢。我误解了单方法接口的含义。
JimmyJames

什么默认的方法意思是,一个单一的抽象方法的接口不再是一个单一的方法接口;-)
约尔格W¯¯米塔格

我在考虑这个问题,我想到AbstractCollection和AbstractList基本上就是您在这里谈论的内容(2种方法,而不是1种,但我认为这不是至关重要的。)如果将它们重铸为带有defualt方法的接口,它将如果您可以索引并知道大小,那么通过增加大小并从任何内容创建列表来将一个可迭代对象转换为一个集合非常简单。
JimmyJames
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.