Java默认接口方法具体用例

ric*_*din 10 design-patterns interface java-8 default-method java-9

Java 9即将推出,更多功能将添加到Java接口,如私有方法.default接口中的方法是在Java 8中添加的,主要是为了支持在集合中使用lambda,而不会破坏与该语言的早期版本的复古兼容性.

在Scala中,traits中的方法非常有用.但是,Scala使用不同的方法处理traits而不是Java default.考虑多重继承解决方案或使用traits作为mixin.

除了以上使用,哪些是使用default方法的真实场景值得?这几年是否出现了一些使用它们的模式?使用这种方法可以解决哪些问题?

Stu*_*rks 9

Brian Goetz和我在JavaOne 2015讲座,Java设计与Java 8 Lambda和Streams上讨论了其中的一些内容.尽管有标题,但最后还是有一些关于默认方法的材料.

幻灯片:https://stuartmarks.files.wordpress.com/2015/10/con6851-api-design-v2.pdf

视频:https://youtu.be/o10ETyiNIsM?t = 24m

我将在这里总结一下我们对默认方法的看法.

接口演变

默认方法的主要用例是接口进化.主要是,这是在不破坏向后兼容性的情况下向接口添加方法的能力.正如问题所述,这是最突出的用于添加允许将集合转换为Streams并将基于lambda的API添加到集合的方法.

但是,还有其他一些用例.

可选方法

有时接口方法在逻辑上是"可选的".例如,考虑对不可变集合的mutator方法.当然,需要实现,但通常在这种情况下它会做的是抛出异常.这可以通过默认方法轻松完成.如果实现不想提供它们,它们可以继承抛出异常的方法,或者如果它们想要提供实现,它们可以覆盖它.示例:Iterator.remove.

便利方法

有时为方便调用者提供方法,并且有一个明显和最佳的实现.可以通过默认方法提供此实现.实现覆盖默认值是合法的,但通常没有理由,因此实现通常会继承它.例如:Comparator.reversed,Spliterator.getExactSizeIfKnown,Spliterator.hasCharacteristics.请注意,这Spliterator是在Java 8中引入的,包括默认方法,因此这显然不是接口演变的情况.

简单实现,意图被覆盖

默认方法可以提供适用于所有实现的简单通用实现,但这可能不是最理想的.这有助于在初始启动期间实现,因为它们可以继承默认值并确保正确操作.但是,从长远来看,实现可能希望覆盖默认值并提供改进的自定义实现.

示例:List.sort.默认实现将列表元素复制到临时数组,对数组进行排序,并将元素复制回列表.这是一个正确的实现,有时它无法改进(例如for LinkedList).但是,对其内部数组进行ArrayList替换sort和排序.这避免了复制开销.

现在,显然sort是在Java 8 上ListArrayList在Java 8中进行了改进,所以这种演变并没有发生.但是你很容易想象出一个新的List实现.sort在正确实现基础知识的同时,您最初可能会继承默认实现.稍后,您可以考虑实施一个针对新实现的内部数据组织的自定义排序算法.


Fed*_*ner 7

首先想到的是使用默认方法来支持一些函数式编程技术:

@FunctionalInterface
public interface Function3<A, B, C, D> {

    D apply(A a, B b, C c);

    default Function<A, Function<B, Function<C, D>>> curry() {
        return a -> b -> c -> this.apply(a, b, c);
    }

    default Function<B, Function<C, D>> bindFirst(A a) {
        return b -> c -> this.apply(a, b, c);
    }
}
Run Code Online (Sandbox Code Playgroud)

样品用法:

Function3<Long, Long, Long, Long> sum = (a, b, c) -> a + b + c;
long result = sum.apply(1L, 2L, 3L); // 6

Function<Long, Function<Long, Function<Long, Long>>> curriedSum = sum.curry();
result = curriedSum.apply(1L).apply(2L).apply(3L); // 6

Function<Long, Function<Long, Long>> incr = sum.bindFirst(1L);
result = incr.apply(7L).apply(3L); // 11
result = incr.apply(6L).apply(7L); // 14
Run Code Online (Sandbox Code Playgroud)

您可以为其他参数使用类似的绑定方法,使用默认方法实现,例如bindSecondbindThird.

你可以使用默认方法来装饰父接口(正如@ holi-java在他的回答中解释的那样),还有很多适配器模式的例子(currying和binding实际上是适配器).


除了函数式编程,你可以使用默认的方法来支持那种,有限的多重继承:

public interface Animal {

    String getHabitat();
}

public interface AquaticAnimal extends Animal {

    @Override
    default String getHabitat() {
        return "water";
    }
}

public interface LandAnimal extends Animal {

    @Override
    default String getHabitat() {
        return "ground";
    }
}

public class Frog implements AquaticAnimal, LandAnimal {

    private int ageInDays;

    public Frog(int ageInDays) {
        this.ageInDays = ageInDays;
    }

    public void liveOneDay() {
        this.ageInDays++;
    }

    @Override
    public String getHabitat() {
        if (this.ageInDays < 30) { // is it a tadpole?
            return AquaticAnimal.super.getHabitat();
        } // else
        return LandAnimal.super.getHabitat();
    }
}
Run Code Online (Sandbox Code Playgroud)

样品:

Frog frog = new Frog(29);

String habitatWhenYoung = frog.getHabitat(); // water

frog.liveOneDay();
String habitatWhenOld = frog.getHabitat(); // ground
Run Code Online (Sandbox Code Playgroud)

也许不是最好的例子,但你明白了......


另一种用法是特征:

public interface WithLog {

    default Logger logger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}

public interface WithMetrics {

    default MetricsService metrics() {
        return MetricsServiceFactory.getMetricsService(
            Configuration.getMetricsIP(
                Environment.getActiveEnv())); // DEV or PROD
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,只要您有一个需要记录某些内容并报告某些指标的类,您就可以使用:

public class YourClass implements WithLog, WithMetrics {

    public void someLongMethod() {

        this.logger().info("Starting long method execution...");

        long start = System.nanoTime();

        // do some very long action

        long end = System.nanoTime();

        this.logger().info("Finished long method execution");

        this.metrics().reportExecutionTime("Long method: ", end - start);
    }
}
Run Code Online (Sandbox Code Playgroud)

同样,这不是最好的实现,只是示例代码,以查看如何通过默认方法使用特征.


Eug*_*ene 5

好吧,我有一个真实的世界场景,我已经使用过它们.以下是上下文:我以结果的形式得到google maps api(通过提供纬度和经度)Array的结果,如下所示:

GeocodingResult[] result
Run Code Online (Sandbox Code Playgroud)

这一结果包含了一些信息,我需要,喜欢zip-codelocalitycountry.不同的服务需要该响应的不同部分.解析该数组是相同的 - 您只需要搜索不同的部分.

所以我在一个default方法里面定义了interface:

default Optional<String> parseResult(
        GeocodingResult[] geocodingResults, 
        AddressComponentType componentType,// enum
        AddressType addressType) { // enum

     ... Some parsing functionality that returns
      city, address or zip-code, etc
}
Run Code Online (Sandbox Code Playgroud)

现在在接口的实现中我只使用这个方法.

 class Example implements Interface {

      @Override
      public Optional<String> findZipCode(Double latitude, Double longitude) {
         LatLng latLng = new LatLng(latitude, longitude);
         return parseResult(latLng, 
             AddressComponentType.POSTAL_CODE, 
             AddressType.POSTAL_CODE);
      }


    .. other methods that use the same technique
Run Code Online (Sandbox Code Playgroud)

曾经是通过抽象类完成的.我可以使用私有方法,但许多其他服务使用此接口.