Java默认接口方法具体用例

时间:2017-04-27 15:01:51

标签: design-patterns interface java-8 java-9 default-method

Java 9即将推出,更多功能将添加到Java接口,如私有方法。接口中的default方法在Java 8中添加,基本上添加到support the use of lambdas inside collections,而不会破坏与以前版本语言的复古兼容性。

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

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

5 个答案:

答案 0 :(得分:8)

Brian Goetz和我在JavaOne 2015演讲中讨论了其中的一些内容, API设计与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.reversedSpliterator.getExactSizeIfKnownSpliterator.hasCharacteristics。请注意,{8}是在Java 8中引入的,包括默认方法,所以这显然不是接口演化的例子。

简单实施,意图被覆盖

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

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

现在,显然{8}已在Java 8中sortList进行了改进,因此这种演变并非如此。但是您可以很容易地想象出一个新的ArrayList实现。在您正确实施基础知识的同时,您最初可能会继承List默认实施。稍后,您可以考虑实施一个自定义排序算法,该算法已针对您的新实施的内部数据组织进行调整。

答案 1 :(得分:6)

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

sql> @TEST.sql

样本用法:

@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);
    }
}

您可以使用默认方法实现其他参数的类似绑定方法,例如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 bindSecond

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

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

bindThird

样品:

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();
    }
}

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

另一种用法是特征:

Frog frog = new Frog(29);

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

frog.liveOneDay();
String habitatWhenOld = frog.getHabitat(); // ground

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

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
    }
}

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

答案 2 :(得分:5)

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

GeocodingResult[] result

该结果包含我需要的一些信息,例如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
}

现在在接口的实现中我只使用这种方法。

 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

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

答案 3 :(得分:4)

使用默认方法

装饰函数接口链接

我想有时链接@FunctionalInterface,我们已经在Function中看到了具有链接函数的默认方法,例如:composeandThen以使代码更强优雅即可。最重要的是我们以后可以重用部分功能,例如:

Predicate<?> isManager = null;
Predicate<?> isMarried = null;

marriedManager = employeeStream().filter(isMarried.and(isManager));
unmarriedManager = employeeStream().filter(isMarried.negate().and(isManager));

但是,有时我们无法链接@FunctionalInterface,因为它没有提供任何链式方法。但我可以编写另一个@FunctionalInterface扩展原始的并添加一些默认方法用于链接目的。例如:

when(myMock.myFunction(anyString()))
       .then(will(returnsFirstArg()).as(String.class).to(MyObject::new));

这是我昨天的回答:Mockito returnsFirstArg() to use。由于Answer没有链方法,因此我引入了另一个Answer类型AnswerPipeline来提供链方法。

AnswerPipeline类

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));
    }
}

答案 4 :(得分:4)

使用默认方法

删除监听器的默认适配器

有时,我们需要为需要触发多个事件的Adapter引入默认 java.util.EventListener类,但我们只对某些事件感兴趣。例如:swing为每个*Adapter创建每个*Listener类。

我最近发现当我们使用默认方法声明侦听器时,这非常有用,我们可以删除中间适配器类。例如:

interface WindowListener extends EventListener {

    default void windowOpened(WindowEvent e) {/**/}

    default void windowClosing(WindowEvent e) {/**/}

    default void windowClosed(WindowEvent e) {/**/}
}