热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java如何为接口创建功能函数_Java8新语法习惯(函数接口)

了解如何创建自定义函数接口,以及为什么应该尽量使用内置的接口。概览lambda表达式的类型是什么?一些语言使用函数值或函数对象来表示lambda表达式&

了解如何创建自定义函数接口,以及为什么应该尽量使用内置的接口。

概览

lambda 表达式的类型是什么?一些语言使用函数值或函数对象来表示 lambda 表达式,但是 Java 语言没有这么做。Java 使用函数接口来表示 lambda 表达式类型。这其实是一种确保 Java 语言旧版本的向后兼容性的有效途径。

看下面一段代码:

Thread thread = new Thread(new Runnable() {

public void run() {

System.out.println("In another thread");

}

});

thread.start();

System.out.println("In main");

Thread 类和它的构造函数是在 Java 1.0 中引入的,距今已有超过 20 年的时间。从那时起,构造函数就未改变过。将 Runnable 的匿名实例传递给构造函数已经是成为一种传统。但是从 java8 开始,可以选择传递 lambda 表达式:

public static void main(String[] args) {

// TODO Auto-generated method stub

Thread thread = new Thread(() -> System.out.println("这是一个线程"));

thread.start();

}

执行结果:

这是一个线程

这里 Thread 类的构造函数想要一个实现 Runnable 的实例。在本例中我们传递了一个 lambda 表达式,而不是一个对象。实际上我们可以向各种各样的方法和构造函数传递 lambda 表达式,包括在 Java8 之前创建的方法和构造函数。这很有效,因为 lambda 表达式在 Java 中表示为函数接口。

函数接口有三条重要法则:

一个函数接口只有一个抽象方法。

在 Object 类中属于公共方法的抽象方法不会被视为单一抽象方法。

函数接口可以有默认方法和静态方法。

任何满足单一抽象方法法则的接口,都会被自动视为函数接口。这包括 Runnable 和 Callable 等传统的接口,以及您自己构建的自定义接口。

内置函数接口

除了已经提到的单一抽象方法之外,JDK 8 还包含多个新函数接口。最常用的接口包括 Function、Predicate 和 Consumer,它们是在 java.util.function 包中定义的。Stream 的 map 方法接受 Function 作为参数。类似地,filter 使用 Predicate,forEach 使用 Consumer。该包还有其他函数接口,比如 Supplier、BiConsumer 和 BiFunction。

可以将内置函数接口用作我们自己的方法的参数。例如,假设我们有一个 Device 类,它包含方法 checkout 和 checkin 来指示是否正在使用某个设备。当用户请求一个新设备时,方法 getFromAvailable 从可用设备池中返回一个设备,或在必要时创建一个新设备。

我们可以实现一个函数来借用设备:

public void borrowDevice(Consumer use) {

Device device = getFromAvailable();

device.checkout();

try {

use.accept(device);

} finally {

device.checkin();

}

}

接受 Consumer 作为参数。

从池中获取一个设备(我们在这个示例中不关心线程安全问题)。

调用 checkout 方法将设备状态设置为 checked out。

将设备交付给用户。

在完成设备调用后返回到 Consumer 的 accept 方法时,通过调用 checkin 方法将设备状态更改为 checked in。

下面给出了一种使用 borrowDevice 方法的方式:

new Sample().borrowDevice(device -> System.out.println("using " + device));

因为该方法接收一个函数接口作为参数,所以传入一个 lambda 表达式作为参数是可以接受的。

自定义函数接口

尽管最好尽量使用内置函数接口,但有时需要自定义函数接口。

要创建自定义函数接口,需要做两件事:

使用 @FunctionalInterface 注释该接口,这是 java8 对自定义函数接口的约定。

确保该接口只有一个抽象方法。

该约定清楚的表明该接口接收 lambda 表达式。当编译器看到该注释时,它会验证该接口是否只有一个抽象方法。

使用 @FunctionalInterface 注释可以确保,如果在未来更改接口时意外违反抽象方法数量规则,您会获得错误消息。这是很有用的,因为你会立即发现问题,而不是留给下一个开发人员在以后处理它。没有人希望将在 lambda 表达式传递给其他人的自定义接口时获得错误的消息。

创建自定义函数接口

下面的示例创建一个 Transformer 函数接口:

@FunctionalInterface

public interface Transformer {

T transform(T input);

}

该接口用 @FunctionalInterface 注释做了标记,表明它是一个函数接口。因为该注释包含在 java.lang 包中,所以没有必要导入。该接口有一个名为 transform 的方法,后者接受一个参数化为 T 类型的对象,并返回一个相同类型的转换后对象。转换的语义将由该接口的实现来决定。

这是 OrderItem 类:

public class OrderItem {

private final int id;

private final int price;

protected OrderItem(int id, int price) {

super();

this.id = id;

this.price = price;

}

public int getId() {

return id;

}

public int getPrice() {

return price;

}

@Override

public String toString() {

// TODO Auto-generated method stub

return String.format("id: %d price: %d", id, price);

}

}

现在来看看 Order 类:

