【问题标题】:Using (empty) default method to make FunctionalInterface使用(空)默认方法制作功能接口
【发布时间】:2018-04-02 01:53:03
【问题描述】:

在 Java 8 中,引入了接口的默认方法,以便在不破坏向后兼容性的情况下向现有接口添加方法。

由于默认方法是非抽象的,它们可用于创建具有多个可覆盖方法的FunctionalInterface

比如说,StringTransformer 接口有两个方法,transform,将给定的Stringend 转换为释放资源:

interface StringTransformer {
    String transform(String s);

    void end();
}

但是有些实现可能没有资源可以释放,所以我们可以为end提供空的默认方法,并为StringTransformer使用lambda函数和方法参考:

interface StringTransformer {
    String transform(String s);

    default void end() {
    }
}

StringTransformer x = String::trim;
StringTransformer y = (x -> x + x);

这是一种有效/最佳做法,还是一种反模式和滥用默认方法?

【问题讨论】:

  • 意见:这是对默认方法的滥用。 --- 另外,如果你想要一个清理资源的方法,你应该实现AutoCloseable及其void close()方法,允许调用者使用try-with-resources
  • 您可能想阅读this answer,了解它为何滥用默认方法。摘要:Lambda 是函数,而不是对象。
  • @Andreas 在我看来,这是默认方法的一个非常传统的用例。您可能会争辩说,将本质上是抽象类的东西实例化为 lambda 是不合适的,但即使这样也比您的链接有点飞跃。

标签: java interface java-8 functional-interface


【解决方案1】:

正如this answer 中所说,允许创建具有多个方法的接口仍然是功能接口,这是默认方法的目的之一。正如那里也提到的,您会在 Java API 本身中找到示例,例如 ComparatorPredicateFunction,具有 default 方法并有意成为函数式接口。

default 实现是否什么都不做并不重要,更重要的问题是,这个默认实现有多自然。只是为了使 lambdas 成为可能,这感觉像是一个杂物,还是确实是某些甚至大多数实现会以任何方式使用的东西(不管它们是如何实现的)?

不需要特殊的清理操作可能确实是一种常见情况,即使您遵循评论中的建议,让您的界面扩展AutoCloseable 并将方法命名为close 而不是end。请注意,同样,Stream 实现了AutoCloseable,其默认行为是对close() 不执行任何操作。您甚至可以按照该模式将清理操作指定为单独的Runnable,类似于Stream.onClose(Runnable)

public interface StringTransformer extends UnaryOperator<String>, AutoCloseable {
    static StringTransformer transformer(Function<String,String> f) {
        return f::apply;
    }
    String transform(String s);
    @Override default String apply(String s) { return transform(s); }
    @Override default void close() {}
    default StringTransformer onClose(Runnable r) {
        return new StringTransformer() {
            @Override public String transform(String s) {
                return StringTransformer.this.transform(s);
            }
            @Override public void close() {
                try(StringTransformer.this) { r.run(); }
            }
        };
    }
}

这允许通过onClose 注册清理操作,因此以下工作:

try(StringTransformer t = 
        StringTransformer.transformer(String::toUpperCase)
                         .onClose(()->System.out.println("close"))) {
    System.out.println(t.apply("some text"));
}

分别

try(StringTransformer t = transformer(String::toUpperCase)
                         .onClose(()->System.out.println("close 1"))) {
    System.out.println(t.apply("some text"));
}

如果您使用import static。如果您链接多个操作,例如

,它还可以确保安全关闭
try(StringTransformer t = transformer(String::toUpperCase)
                         .onClose(()->System.out.println("close 1"))
                         .onClose(()->{ throw new IllegalStateException(); })) {
    System.out.println(t.apply("some text"));
}

try(StringTransformer t = transformer(String::toUpperCase)
                         .onClose(()->{ throw new IllegalStateException("outer fail"); })
                         .onClose(()->{ throw new IllegalStateException("inner fail"); })){
    System.out.println(t.apply("some text"));
}

请注意,try(StringTransformer.this) { r.run(); } 是 Java 9 语法。对于 Java 8,您需要 try(StringTransformer toClose = StringTransformer.this) { r.run(); }

【讨论】:

    【解决方案2】:

    理论上没有问题。您可以使用这种类型的接口。 我想从你需要这种构造的条件开始。

    后向能力

    正如您所提到的,引入了默认方法来保持向后功能,因此使用默认方法来使接口向后兼容是合理的。

    可选实施

    默认的空方法可用于使任何方法的实现成为可选的。但是,您可以使用默认实现创建此类接口的抽象适配器类。这种策略已经使用了很长时间,它优于接口中的默认方法,就像抽象类一样,我们可以定义复杂的默认实现。此外,我们可以从一个简单的空方法开始,然后将其更改为具有复杂的默认实现,而对于接口中的默认方法,这是不可能的,因为类的某些功能不可用。

    此外,Java 8 还引入了FunctionalInterface 将任何接口标记为函数式接口。根据官方指南,如果接口具有一种方法但未使用FunctionalInterface 注释,则应避免使用 lambda 和方法引用。

    【讨论】:

      【解决方案3】:

      在我看来,将可以作为数据类型引用的常规接口转换为函数式接口并不是一个好主意。

      如果您这样做,那么用户可以在他们实际需要此接口作为类型的地方以及他们创建 lambda 的地方实现 StringTransform。这会降低可读性和可维护性。

      【讨论】:

      • 功能接口常规接口。它没有什么不规则的。 “他们实际上需要这个接口作为一种类型”是什么意思?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多