百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Java 8 函数式接口

toyiye 2024-06-21 12:39 9 浏览 0 评论

了解各种开箱即用的函数,如Consumer、Predicate和Supplier。

在本系列的第1部分中,我们了解到lambdas是一种功能接口——具有单个抽象方法的接口。Java API有许多单方法接口,如Runnable、Callable、Comparator、ActionListener等。它们可以使用匿名类语法实现和实例化。例如,以ITrade函数接口为例。它只有一个抽象方法,该方法接受一个交易对象并返回一个布尔值——可能是检查交易状态或验证订单或其他条件。

@FunctionalInterface
public interface ITrade {
public boolean check(Trade t);
}

为了满足我们检查新交易的需求,我们可以使用上面的函数接口创建一个lambda表达式,如下图所示:

ITrade newTradeChecker = (Trade t) -> t.getStatus().equals("NEW");
// Or we could omit the input type setting:
ITrade newTradeChecker = (t) -> t.getStatus().equals("NEW");

表达式期望一个Trade实例作为输入参数,声明在箭头标记的左边(我们可以省略输入的类型)。表达式的右侧只是check方法的主体—检查在交易中传递的状态。返回类型是隐式布尔类型,因为检查方法返回布尔类型。当您开始创建大量表示真实行为函数的lambdas时,它的真正威力就来了。例如,除了我们已经看到的,这里是用于找出大交易(i)的lambda表达式。e,如果交易数量大于100万)或查看新创建的大型谷歌交易:

// Lambda for big trade
ITrade bigTradeLambda = (Trade t) -> t.getQuantity() > 10000000;
// Lambda that checks if the trade is a new large google trade
ITrade issuerBigNewTradeLambda = (t) -> {
return t.getIssuer().equals("GOOG") &&
t.getQuantity() > 10000000 &&
t.getStatus().equals("NEW");
};

然后可以将这些函数传递给一个方法(很可能是服务器端),该方法接受ITrade作为其参数之一。假设我们有一个交易集合,希望根据某个标准过滤掉一些交易。这个要求可以很容易地表达使用上述lambda传递给一个方法,它也接受一个交易列表:

// Method that takes in list of trades and applies the lambda behavior for each of the trade in the collection
private List<Trade> filterTrades(ITrade tradeLambda, List<Trade> trades) {
List<Trade> newTrades = new ArrayList<>();
for (Trade trade : trades) {
if (tradeLambda.check(trade)) {
newTrades.add(trade);
}
}
return newTrades;
}

如果输入交易满足lambda定义的先决条件,则将该交易添加到累积篮子中,否则将丢弃。这个方法的好处是,只要实现ITrade接口,它就可以接受任何lambda。方法如下所示,取不同的lambdas:

// Big trades function is passed
List<Trade> bigTrades =
client.filterTrades(bigTradeLambda,tradesCollection);
// "BIG+NEW+ISSUER" function is passed
List<Trade> bigNewIssuerTrades =
client.filterTrades(issuerBigNewTradeLambda,tradesCollection);
// cancelled trades function is passed
List<Trade> bigNewIssuerTrades =
client.filterTrades(cancelledTradesLambda,tradesCollection);

预置的函数库

Java 8的设计者捕获了常用用例,并为它们创建了函数库。一个名为java.util的新包。创建函数来承载这些公共函数。在我们的ITrade案例中,我们所做的一切都是基于一个条件检查业务功能的布尔值。您可以在任何其他对象上测试条件(不仅仅是在trade上)。例如,您可以检查一个员工是否是一长串员工中的一员;从伦敦到巴黎的火车是否准点;不管今天是晴天还是晴天,等等。总的目标是检查一个条件,并在此基础上返回true或false。考虑到这些用例的共性,Java 8引入了一个名为Predicate的函数接口,它的功能与我们使用ITrade时的功能完全相同——检查输入是否正确(真/假)。我们可以使用定义良好的、多面功能接口的标准库,而不是编写自己的功能接口。在库中添加了一些这样的接口,我们将在下一节中讨论。

