Java default interface methods concrete use cases
Java 9即将到来,更多的特性将被添加到Java接口,比如私有方法。在Java 8中添加了接口中的EDCOX1和0种方法,本质上是为了支持LAMBDAS在集合内的使用,而不破坏与以前版本的语言的反向兼容性。
在scala中,
除上述用途外,使用
Brian Goetz和我在JavaOne 2015 Talk,API设计中用Java 8 lambda和流覆盖了一些。尽管有这个标题,但在最后还有一些关于默认方法的材料。
幻灯片:https://stuartmarks.files.wordpress.com/2015/10/con6851-api-design-v2.pdf
视频:https://youtu.be/o10etynism?t=24m
我将在这里总结一下我们所说的默认方法。
界面演化
默认方法的主要用例是接口演进。主要地,这是在不破坏向后兼容性的情况下向接口添加方法的能力。正如问题中提到的,这是最显著的用于添加方法,允许将集合转换为流,并将基于lambda的API添加到集合。
不过,还有其他几个用例。
可选方法
有时接口方法在逻辑上是"可选的"。例如,考虑对不可变集合使用mutator方法。当然,实现是必需的,但在这种情况下,它通常要做的是抛出一个异常。这可以用默认方法轻松完成。如果不想提供异常引发方法,则实现可以继承该方法;如果想提供实现,则实现可以重写该方法。示例:
方便方法
有时,为了方便调用者,提供了一种方法,并且有一个明显的最佳实现。此实现可以由默认方法提供。实现重写默认值是合法的,但通常没有理由,因此实现通常会继承它。示例:
简单实现,旨在重写
默认方法可以提供一个简单、通用的实现,适用于所有实现,但这可能是次优的。这有助于在初始启动期间实现,因为它们可以继承默认值并确保正确操作。但是,从长远来看,实现可能希望覆盖默认值,并提供一个改进的、定制的实现。
示例:
显然,
首先想到的是使用默认方法来支持一些函数式编程技术:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @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); } } |
样品使用情况:
1 2 3 4 5 6 7 8 9 | 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 |
您可以对其他参数使用类似的绑定方法,使用默认方法实现,如
您可以使用默认方法来装饰父接口(如在他的回答中解释的"HORIJAVA"),也有许多适配器模式的示例(Currand and Band实际上是适配器)。
除了函数式编程,您还可以使用默认方法来支持某种有限的多重继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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(); } } |
Sample:
1 2 3 4 5 6 | Frog frog = new Frog(29); String habitatWhenYoung = frog.getHabitat(); // water frog.liveOneDay(); String habitatWhenOld = frog.getHabitat(); // ground |
也许不是最好的例子,但你知道…
另一种用法是特征:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 } } |
现在,只要您有一个类需要记录一些东西并报告一些度量,就可以使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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); } } |
同样,这不是最好的实现,只是示例代码,看看如何通过默认方法使用特性。
好吧,我有一个真实的场景,在其中我使用了它们。上下文如下:我从
1 | GeocodingResult[] result |
这个结果包含了一些我需要的信息,比如
所以我在
1 2 3 4 5 6 7 8 | default Optional<String> parseResult( GeocodingResult[] geocodingResults, AddressComponentType componentType,// enum AddressType addressType) { // enum ... Some parsing functionality that returns city, address or zip-code, etc } |
现在在接口的实现中,我只使用这个方法。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
这以前是通过抽象类完成的。我本可以使用私有方法,但这个接口被许多其他服务使用。
使用默认方法删除侦听器的默认适配器
有时,我们需要为
我最近发现,当我们用默认方法声明侦听器时,这非常有用,我们可以删除中间适配器类。例如:
1 2 3 4 5 6 7 8 | interface WindowListener extends EventListener { default void windowOpened(WindowEvent e) {/**/} default void windowClosing(WindowEvent e) {/**/} default void windowClosed(WindowEvent e) {/**/} } |
用默认方法修饰函数接口链接
我有时想链接@functionalinterface,我们已经在函数中看到了链接函数的默认方法,例如:
1 2 3 4 5 | Predicate<?> isManager = null; Predicate<?> isMarried = null; marriedManager = employeeStream().filter(isMarried.and(isManager)); unmarriedManager = employeeStream().filter(isMarried.negate().and(isManager)); |
但是,有时我们不能链接@functionalinterface,因为它没有提供任何链方法。但我可以编写另一个@functionalinterface来扩展原始的方法,并添加一些默认方法来链接。例如:
1 2 | when(myMock.myFunction(anyString())) .then(will(returnsFirstArg()).as(String.class).to(MyObject::new)); |
这是我昨天的答案:mockito returnsfirstarg()使用。由于
AnswerPipeline类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | interface AnswerPipeline<T> extends Answer<T> { static <R> AnswerPipeline<R> will(Answer<R> answer) { return answer::answer; } default <R> AnswerPipeline<R> as(Class<R> type) { return to(type::cast); } default <R> AnswerPipeline<R> to(Function<T, R> mapper) { return it -> mapper.apply(answer(it)); } } |