public class Order {

List items;

protected Order(List items) {

super();

this.items = items;

}

public void transformAndPrint(Transformer> transformOrderItems) {

transformOrderItems.transform(items.stream())

.forEach(System.out::println);

}

}

transformAndPrint 方法接受 Transform 作为参数,调用 transform 方法来转换属于 Order 实例的订单项,然后按转换后的顺序输出这些订单项。

测试这个方法:

public class Sample {

public static void main(String[] args) {

// TODO Auto-generated method stub

Order order = new Order(Arrays.asList(

new OrderItem(1, 1225),

new OrderItem(2, 983),

new OrderItem(3, 1554)

));

order.transformAndPrint(new Transformer>() {

@Override

public Stream transform(Stream input) {

// TODO Auto-generated method stub

return input.sorted(Comparator.comparing(OrderItem::getPrice));

}

});

}

}

测试结果:

id: 2 price: 983

id: 1 price: 1225

id: 3 price: 1554

我们传递一个匿名内部类作为 transformAndPrint 方法的参数。在 transform 方法内,调用给定流的 sorted 方法,这会对订单项进行排序。

lambda表达式的强大功能

在任何需要函数接口的地方,我们都有三种选择:

传递一个匿名内部类。

传递一个 lambda 表达式。

在某些情况下传递一个方法引用而不是 lambda 表达式。

order.transformAndPrint(input -> input.sorted(Comparator.comparing(OrderItem::getPrice)));

与我们最初提供的匿名内部类相比,这简洁得多且更容易阅读。

自定义函数接口与内置函数接口

我们上边演示了自定义函数接口,那么自定义函数接口有什么优势和不足呢?

首先优势:

您可以为自定义接口提供一个描述性名称,帮助其他开发人员修改或重用它。

只要您高兴,可以为抽象方法提供任何具有有效语法的名称。只有接口的接收者会获得此优势,而且仅在传递抽象方法时才会体现出来。传递 lambda 表达式或方法引用的调用不会获得此优势。

您可以在自己的接口中使用参数化的类型,或者让它保持简单并特定于某些类型。

您可以编写自定义的默认方法和静态方法,它们可供该接口的其他实现使用。

自定义接口的不足:

想创建多个接口,所有接口都具有相同签名的抽象方法,比如接受 String 作为参数并返回 Integer。尽管方法的名称可能有所不同,但它们大部分都是多余的,可替换为一个具有通用名称的接口。

任何想要使用自定义接口的人,都必须投入额外的精力来学习、理解和记住它们。

那种接口最好

了解了自定义函数接口与内置函数接口的优缺点之后,那么我们该如何采用那种接口呢?我们回顾一下 Transformer 接口。

public void transformAndPrint(Transformer> transformOrderItems) {

方法 transformAndPrint 接收一个负责执行转换的参数。该转换可能对 orderItems 集合中的元素进行重新排序。或者,它可能屏蔽每个订单项的部分袭击。或者什么也不做。我们将具体的实现留给调用者。

重要的是,调用方知道它们可以将转换实现作为参数提供给 transformAndPrint 方法。函数接口的名称和它的文档应该提供这些细节。在本例中,从参数名称 (transformOrderItems) 也可以清楚了解这些细节,而且它们应包含在 transformAndPrint 函数的文档中。尽管函数接口的名称很有用,但它不是了解函数接口用途和用法的唯一途径。

仔细查看 Transformer 接口,并将它的用途与 JDK 的内置函数接口进行比较,我们看到 Function 可以取代 Transformer。

public void transformAndPrint(Function, Stream> transformOrderItems) {

transformOrderItems.apply(items.stream())

.forEach(System.out::println);

}

对 transformAndPrint 的调用使用了一个匿名内部类,我们还需要更改这一点。但是,我们已更改该调用来使用 lambda 表达式:

order.transformAndPrint(orderItems -> orderItems.sorted(comparing(OrderItem::getPrice)));

我们看到我们同样可以用方法引用或者 lambda 表达式去调用,函数接口的名称与 lambda 表达式无关,它仅与编译器有关,编译器将 lambda 表达式参数与方法参数联系起来。方法名称是 transform 还是 apply 同样与调用方无关。

使用内置的函数接口让我们的接口减少了一个,调用该方法也具有同样功效。我们也没有损害代码的可读性。

总结

将 lambda 表达式设计为函数接口类型的设计策略,有助于 Java 向早期的版本进行兼容。可以将 lambda 表达式传递给任何通常接收单一抽象方法接口的旧函数。要接收 lambda 表达式,方法的参数类型应为函数接口。

在某些情况下,创建自己的函数接口是合情合理的,但在这么做时应该小心谨慎。仅在应用程序需要高度专业化的方法时,或者现有接口无法满足您的需求时,才考虑自定义函数接口。请始终检查一个 JDK 的内置函数接口中是否存在该功能。尽量使用内置函数接口。



推荐阅读
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 摘要: 在测试数据中,生成中文姓名是一个常见的需求。本文介绍了使用C#编写的随机生成中文姓名的方法,并分享了相关代码。作者欢迎读者提出意见和建议。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
author-avatar
贝乐小凸
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有