java.util.Predicate

Predicate 就是这样一个函数,它接受一个参数来计算布尔结果。它有一个返回布尔值的方法测试。参见下面的接口定义,它是一个接受任何类型T的泛型类型:

@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
From our knowledge of lambdas so far, we can deduce the lambda expression for this Predicate. Here are some example lambda expressions:
// A large or cancelled trade (this time using library function)!
Predicate<Trade> largeTrade = (Trade t) -> t.isBigTrade();
// Parenthesis and type are optional
Predicate<Trade> cancelledTrade = t -> t.isCancelledTrade();
// Lambda to check an empty string
Predicate<String> emptyStringChecker = s -> s.isEmpty();
// Lambda to find if the employee is an executive
Predicate<Employee> isExec = emp -> emp.isExec();

调用Predicate的test来检查true false,如下面的代码片段所示:

// Check to see if the trade has been cancelled
boolean cancelledTrade = cancelledTrade.test(t);
// Check to find if the employee is executive
boolean executive = isExec.test(emp);

java.util.Function

它接受类型T的参数,并通过apply方法在输入上应用指定的逻辑返回类型R的结果。界面定义如下:

// The T is the input argument while R is the return result
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}

我们使用一个函数来进行转换,例如将温度从摄氏温度转换为华氏温度,将字符串转换为整数,等等:

// convert centigrade to fahrenheit
Function<Integer,Double> centigradeToFahrenheitInt = x -> new Double((x*9/5)+32);
// String to an integer
Function<String, Integer> stringToInt = x -> Integer.valueOf(x);
// tests
System.out.println("Centigrade to Fahrenheit: "+centigradeToFahrenheitInt.apply(centigrade))
System.out.println(" String to Int: " + stringToInt.apply("4"));

这表示函数正在等待字符串并返回整数。更复杂一点的需求,比如为给定的交易列表聚合交易数量,可以表示为一个函数,如下所示:

// Function to calculate the aggregated quantity of all the trades - taking in a collection and returning an integer!
Function<List<Trade>,Integer> aggegatedQuantity = t -> {
int aggregatedQuantity = 0;
for (Trade t: t){
aggregatedQuantity+=t.getQuantity();
}
return aggregatedQuantity;
};

以上交易的聚合可以使用Stream API的“fluent”风格来完成:

// Using Stream and map and reduce functionality
aggregatedQuantity =
trades.stream()
.map((t) -> t.getQuantity())
.reduce(0, Integer::sum);
// Or, even better
aggregatedQuantity =
trades.stream()
.map((t) -> t.getQuantity())
.sum();

其他函数

使用者接受单个参数,但不返回任何结果:

@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}

这主要用于对参数执行操作,如持久化员工、调用内部维护操作、发送电子邮件时事通讯等。顾名思义,供应商为我们提供了结果;下面是方法签名:

@FunctionalInterface
public interface Supplier<T> {
T get();
}

例如,从数据库中获取配置值、加载引用数据、使用默认标识符创建学生列表等,都可以由supplier函数表示。在这些函数之上,Java 8还为特定用例提供了这些函数的专门版本。例如,扩展函数的UnaryOperator只对相同类型起作用。因此,如果我们知道输入和输出类型是相同的,我们可以使用UnaryOperator而不是Function。

// Here, the expected input and return type are exactly same.
// Hence we didn't use Function, but we are using a specialized sub-class
UnaryOperator<String> toLowerUsingUnary = (s) -> s.toLowerCase();

您注意到函数是用一种泛型类型声明的吗?在本例中,它们都是相同的字符串——因此,我们没有编写函数,而是使用+UnaryOperator进一步缩短了它。远一点,功能也提供给代表原始的专门化,DoubleUnaryOperator, LongUnaryOperator, IntUnaryOperator等等。他们基本上处理操作原语如双出双或长返回,等。就像函数的孩子适应特殊情况,所以做其他功能像IntPredicate或者LongConsumer BooleanSupplier,等。例如,IntPredicate代表一个整数值函数为基础,LongConsumer期望long值不返回结果,而BooleanSupplier提供布尔值。有太多这样的专门化,因此我强烈建议您详细阅读API以理解它们。

