博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Understanding Java Lambdas
阅读量:6818 次
发布时间:2019-06-26

本文共 10945 字,大约阅读时间需要 36 分钟。

hot3.png

Understanding Java Lambdas

Posted on 2017-04-25

It took me quite some reading and coding to finally understand how Java Lambdas actually work conceptually. Most tutorials and introductions I read follow a top-down approach, starting with use cases and in the end leaving conceptual questions open. In this post I want to offer a bottom-up explanation, deriving the concept of Lambdas from other established Java concepts.

Firstly typing of methods is introduced, which is a prerequisite for supporting methods as first-class citizens. Based on this the concept of Lambdas is presented as an advancement and special case of anonymous class usage. All this is illustrated by the implementation and usage of the .

The primary audience of this post are people who grasp the basics of functional programming and who want to understand how Lambdas fit into the Java language conceptually.

Method types

From Java 8 on methods are . Following the standard definition, a first-class citizen in a programming language is an entity that can be

  • passed as an argument,
  • returned from a method and
  • assigned to a variable.

In Java, every argument, return value or variable is typed, therefore every first-class citizen has to be typed. A type in Java can be one of the following:

  • a builtin type (e. g. int or double)
  • a class (e. g. ArrayList)
  • an interface (e. g. Iterable)

Methods are typed via interfaces. They do not implicitely implement certain interfaces, but, when necessary, the Java compiler implicitely checks during compile time if a method conforms to an interface. An example should illustrate this:

class LambdaMap {    static void oneStringArgumentMethod(String arg) {        System.out.println(arg);    }}

Regarding the type of the method oneStringArgumentMethod it is of relevance that the method is static, the return type is void and that it accepts one argument of type String. A static method conforms to an interface that contains a method apply, whose signature in turn conforms to the signature of the static method. An interface matching the method oneStringArgumentMethod therefore must meet the following criteria:

  • It must contain a method called apply.
  • The return type of this method must be void.
  • This method must accept one argument that an object of type String can be casted to.

Amongst the interfaces conforming to this criteria, the following one might be the most obvious:

interface OneStringArgumentInterface {    void apply(String arg);}

With the help of this interface the method can assigned to a variable:

OneStringArgumentInterface meth = LambdaMap::oneStringArgumentMethod;

Using interfaces as a types in this way, methods can thus be assigned to variables, passed as parameters and returned from methods:

static OneStringArgumentInterface getWriter() {    return LambdaMap::oneStringArgumentMethod;}static void write(OneStringArgumentInterface writer, String msg) {    writer.apply(msg);}

Finally methods are first-class citizens in Java!

Generic method types

As with collections, generics add a lot of power and flexibility to method types. Generic method types make it possible to implement functional algorithms disregarding certain type informations. This ability will be used below in the implementation of the map method.

A generic version of the OneStringArgumentInterface is provided here:

interface OneArgumentInterface
{ void apply(T arg);}

The method oneStringArgumentMethod can be assigned to it:

OneArgumentInterface
meth = LambdaMap::oneStringArgumentMethod;

Using generic method types one can now implement algorithms in a generic way, as one is used to from collections:

static 
void applyArgument(OneArgumentInterface
meth, T arg) { meth.apply(arg);}

The method above does not do anything useful, however it can at least give a first idea how support for methods as first-class citizens can lead to very concise and flexible code:

applyArgument(Lambda::oneStringArgumentMethod, "X");

Implementing map

Amongst higher-order functions, map is a classic. The first argument to map is a function that accepts one argument and returns a value; the second argument is a list of values. map applies the passed function to every item of the passed list and returns a new list with the resulting values. The following snippet from a Python session illustrates its usage very well:

>>> map(math.sqrt, [1, 4, 9, 16])[1.0, 2.0, 3.0, 4.0]

In the remaining part of this section a Java implementation of this function will be given. Java 8 already offers this functionality via streams. As it mainly serves educational purposes, the implementation given in this section is kept deliberately simple and will be restricted to work on List objects only.

In Java, as opposed to Python, one first has to consider the type of the first argument to map: a method accepting one argument and returning a return value. The argument type and the return type can be different. The following interface fits the purpose, where obviously I denotes the type of the argument (input) and Odenotes the type of the return value (output):

interface MapFunction
{ O apply(I in);}

The implementation of the generic map method itself becomes surprisingly simple and straightforward:

static 
List
map(MapFunction
func, List
input) { List
out = new ArrayList<>(); for (I in : input) { out.add(func.apply(in)); } return out;}
  1. A new list out is created (holding objects of the O output type).
  2. In a loop over the input list, func is applied to every item of the list. The return value is added to out.
  3. out is returned.

Here is an example of the map method in action:

MapFunction
func = Math::sqrt;List
output = map(func, Arrays.asList(1., 4., 9., 16.));System.out.println(output);

Motivated by the Python one-liner, this can of course be expressed in a more concise way:

System.out.println(map(Math::sqrt, Arrays.asList(1., 4., 9., 16.)));

Well, Java is not Python after all ...

Lambdas, finally!

The inclined reader will have noticed that there has not been any mention of a Lambda yet. That's owing to following a bottom-up approach - however, the foundations are almost set and Lambdas will be introduced in the following section.

