编写好的API很难。 非常辛苦。 如果您想让用户喜欢您的API,则必须考虑很多事情。 您必须在以下两者之间找到适当的平衡:
- 有用性
- 易用性
- 向后兼容
- 前向兼容性
之前,在我们的文章: 如何设计良好的常规API中,我们已经就此主题进行过博客讨论。 今天,我们将研究如何…
Java 8更改规则
是!
重载是一个很好的工具,可以在两个方面提供便利:
- 通过提供参数类型替代
- 通过提供参数默认值
来自JDK的上述示例包括:
public class Arrays {// Argument type alternativespublic static void sort(int[] a) { ... }public static void sort(long[] a) { ... }// Argument default valuespublic static IntStream stream(int[] array) { ... }public static IntStream stream(int[] array, int startInclusive, int endExclusive) { ... }
}
jOOQ API显然充满了这种便利。 由于jOOQ是SQL的DSL ,我们甚至可能会滥用一点:
public interface DSLContext {<T1> SelectSelectStep<Record1<T1>> select(SelectField<T1> field1);<T1, T2> SelectSelectStep<Record2<T1, T2>> select(SelectField<T1> field1, SelectField<T2> field2);<T1, T2, T3> SelectSelectStep<Record3<T1, T2, T3>> sselect(SelectField<T1> field1, SelectField<T2> field2, SelectField<T3> field3);<T1, T2, T3, T4> SelectSelectStep<Record4<T1, T2, T3, T4>> select(SelectField<T1> field1, SelectField<T2> field2, SelectField<T3> field3, SelectField<T4> field4);// and so on...
}
诸如Ceylon之类的语言通过声称以上内容是在Java中使用重载的唯一合理原因,将便利性这一概念进一步提高了。 因此,锡兰(Ceylon)的创建者已完全消除了其语言中的重载,将以上内容替换为联合类型和参数的实际默认值。 例如
// Union types
void sort(int[]|long[] a) { ... }// Default argument values
IntStream stream(int[] array,int startInclusive = 0,int endInclusive = array.length) { ... }
阅读“我希望我们在Java中拥有的十大锡兰语言功能” ,以获取有关锡兰的更多信息。
不幸的是,在Java中,我们不能使用联合类型或参数默认值。 因此,我们必须使用重载为API使用者提供便捷的方法。
但是,如果您的方法参数是一个函数接口 ,则在方法重载方面,Java 7和Java 8之间的情况发生了巨大变化。 JavaFX在此提供了一个示例。
JavaFX的“不友好”的ObservableList
JavaFX通过使它们“可观察”来增强JDK集合类型。 不要与Observable
混淆, Observable
是JDK 1.0和Swing之前的恐龙类型。
JavaFX自己的Observable
本质上是这样的:
public interface Observable {void addListener(InvalidationListener listener);void removeListener(InvalidationListener listener);
}
幸运的是,这个InvalidationListener
是一个功能接口:
@FunctionalInterface
public interface InvalidationListener {void invalidated(Observable observable);
}
这很棒,因为我们可以做以下事情:
Observable awesome = FXCollections.observableArrayList();
awesome.addListener(fantastic -> splendid.cheer());
(请注意,我是如何用更愉快的术语替换foo / bar / baz的。我们都应该这样做。Foo和bar是如此1970 )
不幸的是,当我们做我们可能要做的事情时,事情变得更加繁琐。 也就是说,与其声明一个Observable
, Observable
是一个更加有用的ObservableList
:
ObservableList<String> awesome = FXCollections.observableArrayList();
awesome.addListener(fantastic -> splendid.cheer());
但是现在,我们在第二行收到编译错误:
awesome.addListener(fantastic -> splendid.cheer());
// ^^^^^^^^^^^
// The method addListener(ListChangeListener<? super String>)
// is ambiguous for the type ObservableList<String>
因为,本质上...
public interface ObservableList<E>
extends List<E>, Observable {void addListener(ListChangeListener<? super E> listener);
}
和…
@FunctionalInterface
public interface ListChangeListener<E> {void onChanged(Change<? extends E> c);
}
再一次,在Java 8之前,这两种侦听器类型是完全可区分的,并且仍然是。 您可以通过传递命名类型来轻松调用它们。 如果我们编写以下代码,我们的原始代码仍然可以使用:
ObservableList<String> awesome = FXCollections.observableArrayList();
InvalidationListener hearYe = fantastic -> splendid.cheer();
awesome.addListener(hearYe);
要么…
ObservableList<String> awesome = FXCollections.observableArrayList();
awesome.addListener((InvalidationListener) fantastic -> splendid.cheer());
甚至…
ObservableList<String> awesome = FXCollections.observableArrayList();
awesome.addListener((Observable fantastic) -> splendid.cheer());
所有这些措施将消除歧义。 但坦率地说,如果您必须显式键入lambda或参数类型,则lambda的性能只有后者的一半。 我们拥有现代化的IDE,它们可以执行自动补全并帮助推断类型,就像编译器本身一样。
想象一下,如果我们真的想调用另一个addListener()
方法,它需要一个ListChangeListener。 我们必须写任何
ObservableList<String> awesome = FXCollections.observableArrayList();// Agh. Remember that we have to repeat "String" here
ListChangeListener<String> hearYe = fantastic -> splendid.cheer();
awesome.addListener(hearYe);
要么…
ObservableList<String> awesome = FXCollections.observableArrayList();// Agh. Remember that we have to repeat "String" here
awesome.addListener((ListChangeListener<String>) fantastic -> splendid.cheer());
甚至…
ObservableList<String> awesome = FXCollections.observableArrayList();// WTF... "extends" String?? But that's what this thing needs...
awesome.addListener((Change<? extends String> fantastic) -> splendid.cheer());
必须警惕。
API设计很难。 以前很难,现在变得越来越难。 在Java 8中,如果您的API方法的任何参数是功能接口,请三思而后行重载该API方法。 一旦您确定要继续进行重载,请再次思考,这是否真的是一个好主意。
不服气吗? 仔细看一下JDK。 例如java.util.stream.Stream
类型。 您看到多少个具有相同数量的功能接口参数的重载方法,而这些接口又使用了相同数量的方法参数(就像我们前面的addListener()
示例中一样)?
零。
在重载参数编号不同的地方有重载。 例如:
<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);<R, A> R collect(Collector<? super T, A, R> collector);
调用collect()
时,您永远不会有任何歧义。
但是,如果参数编号没有不同,并且参数本身的方法参数编号也没有变化,则方法名称也不同。 例如:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
现在,这在呼叫站点上非常令人讨厌,因为您必须事先考虑必须根据各种相关类型使用哪种方法。
但这确实是解决这一难题的唯一方法。 因此,请记住: 您会为Lambdas应用重载感到遗憾!
翻译自: https://www.javacodegeeks.com/2015/02/you-will-regret-applying-overloading-with-lambdas.html