两个参数函数

到目前为止,我们已经处理了只接受单个输入参数的函数。有些用例可能必须对两个参数进行操作。例如,一个函数需要两个参数,但通过对这两个参数进行操作而产生结果。这种类型的功能适合于双参数函数桶,如BiPredicate、BiConsumer、BiFunction等。它们也非常容易理解,除了签名将具有额外的类型(两个输入类型和一个返回类型)。例如,双功能接口定义如下所示

@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
The above function has three types – T, U and R. The first two are are input types while the last one is the return result. For completeness, the following example provides a snippet of BiFunction usage, which accepts two Trades to produce sum of trade quantities. The input types are Trade and return type is Integer:
BiFunction<Trade,Trade,Integer> sumQuantities = (t1, t2) -> {
return t1.getQuantity()+t2.getQuantity();
};

使用两个参数函数的相同主题时,BiPredicate需要两个输入参数并返回一个布尔值(这并不奇怪!)

// Predicate expecting two trades to compare and returning the condition's output
BiPredicate<Trade,Trade> isBig = (t1, t2) -> t1.getQuantity() > t2.getQuantity();

正如您现在可能已经猜到的,这两个参数函数也有专门化。例如,对相同类型的操作数进行操作并发出相同类型的函数。这种功能是在BinaryOperator函数中捕获的。注意,extends BinaryOperator扩展了双函数。下面的例子展示了BinaryOperator的作用——它接受两个交易来生成一个合并的交易(它与输入参数的类型相同——请记住,BinaryOperator是双函数的一种特殊情况)。

BiFunction<Trade,Trade,Trade> tradeMerger2 = (t1, t2) -> {
// calling another method for merger
return merge(t1,t2);
};
// This method is called from the lambda.
// You can prepare a sophisticated algorithm to merge a trade in here
private Trade merge(Trade t1, Trade t2){
t1.setQuantity(t1.getQuantity()+t2.getQuantity());
return t1;
}

另外,您是否注意到实际的逻辑是由超函数双函数而不是二元运算符执行的?这是因为BinaryOperator扩展了双函数:

//BinaryOperator is a special case of BiFunction.
public interface BinaryOperator<T> extends BiFunction<T,T,T> { .. }

Now that we have seen the functions and functional interfaces, there is something I would like to reveal about interfaces. Until Java 8, the interfaces were abstract creatures – you certainly cannot add implementation to it, making it brittle in some ways. However, Java 8 re-engineered this, calling them virtual methods.

Virtual (default) methods

The journey of a Java library begins with a simple interface. Over time, these libraries are expected to evolve and grow in functionality. However, pre-Java 8 interfaces are untouchable! Once they are defined and declared, they are practically written in stone. For obvious reasons, backward compatibility being the biggest one, they cannot be changed after the fact. While it’s great that lambdas have been added to the language, there’s no point of having them if they cannot be used with existing APIs. In order to add support to absorb lambdas into our libraries, the interfaces needed to evolve. That is, we need to be able to add additional functionality to an already published API. The dilemma is how to embrace lambdas to create or enhance the APIs without losing backward compatibility? This requirement pushed Java designers to come up with yet another elegant feature – providing virtual extension methods or simply default methods to the interfaces. This means we can create concrete methods in our interfaces going forward. The virtual methods allow us to add newer functionality too. The collections API is one such example, where bringing lambdas into the equation has overhauled and enhanced the APIs. Let us walk through a simple example to demonstrate the default method functionality. Let’s assume that every component is said to have a name and creation date when instantiated. However, should the implementation doesn’t provide concrete implementation for the name and creation date, they would be inherited from the interface by default. For out example, the IComponent interface defines a set of default methods as shown below:

