编写好的API很难。 非常辛苦。 如果您想让用户喜欢您的API,则必须考虑很多事情。 您必须在以下两者之间找到适当的平衡:

  1. 有用性
  2. 易用性
  3. 向后兼容
  4. 前向兼容性

之前,在我们的文章: 如何设计良好的常规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 )

不幸的是,当我们做我们可能要做的事情时,事情变得更加繁琐。 也就是说,与其声明一个ObservableObservable是一个更加有用的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

您会后悔对Lambdas应用重载!相关推荐

  1. c++ lambda 重载_您会后悔对Lambdas应用重载!

    c++ lambda 重载 编写好的API很难. 非常辛苦. 如果您希望用户喜欢您的API,则必须考虑很多事情. 您必须在以下两者之间找到适当的平衡: 用处 易用性 向后兼容 前向兼容性 之前,在我们 ...

  2. java方法重载和重载方法_Java 8的方法参考进一步限制了重载

    java方法重载和重载方法 方法重载一直是一个充满喜忧参半的话题. 我们已经在博客上介绍了它,并介绍了几次警告: 您会后悔对Lambdas应用重载! 保持干燥:方法重载 为什么每个人都讨厌操作员超载 ...

  3. Java 8的方法参考进一步限制了重载

    方法重载一直是一个充满喜忧参半的话题. 我们已经在博客上介绍了它,并介绍了几次警告: 您会后悔对Lambdas应用重载! 保持干燥:方法重载 为什么每个人都讨厌操作员超载 API设计师,请小心 重载有 ...

  4. 【C++】重载运算符(二)

    1.4 下标运算符p501 下标运算符必须是成员函数,表示容器的类通常可以通过容器中的位置访问元素,定义下标运算符operator[] 一个包含下标运算符的类,通常,定义2个版本:一个返回普通引用,另 ...

  5. python 重载的实现(single-dispatch generic function)

    DAY 11. python 重载 函数重载是指允许定义参数数量或类型不同的同名函数,程序在运行时会根据所传递的参数类型选择应该调用的函数 ,但在默认情况下,python是不支持函数重载的,定义同名函 ...

  6. C++ 重载运算符 operator

    operator  是什么 operator 是C++的一个关键字,它和运算符(+,-,*,/,=,等等)一起使用,表示一个运算符重载函数 operator 没有返回语句 operator 的作用 : ...

  7. C++ 笔记(13)— 函数(函数声明、函数定义、函数调用[传值、指针、引用]、函数参数默认值、函数重载)

    每个 C++ 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数. 1. 函数声明 函数声明告诉编译器函数的名称.返回类型和参数.函数声明包括以下几个部分: ret ...

  8. Java知多少(29)覆盖和重载

    在类继承中,子类可以修改从父类继承来的方法,也就是说子类能创建一个与父类方法有不同功能的方法,但具有相同的名称.返回值类型.参数列表. 如果在新类中定义一个方法,其名称.返回值类型和参数列表正好与父类 ...

  9. Const 重载解析

    1. Const重载应用场景 首先,对于函数值传递的情况,因为参数传递是通过复制实参创建一个临时变量传递进函数的,函数内只能改变临时变量,但无法改变实参.则这个时候无论加不加const对实参不会产生任 ...

最新文章

  1. Moodle网络课程上如何添加视频文件
  2. 看别人的C/C++代码时发现自己所不知道的语法~
  3. 一篇文章了解蛋白质组学研究
  4. 实例源码_SpringBoot数据库源码解析Template实例化操作
  5. c#获取机器唯一识别码
  6. Springboot基于thymeleaf的一个简单的学生管理系统
  7. Django下载服务器文件到本地
  8. html背景颜色代码格式,html常用背景颜色代码.docx
  9. CSS 3之 文本样式(三)
  10. 我是如何走进黑客世界的?
  11. 使用Excel TRIMMEAN忽略异常值
  12. 进程间通讯SendMessage
  13. 计算机硬盘怎么设置ntfs,Windows7系统如何把磁盘格式转换为NTFS的方法
  14. 抖音21.8版本抓包方法(Android)
  15. 使用PreTranslateMessage替代钩子函数处理键盘消息
  16. 二维中的OBB相交测试
  17. 软件质量保证与测试大作业,软件测试大作业..docx
  18. GIT CZ的错误解决
  19. Node 中的 Events
  20. msp430发送pwm信号_使用MSP430G2单片机的PWM模块控制LED指示灯的亮度

热门文章

  1. Hibernate中使用Criteria查询及注解——(Dept.hbm.xml)
  2. SpringMVC的视图解析器
  3. 2020蓝桥杯省赛---java---B---4( 合并检测)
  4. mysql---批量插入数据:100w条数据
  5. java for遍历hashmap_Java 使用for和while循环遍历HashMap的方法及示例代码
  6. mysql自动插入的时间不对 差8小时
  7. java虚拟机的内存模型_JVM(Java虚拟机)内存模型(转载/整理)
  8. linux写入二进制文件内容,linux – 从管道读取数据并写入标准输出,中间延迟.必须处理二进制文件...
  9. compose应用_带有PostgreSQLDocker Compose for Spring Boot应用程序
  10. 螺旋测微器 flash_使用测微计收集应用程序指标