您好,登录后才能下订单哦!
本篇内容介绍了“怎么掌握Java中的Lambda”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
方法类型
从Java 8起方法就是一等公民了。按照标准的定义,编程语言中的一等公民是一个具有下列功能的实体,
可以作为参数进行传递, 可以作为方法的返回值 可以赋值给一个变量.
在Java中,每一个参数、返回值或变量都是有类型的,因此每个一等公民都必须是有类型的。Java中的一种类型可以是以下内容之一:
一种内建类型 (比如 int 或者 double) 一个类 (比如ArrayList) 一个接口 (比如 Iterable)
方法是通过接口进行定义类型的。它们不隐式的实现特定接口,但是在必要的时候,如果一个方法符合一个接口,那么在编译期间,Java编译器会对其进行隐式的检查。举个例子说明:
class LambdaMap {static void oneStringArgumentMethod(String arg) {System.out.println(arg);}}
关于oneStringArgumentMethod函数的类型,与之相关的有:它的的函数是静态的,返回类型是void,它接受一个String类型的参数。一个静态函数符合包含一个apply函数的接口,apply函数的签名相应地符合这个静态函数的签名。
oneStringArgumentMethod函数对应的接口因此必须符合下列标准。
它必须包含一个名为apply的函数。 函数返回类型必须是void。 函数必须接受一个String类型可以转换到的对象的参数。
在符合这个标准的接口之中,下面的这个是最明确的:
interface OneStringArgumentInterface {void apply(String arg);}
利用这个接口,函数可以分配给一个变量:
OneStringArgumentInterface meth = LambdaMap::oneStringArgumentMethod;
用这种方法使用接口作为类型,函数可以借此被分配给变量,传递参数并且从函数返回:
static OneStringArgumentInterface getWriter() {return LambdaMap::oneStringArgumentMethod;}static void write(OneStringArgumentInterface writer, String msg) {writer.apply(msg);}
最终函数是一等公民。
泛型函数类型
就像使用集合一样,泛型为函数类型增加了大量的功能和灵活性。实现功能上的算法而不考虑类型相关信息,泛型函数类型使其变为可能。在对map函数的实现中,会在下面用到这种功能。
在这提供的OneStringArgumentInterface一个泛型版本:
interface OneArgumentInterface<T> {void apply(T arg);}
OneStringArgumentInterface函数可以被分配给它:
OneArgumentInterface<String> meth = LambdaMap::oneStringArgumentMethod;
通过使用泛型函数类型,它现在可以以一种通用的方法实现算法,就像它在集合中使用的一样:
static <T> void applyArgument(OneArgumentInterface<T> meth, T arg) {meth.apply(arg);}
上面的函数并没有什么用,然而它至少可以提出一个想法:对函数作为第一个类成员的支持怎样可以形成非常简洁且灵活的代码:
applyArgument(Lambda::oneStringArgumentMethod, "X");
实现map
在诸多高阶函数中,map是最经典的. map的第一个参数是函数,该函数可以接收一个参数并返回一个值;第二个参数是值列表. map使用传入的函数处理值列表的每一项,然后返回一个新的值列表。下面Python的代码片段,可以很好的说明map的用法:
>>> map(math.sqrt, [1, 4, 9, 16])[1.0, 2.0, 3.0, 4.0]
在本节的后续内容中,将给出该函数的Java实现。Java 8已经通过Stream提供了该函数。因为主要出于教学目的,所以,本节中给出的实现特意保持简单,仅限于List对象使用。
与Python不同,在Java中必须首先考虑map第一个参数的类型:一个可以接收一个参数并返回一个值的方法。参数的类型和返回值的类型可以不同。下面接口符合这个预期,显然,I表示参数(入参),O表示返回值(出参):
interface MapFunction<I, O> {O apply(I in);}
泛型map方法的实现,变得惊人的简单明了:
static <I, O> List<O> map(MapFunction<I, O> func, List<I> input) {List<O> out = new ArrayList<>();for (I in : input) {out.add(func.apply(in));}return out;}
1.创建新的返回值列表out(用于保存O类型的对象).
2.通过遍历input,func处理列表的每一项,并将返回值添加到out中。
3.返回out.
下面是实际使用map方法的实例:
MapFunction<Integer, Double> func = Math::sqrt;List<Double> output = map(func, Arrays.asList(1., 4., 9., 16.));System.out.println(output);
在Python one-liner的推动下,可以用更简洁的方法表达:
System.out.println(map(Math::sqrt, Arrays.asList(1., 4., 9., 16.)));
Java毕竟不是Python...
Lambdas来了!
读者可能会注意到,还没有提到Lambdas。这是由于采用了“自下而上”的方式描述,现在基础已基本建立,Lambdas将在后续的章节中介绍。
下面的用例作为基础:一个double类型的list,表示半径,然后得到一个列表,表示圆面积。map方法就是为此任务预先准备的。计算圆面积的公式是众所周知的:
A = r2π
应用这个公式的方法很容易实现:
static Double circleArea(Double radius) {return Math.pow(radius, 2) * Math.PI;}
这个方法现在可以用作map方法的第一个参数:
System.out.println(map(LambdaMap::circleArea,Arrays.asList(1., 4., 9., 16.)));
如果circleArea方法只需要这一次, 没有道理把类接口被他弄得乱七八糟,也没有道理将实现和真正使用它的地方分离。最佳实践是使用用匿名内部类。可以看到,实例化一个实现MapFunction接口的匿名内部类可以很好的完成这个任务:
System.out.println(map(new MapFunction<Double, Double>() {public Double apply(Double radius) {return Math.sqrt(radius) * Math.PI;}},Arrays.asList(1., 2., 3., 4.)));
这看起来很漂亮,但是很多人会认为函数式的解决方案更清晰,更具可读性:
List<Double> out = new ArrayList<>();for (Double radius : Arrays.asList(1., 2., 3., 4.)) {out.add(Math.sqrt(radius) * Math.PI);}System.out.println(out);
到目前为止,最后是使用Lambda表达式。 读者应该注意Lambda如何取代上面提到的匿名类:
System.out.println(map(radius -> { return Math.sqrt(radius) * Math.PI; },Arrays.asList(1., 2., 3., 4.)));
这看起来简洁明了 - 请注意 Lambda 表达式如何缺省任何明确的类型信息。 没有显式模板实例化,没有方法签名。
Lambda表达式由两部分组成,这两部分被->分隔。第一部分是参数列表,第二部分是实际实现。
Lambda表达式和匿名内部类作用完全相同,然而它摒弃了许多编译器可以自动推断的样板代码。让我们再次比较这两种方式,然后分析编译器为开发人员节省了哪些工作。
MapFunction<Double, Double> functionLambda =radius -> Math.sqrt(radius) * Math.PI;MapFunction<Double, Double> functionClass =new MapFunction<Double, Double>() {public Double apply(Double radius) {return Math.sqrt(radius) * Math.PI;}};
对于Lambda实现来说,只有一个表达式,返回语句和花括号可以省略。这使得代码更简短。 Lambda表达式的返回值类型是从Lambda实现推断出来的。 对于参数类型,我不完全确定,但我认为必须从Lambda表达式所处的上下文中推断出参数类型。 最后编译器必须检查返回值类型是否与Lambda的上下文匹配,以及参数类型是否与Lambda实现匹配。
这一切都可以在编译期间完成,根本没有运行时开销。
“怎么掌握Java中的Lambda”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。