@FunctionalInterface
public interface IComponent {
// Functional method - note we must have one of these functional methods only
public void init();
// default method - note the keyword default
default String getComponentName(){
return "DEFAULT NAME";
}
// default method - note the keyword default
default Date getCreationDate(){
return new Date();
}
}

从上面的代码片段可以看出,接口现在有了一个实现,而不仅仅是抽象的。方法的前缀是一个关键字default,表示它们是默认方法

多重继承

多重继承对Java来说并不新鲜。Java从一开始就提供了多种类型的继承。如果我们有一个实现各种接口的对象层次结构,有一些规则可以帮助我们理解哪些实现应用于子类。基本规则是最接近子类的具体实现胜过其他继承的行为。直接的具体类型优先于任何其他类型。以下面的类层次结构为例。

// Person interface with a concrete implementation of name
interface Person{
default String getName(){
return "Person";
}
}
// Faculty interface extending Person but with its own name implementation
interface Faculty extends Person{
default public String getName(){
return "Faculty";
}
}

因此,Person和Faculty接口都提供了name的默认实现。但是,请注意,Faculty扩展Person,但是覆盖行为来提供它自己的实现。对于实现这两个接口的任何类,名称都是从Faculty继承的,因为它是最接近子类的子类型。因此,如果我有一个Student子类来实现Faculty(和Person),那么Student的getName()方法会打印出Faculty的名称:

// The Student inherits Faculty's name rather than Person
class Student implements Faculty, Person{ .. }
// the getName() prints Faculty
private void test() {
String name = new Student().getName();
System.out.println("Name is "+name);
}


output: Name is Faculty

然而,有一点需要注意。如果我们的教职工班根本不扩员怎么办?在这种情况下,Student类从两个实现中继承名称,从而使编译器发出呻吟。在这种情况下,为了使编译器满意,我们必须自己提供具体的实现。但是,如果希望继承超类型的行为之一,可以显式地继承,如下面的代码片段所示。

interface Person{ .. }
// Notice that the faculty is NOT implementing Person
interface Faculty { .. }
// As there's a conflict, out Student class must explicitly declare whose name it's going to inherit!
class Student implements Faculty, Person{
@Override
public String getName() {
return Person.super.getName();
}
}

有一种特殊的语法可以从超级接口获取方法– using super-interface.super.method: Person.super.getName().

方法引用

public class AddableTest {
// Add given two integers
private int addThemUp(int i1, int i2){
return i1+i2;
}

因为添加整数的方法已经存在,所以没有必要创建一个lambda表达式来做完全相同的事情。因此,当为可IAddable实现创建lambda时,我们通过方法引用(使用双冒号::)来引用这个现有的方法:

public class AddableTest {
// Lambda expression using existing method
IAddable addableViaMethodReference = this::addThemUp;
// Add given two integers
private int addThemUp(int i1, int i2){
return i1+i2;
}
}

注意上面代码中的this::addThemUp lambda表达式。this引用AddableTest类的一个实例,而双冒号后面的位是对已有方法的调用。另外,请注意方法引用没有在末尾添加大括号()。如果您有另一个类通过静态方法实现所需的功能,您可以通过使用方法引用的这个特性在lambda表达式中简单地使用它的方法。请看下面给出的例子:

// Class that provides the functionality via it's static method
public class AddableUtil {
public static int addThemUp(int i1, int i2){
return i1+i2;
}
}
// Test class
public class AddableTest {
// Lambda expression using static method on a separate class
IAddable addableViaMethodReference = AddableUtil::addThemUp;
...
}

我们还可以通过简单地调用类的构造函数Employee::new或Trade::new来使用构造函数引用。

总结

在这篇文章中,我们学习了更多关于Java功能接口和函数的知识。我们深入了解了各种开箱即用的函数,如Consumer、Predicate、Supplier等。我们还研究了虚拟方法和方法引用。在本系列的下一篇文章中,我们将详细讨论流API。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码