The following use case serves as a basis: having a list of doubles denoting circle radiuses, a list of corresponding circle areas has to be obtained. The map method is predestined for this task. The formula for calculating the area of a circle is well known:

A = r2π

A method applying this formula is easily implemented:

static Double circleArea(Double radius) {    return Math.pow(radius, 2) * Math.PI;}

This method can now be used as first argument to the map method:

System.out.println(        map(LambdaMap::circleArea,            Arrays.asList(1., 4., 9., 16.)));

Assuming the method circleArea is only needed this one time, it does not make sense to clutter the class interface with it and to separate its implementation from the one place where it is actually used. A Java best practice is to use an anonymous class in this case. As one can see, this works out well with instantiating an anonymous class that implements the MapFunction interface:

System.out.println(        map(new MapFunction
() { public Double apply(Double radius) { return Math.sqrt(radius) * Math.PI; } }, Arrays.asList(1., 2., 3., 4.)));

That's looks nifty, however many will consider the functionally equivalent solution below clearer and more readable:

List
out = new ArrayList<>();for (Double radius : Arrays.asList(1., 2., 3., 4.)) { out.add(Math.sqrt(radius) * Math.PI);}System.out.println(out);

Having come thus far, it is finally time use a Lambda expression. The reader should notice how the Lambda can replace the anonymous class presented above:

System.out.println(        map(radius -> { return Math.sqrt(radius) * Math.PI; },            Arrays.asList(1., 2., 3., 4.)));

That looks concise and clear - note how the Lambda expression lacks any explicit type information. No explicit template instantiation, no method signatures.

A Lambda expression consists of two parts, which are separated by a ->. The first part denotes an argument list, the second part contains the actual implementation.

The Lambda expression serves the exact same purpose as the anonymous class, however it gets rid of lots of boilerplate code that the compiler can infer automatically anyway. Let's compare the two approaches once again and then analyze, what work the compiler takes off the developer's back.

MapFunction
functionLambda = radius -> Math.sqrt(radius) * Math.PI;MapFunction
functionClass = new MapFunction
() { public Double apply(Double radius) { return Math.sqrt(radius) * Math.PI; } };
  • For a Lambda implementation that consists of only one expression, the return statement and the curly braces can be omitted. This makes it even shorter.
  • The return type of the Lambda expression is infered from the Lambda implementation.
  • I am not completely sure about the argument types, but I think those must be infered from the context the Lambda expression is used in.
  • Finally the compiler has to check if the return type matches the context the Lambda is used in and if the argument types match the Lambda implementation.

This all can be done during compile time, there is no runtime overhead at all.

Conclusion

All in all, the concept of Lambdas in Java is neat. I allows for more concise and clearer code and reliefs the programmer from writing boilerplate code that can be infered by the compiler anyway. It's syntactic sugar, as shown above it's nothing that cannot also be achieved by using anonymous classes. However, I would say it is very sweet syntactic sugar.

On the other hand, Lambdas also allow for code that is much more obfuscated and harder to debug. The Python community realized this long ago - although Python has Lambdas too, it is generally considered bad style to use them extensively (it is not hard to avoid them when nested functions can be used). For Java I would give similar advice. Without doubt there are situations in which the use of Lambdas can lead to significantly shorter and more readable code, mostly in connection with streams. In other situations one is most likely better off if one resorts to more conservative approaches and best practices.

Links

  • The  for Java Lambdas.
  • The package  contains lots of different Lambda interfaces and reliefs the programmer from introducing own interfaces like it was done withMapFunction above.
  •  describes how to use Lambdas for other method types like instance methods or constructors.

Comments

转载于:https://my.oschina.net/u/3134761/blog/1921231

你可能感兴趣的文章
容灾备份的7个等级
查看>>
疯狂ios讲义疯狂连载之实现游戏视图控制器
查看>>
虚拟机XP系统---扩展磁盘空间
查看>>
Windows 10 MSDN官方原版ISO镜像(简体中文)下载
查看>>
大数据与CT,IT的关系?
查看>>
利用腾讯企业邮箱zabbix3.x邮件告警详细配置(微信/QQ/短信通知)
查看>>
软件自动化测试框架
查看>>
Centos7系列(七)逻辑卷详解
查看>>
Kafka笔记整理(三):消费形式验证与性能测试
查看>>
RHCE 学习笔记(19) 进程的优先级
查看>>
GET*** 测试
查看>>
Kvm之Lvm存储测试
查看>>
智能&大数据时代,架构师思维的十个学习步骤(优化版)
查看>>
nginx + uwsgi + Django 应用部署
查看>>
实战:使用WindowsPE备份和还原系统
查看>>
27.将 VMware 服务器上的虚拟机备份到 Azure(上)
查看>>
巧用Windows Phone应用商城中的应用链接
查看>>
自我反省系列——粗心导致GG同步失效
查看>>
【cocos2d-x从c++到js】22:使用非侵入方式扩展UI系统接口的举例
查看>>
颠覆与重构——戴尔助力徐工集团等行业客户实现业务转型
查看>>