【编者按】本文作者为拥有15年 Java 开发经验的资深程序员 Per-Åke Minborg,主要介绍如何灵活地解析 Java 中的方法引用。文章系国内 ITOM 管理平台 OneAPM 编译呈现。
方法引用
众所周知,在Java 8中我们可以使用方法引用。譬如,在我们需要遍历流元素时,可以使用 String::isEmpty
来引用isEmpty
方法。试看下面这段代码:
Stream.of("A", "", "B").filter(Stream::isEmpty).count();
运行的结果为1(因为在这个流中只有一个空元素)。但是,如果我们要过滤出非空字符串,我们得写成.filter(s -> !s.isEmpty())
。这是一个Lambda表达式。显然,这儿有个讨厌的不对称想象。我们可以使用方法引用,但却不能用它的反式。我们可以写predicate.negate()
却不能写Stream::isEmpty.negate()
或!Stream::isEmpty
。
为什么呢?这是因为方法引用并非Lambda表达式或者函数接口。不过,使用Java的类型推导可以将方法引用解析为一个或多个函数接口。上例中的String::isEmpty
至少可以解析为:
Predicate<String> Function<String, Boolean>
所以,我们要排除其他可能性,确定到底将方法引用转换为哪个函数接口。本文在一定程度上解决了该问题。文中的代码来自开源项目Speedment,它让数据库看起来像Java 8的流。
解析方法引用
其实,以静态方法为“管道”,可以部分地解决这个问题——该静态方法以一个方法引用为输入,以特定的函数接口为其返回。试考虑下面这个简短的静态方法:
public static <T> Predicate<T> as(Predicate<T> predicate) { return predicate; }
现在,如果静态地导入这个方法,事实上,我们就能更简单地使用方法引用。如下例所示:
Stream.of("A", "", "B").filter(as(String::isEmpty).negate()).count();
这段代码返回的结果为2,即流中非空元素的数量。有关方法引用的使用方式,我们又向前迈进了一步。另一个好处是,这个解决方案让我们更轻松地编写predicates接口,例如:
.filter(as(String::isEmpty).negate().and("A"::equals))
解析所有方法引用
但是,现在仍有一个问题亟待解决。我们不能随随便便地创建一大堆静态as()
函数,因为一个方法引用可能解析为多种as()
方法,正如本文开头提到的那样。所以,一个更妙的解决方案,是把函数接口类型名添加至每个静态方法,这样我们就可以程序化地为每个函数接口转换方法选择一个特定的方法引用。我们有一个工具类,可以让每个方法引用都转换为Java标准包 `java.util.function中任意匹配的函数接口。
直接在GitHub下载最新版本
import java.util.function.*; /** * * @author Per Minborg */ public class FunctionCastUtil { public static <T, U> BiConsumer<T, U> asBiConsumer(BiConsumer<T, U> biConsumer) { return biConsumer; } public static <T, U, R> BiFunction<T, U, R> asBiFunction(BiFunction<T, U, R> biFunction) { return biFunction; } public static <T> BinaryOperator<T> asBinaryOperator(BinaryOperator<T> binaryOperator) { return binaryOperator; } public static <T, U> BiPredicate<T, U> asBiPredicate(BiPredicate<T, U> biPredicate) { return biPredicate; } public static BooleanSupplier asBooleanSupplier(BooleanSupplier booleanSupplier) { return booleanSupplier; } public static <T> Consumer<T> asConsumer(Consumer<T> consumer) { return consumer; } public static DoubleBinaryOperator asDoubleBinaryOperator(DoubleBinaryOperator doubleBinaryOperator) { return doubleBinaryOperator; } public static DoubleConsumer asDoubleConsumer(DoubleConsumer doubleConsumer) { return doubleConsumer; } public static <R> DoubleFunction<R> asDoubleFunction(DoubleFunction<R> doubleFunction) { return doubleFunction; } public static DoublePredicate asDoublePredicate(DoublePredicate doublePredicate) { return doublePredicate; } public static DoubleToIntFunction asDoubleToIntFunction(DoubleToIntFunction doubleToIntFunctiontem) { return doubleToIntFunctiontem; } public static DoubleToLongFunction asDoubleToLongFunction(DoubleToLongFunction doubleToLongFunction) { return doubleToLongFunction; } public static DoubleUnaryOperator asDoubleUnaryOperator(DoubleUnaryOperator doubleUnaryOperator) { return doubleUnaryOperator; } public static <T, R> Function<T, R> asFunction(Function<T, R> function) { return function; } public static IntBinaryOperator asIntBinaryOperator(IntBinaryOperator intBinaryOperator) { return intBinaryOperator; } public static IntConsumer asIntConsumer(IntConsumer intConsumer) { return intConsumer; } public static <R> IntFunction<R> asIntFunction(IntFunction<R> intFunction) { return intFunction; } public static IntPredicate asIntPredicate(IntPredicate intPredicate) { return intPredicate; } public static IntSupplier asIntSupplier(IntSupplier intSupplier) { return intSupplier; } public static IntToDoubleFunction asIntToDoubleFunction(IntToDoubleFunction intToDoubleFunction) { return intToDoubleFunction; } public static IntToLongFunction asIntToLongFunction(IntToLongFunction intToLongFunction) { return intToLongFunction; } public static IntUnaryOperator asIntUnaryOperator(IntUnaryOperator intUnaryOperator) { return intUnaryOperator; } public static LongBinaryOperator asLongBinaryOperator(LongBinaryOperator longBinaryOperator) { return longBinaryOperator; } public static LongConsumer asLongConsumer(LongConsumer longConsumer) { return longConsumer; } public static <R> LongFunction<R> asLongFunction(LongFunction<R> longFunction) { return longFunction; } public static LongPredicate asLongPredicate(LongPredicate longPredicate) { return longPredicate; } public static <T> LongSupplier asLongSupplier(LongSupplier longSupplier) { return longSupplier; } public static LongToDoubleFunction asLongToDoubleFunction(LongToDoubleFunction longToDoubleFunction) { return longToDoubleFunction; } public static LongToIntFunction asLongToIntFunction(LongToIntFunction longToIntFunction) { return longToIntFunction; } public static LongUnaryOperator asLongUnaryOperator(LongUnaryOperator longUnaryOperator) { return longUnaryOperator; } public static <T> ObjDoubleConsumer<T> asObjDoubleConsumer(ObjDoubleConsumer<T> objDoubleConsumer) { return objDoubleConsumer; } public static <T> ObjIntConsumer<T> asObjIntConsumer(ObjIntConsumer<T> objIntConsumer) { return objIntConsumer; } public static <T> ObjLongConsumer<T> asObjLongConsumer(ObjLongConsumer<T> objLongConsumer) { return objLongConsumer; } public static <T> Predicate<T> asPredicate(Predicate<T> predicate) { return predicate; } public static <T> Supplier<T> asSupplier(Supplier<T> supplier) { return supplier; } public static <T, U> ToDoubleBiFunction<T, U> asToDoubleBiFunction(ToDoubleBiFunction<T, U> toDoubleBiFunction) { return toDoubleBiFunction; } public static <T> ToDoubleFunction<T> asToDoubleFunction(ToDoubleFunction<T> toDoubleFunction) { return toDoubleFunction; } public static <T, U> ToIntBiFunction<T, U> asToIntBiFunction(ToIntBiFunction<T, U> toIntBiFunction) { return toIntBiFunction; } public static <T> ToIntFunction<T> asToIntFunction(ToIntFunction<T> ioIntFunction) { return ioIntFunction; } public static <T, U> ToLongBiFunction<T, U> asToLongBiFunction(ToLongBiFunction<T, U> toLongBiFunction) { return toLongBiFunction; } public static <T> ToLongFunction<T> asToLongFunction(ToLongFunction<T> toLongFunction) { return toLongFunction; } public static <T> UnaryOperator<T> asUnaryOperator(UnaryOperator<T> unaryOperator) { return unaryOperator; } private FunctionCastUtil() { } }
在静态导入了相关方法之后,我们就可以这样写:
Stream.of("A", "", "B").filter(asPredicate(String::isEmpty).negate()).count();
一个更好的解决方案
如果函数接口本身就包含一个接收方法引用并将其转换为某类函数接口的静态方法,那就更好了。举例来说,标准的Java Predicated
函数接口就会变成这样:
@FunctionalInterface public interface Predicate<T> { boolean test(T t); default Predicate<T> and(Predicate<? super T> other) {...} default Predicate<T> negate() {...} default Predicate<T> or(Predicate<? super T> other) {...} static <T> Predicate<T> isEqual(Object targetRef) {...} // New proposed support method to return a // Predicate view of a Functional Reference public static <T> Predicate<T> of(Predicate<T> predicate) { return predicate; } }
因此,我们可以这样写:
Stream.of("A", "", "B").filter(Predicate.of(String::isEmpty).negate()).count();
笔者觉得这样看起来好极了!
快联系离你最近的Open JDK开发人员,提出你的修改建议吧!
OneAPM 能为您提供端到端的 Java 应用性能解决方案,我们支持所有常见的 Java 框架及应用服务器,助您快速发现系统瓶颈,定位异常根本原因。分钟级部署,即刻体验,Java 监控从来没有如此简单。想阅读更多技术文章,请访问 OneAPM 官方技术博客。
本文转自 OneAPM 官方博客
原帖地址:https://dzone.com/articles/put-your-java-8-method-references-to-work
相关推荐
4. **方法引用**:在Java 8中,方法引用是一种更加简洁的语法,可以直接引用已有方法而无需写lambda表达式。这使得代码更加清晰,减少了冗余。 5. **日期和时间API(java.time)**:Java 8对日期和时间API进行了...
Java 8允许接口定义默认方法,这些方法有一个实现,不强制实现类去重写。默认方法通过`default`关键字声明,增强了接口的功能。静态方法也可以在接口中定义,它们不属于任何实现类,而是属于接口本身。 3. **方法...
4. **方法引用来代替Lambda**:除了Lambda表达式,Java 8还允许使用方法引用来代替Lambda,如 `myClass::myMethod`,这使得可以直接引用已有的方法作为函数式接口的实现。 5. **日期和时间API**:Java 8替换原有的...
4. **方法与构造器引用**:除了Lambda表达式,Java 8还引入了方法和构造器引用,它们可以更直接地将已有方法或构造器作为函数进行传递,进一步简化了代码。 5. **日期与时间API**:Java 8对日期和时间API进行了全面...
Java 8 API 中版chm手册是一份详细记录了Java 8编程接口的参考资料,它为开发者提供了关于Java 8新特性和已有组件的全面指南。这份手册是中英双语版本,中文版包含了所有核心Java类库和API的详细说明,方便中国地区...
例如,使用方法引用重写上面的转换器代码如下: ```java Converter, Integer> converter = Integer::valueOf; ``` 这里,`Integer::valueOf`是一个方法引用,表示使用Integer类的valueOf静态方法来实现Converter接口...
17. `this`关键字:`this`关键字在Java中表示当前对象的引用,即调用该方法或字段的当前实例。 18. 正则表达式:".boy\\w{3}"匹配以"boy"开始,后跟三个任意单词字符(字母、数字或下划线)的字符串。例如,"boyabc...
3. **方法引用和构造器引用**:除了Lambda表达式,Java 8还引入了方法引用和构造器引用,可以直接引用类的方法或构造器,而无需编写lambda表达式。这使得代码更加清晰且减少了冗余。 4. **Stream API**:Stream API...
使用方法引用可以提高代码的可读性。 接口中的默认方法是一个接口可以包含具有具体实现的方法,这允许在不破坏现有接口契约的情况下添加新的方法到现有的接口。这样,接口可以提供一些基础的默认实现,接口的实现类...
1. **基础语法**:Java的基础语法是所有编程学习的起点,包括数据类型(如基本类型和引用类型)、变量声明、运算符、流程控制语句(如if、for、while)以及方法的定义和调用。 2. **面向对象编程**:Java是一种面向...
《Pro Java 8 Programming》是Java开发者的一本深入指南,主要针对Java 8这一重要的版本。这本书详尽地探讨了Java 8的新特性和改进,旨在帮助开发者充分利用这个平台的最新功能,提升编程效率和代码质量。以下是该书...
Java程序员开发指南旨在帮助初学者和有一定经验的开发者深入理解并掌握Java编程语言,从而提升在实际项目中的应用能力。本指南将涵盖以下几个核心领域: 1. **Java基本语法**:Java是一种静态类型的、强类型的语言...
《阿里巴巴 Java 编码指南》是业界广泛采用的编码规范,旨在提高代码质量和开发效率,尤其对于使用 IntelliJ IDEA 的开发者来说,此指南的兼容性更新至 2023.3+ 版本,确保了最新的开发环境支持。这份指南在 2024 年...
Java高效编程指南主要关注如何优化Java代码的性能和设计,以提高程序的效率和可维护性。以下是一些关键知识点的详细说明: 1. **创建和销毁对象** - **静态工厂方法**:使用静态工厂方法代替构造函数,因为它们...
- **Java实现浅克隆与深克隆**:浅克隆复制对象本身及含有引用的对象地址,而深克隆则复制了对象本身及所有成员变量的值。 - **枚举可以序列化吗**:枚举类型默认实现了`Serializable`接口,因此可以直接进行序列化...
- 使用方法覆盖时,应遵循开闭原则(Open-Closed Principle),即对扩展开放,对修改关闭,使得代码更易于维护和扩展。 以上是根据标题和描述推测的可能内容,实际文档中可能还会涉及更多的细节、示例代码以及实战...
另外,对Java中的同步机制、锁的使用以及对于“==”和“equals”方法的不同用法也应了如指掌。这些知识点对于求职者在面试过程中解答技术问题至关重要。 书中强调了在面试中如何有效沟通的重要性,比如在解释equals...
《Java 8编程入门官方教程》是一本非常全面的Java 8学习指南,不仅覆盖了语言的基础知识,还深入讲解了Java 8中的新特性及其应用场景。对于希望快速掌握Java 8并进行高效编程的开发者来说,这是一本不可或缺的参考...