并发度低的小伙伴们好,我是闪客。Lambda表达式很方便,一般在项目中用在流式编程中。ListstudentList=gen();Mapmap=studentList.stream().collect(Collectors.toMap(Student::getId,a->a,(a,b)->a));理解一个Lambda表达式分为三步:1.确认Lambda表达式的类型2.找到要实现的方法3.只有这三步才能实现这个方法,没有别的。而且每一个步骤都非常非常简单,所以我会分开来解释,你就明白了。确认Lambda表达式的类型可以用Lambda表达式表示,必须是函数式接口,函数式接口是只有一个抽象方法的接口。让我们看看JDK中非常熟悉的Runnable接口是什么样子的。@FunctionalInterfacepublicinterfaceRunnable{publicabstractvoidrun();}这是一个标准的功能接口。因为只有一个抽象方法。而这个接口上有一个注解@FunctionalInterface,它只是帮助你在编译时检查你的接口是否满足函数式接口的条件。比如你没有任何抽象方法,或者有多个抽象方法,编译都不会通过。//没有实现任何抽象方法的接口@FunctionalInterfacepublicinterfaceMyRunnable{}//编译后控制台显示如下信息Error:(3,1)java:Unexpected@FunctionalInterfaceannotationMyRunnableisnotafunctionalinterfaceNoabstractmethodfoundininterfaceMyRunnable稍微复杂一点,Java8之后,接口中允许使用默认方法和静态方法,这些都不是抽象方法,所以也可以在函数式接口中加入。看看一个你可能不熟悉但又有点熟悉的界面。@FunctionalInterfacepublicinterfaceConsumer{voidaccept(Tt);defaultConsumerandThen(Consumerafter){...}}看,只有一个抽象方法和一个默认方法(方法体代码为省略),这并不影响它是一个功能接口。让我们看一个更复杂的,有更多的静态方法,它也是一个功能接口,因为它仍然只有一个抽象方法。亲身体验吧。@FunctionalInterfacepublicinterfacePredicate{booleantest(Tt);defaultPredicateand(Predicateother){...}defaultPredicatenegate(){...}defaultPredicateor(Predicateother){...}staticPredicateisEqual(ObjecttargetRef){...}staticPredicatenot(Predicatetarget){...}}first不管这些方法是做什么用的,这些类中比比皆是Stream设计的方法,我们先记住这句话,Lambda表达式需要的类型是函数式接口,函数式接口中只有一个抽象方法,就够了,以上三个例子都是函数式接口。恭喜,你已经学会了Lambda表达式中最难的部分,即函数式接口。找到要实现的方法一个Lambda表达式就是实现一个方法,什么方法?就是刚才那些函数式接口中的抽象方法。那也太简单了吧,因为函数式接口只有一个抽象方法,找一个就行了。我们试着找出刚才那几个函数式接口的抽象方法。@FunctionalInterfacepublicinterfaceRunnable{publicabstractvoidrun();}@FunctionalInterfacepublicinterfaceConsumer{voidaccept(Tt);defaultConsumerandThen(Consumerafter){...}}@FunctionalInterfacepublicinterfacePredicate{booleantest(Tt);defaultPredicateand(Predicateother){...}defaultPredicatenegate(){...}defaultPredicateor(Predicateother){...}staticPredicateisEqual(ObjecttargetRef){...}staticPredicatenot(Predicatetarget){...}}好的,开始吧,简单!要实现这个方法,Lambda表达式就是实现这个抽象方法。如果你不会用Lambda表达式,你一定知道如何用匿名类来实现它,对吧?比如我们刚才实现了Predicate接口的匿名类。Predicatepredicate=newPredicate(){@Overridepublicbooleantest(Strings){returns.length()!=0;}};如果换成Lambda表达式呢?像这样。Predicatepredicate=(Strings)->{返回。长度()!=0;};看到了吗?这个Lambda语法由三部分组成:参数块:就是前面的(Strings),简单的把要实现的抽象方法的参数原封不动的写在这里。小箭头:是->符号。代码块:这里原样写了要实现的方法。不过这种写法大家一定不陌生,连idea都帮不上我们简化成这种怪模样,别着急,我来变形一下!其实就是格式的简化。先看参数部分,(Strings)中的类型信息是多余的,因为可以被编译器推导出来,去掉。Predicatepredicate=(s)->{返回。长度()!=0;};当只有一个参数时,括号也可以去掉。Predicatepredicate=s->{返回。长度()!=0;};再看代码块,方法体只有一行代码,去掉花括号和return关键字即可。Predicatep=s->s.length()!=0;这看起来很熟悉吗?来,我们再实现一个Runnable接口。@FunctionalInterfacepublicinterfaceRunnable{publicabstractvoidrun();}Runnabler=()->System.out.println("Iamrunning");你看,这个方法没有入参,所以前面括号里的参数都没有了。在这种情况下,括号是不能省略的。通常我们在快速创建一个新线程并启动时,是不是像下面这样写的,大家是不是很熟悉呢?newThread(()->System.out.println("Iamrunning")).start();在多入参之前,我们只尝试了一个入参。接下来,让我们看一下多个输入参数。@FunctionalInterfacepublicinterfaceBiConsumer{voidaccept(Tt,Uu);//defaultmethodsremoved}看一个用法是否一目了然。BiConsumerrandomNumberPrinter=(random,number)->{for(inti=0;i{Rapply(Tt,Uu);//defaultmethodsremoved}//看例子BiFunctionfindWordInSentence=(word,sentence)->sentence.indexOf(word);找到规律就OK了吗?看了这么多例子,不知道大家有没有发现其中的规律呢?其实函数式接口中的抽象方法无非就是输入参数的个数和返回值的类型。输入参数的个数可以是一个或两个,返回值可以是void,也可以是boolean,也可以是类型。这些情况的排列组合就是JDK提供的java.util.function包下的类。别看晕了,我们分类就好了。用户,例如DoubleFunction。@FunctionalInterfacepublicinterfaceDoubleFunction{Rapply(doublevalue);}这个可以通过比较自由的函数式接口Function来实现。@FunctionalInterfacepublicinterfaceFunction{Rapply(Tt);}那我们不妨先去掉这些特定类型的函数式接口(我还偷偷去掉了XXXOperator的几个类,因为它们都继承了其他函数式接口),然后再排序再次查看还剩下什么。ConsumerFunctionPredicateBiConsumerBiFunctionBiPredicateSupplier哇,差点没了,接下来重点说说这些。这里我只列出类和对应的抽象方法Consumervoidaccept(Tt)FunctionRapply(Tt)Predicatebooleantest(Tt)BiConsumervoidaccept(Tt,Uu)BiFunctionRapply(Tt,Uu)BiPredicatebooleantest(Tt,Uu)SupplierTget()你看到模式了吗?上面的简单分类是:supplier:没有输入参数,有返回值。consumer:有入参,无返回值。predicate:有输入参数,返回布尔值function:有输入参数,有返回值,有bi前缀,即有两个输入参数,没有则只有一个这样的参数。OK,这些我们已经分清楚了。实际上,它为我们提供了一个函数模板。区别仅在于输入和返回参数的排列和组合。使用我们常用的Stream编程来熟悉下面的代码。如果你在你的项目中使用流式编程,你一定不会陌生。有一个学生列表,您想将其转换为地图。键是学生对象的id,值是学生对象。本身。ListstudentList=gen();Mapmap=studentList.stream().collect(Collectors.toMap(Student::getId,a->a,(a,b)->a));提取Lambda表达式的一部分。Collectors.toMap(Student::getId,a->a,(a,b)->a)由于我们还没有看到::形式,所以我们先回到原来的形式,这里只是一个热身为你。Collectors.toMap(a->a.getId(),a->a,(a,b)->a)为什么要这么写呢?让我们看一下Collectors.toMap方法的定义。publicstaticCollector>toMap(FunctionkeyMapper,FunctionvalueMapper,BinaryOperatormergeFunction){returntoMap(keyMapper,valueMapper,mergeFunction,HashMap::new);}看,入参有3个,分别是Function,Function,BinaryOperator,其中BinaryOperator只是继承了BiFunction,扩展了几个方法,我们没有用到,所以我们可能也可以将其视为BiFunction。还记得Function和BiFunction吗?FunctionRapply(Tt)BiFunctionRapply(Tt,Uu)很容易理解。第一个参数a->a.getId()是Rapply(Tt)的实现。输入参数是Student类型的对象a,返回a.getId()。第二个参数a->a也是Rapply(Tt的实现),入参是Student类型的a,返回a本身的第三个参数(a,b)->a是对的实现Rapply(Tt,Uu),入参为Student类型的a和b,返回为第一个入参a,在Stream中使用。当两个对象a和b的key相同时,value取第一个元素a。Stream中的第二个参数a->a表示从list转为map时,使用原始对象本身的值。你一定见过这种写法。Collectors.toMap(a->a.getId(),Function.identity(),(a,b)->a)可以这么写,给你看一下Function类的全貌你就明白了。@FunctionalInterfacepublicinterfaceFunction{Rapply(Tt);...staticFunctionidentity(){returnt->t;}}看,identity方法是帮我们表达的,公式是实现了,所以我们不用自己写,其实只是包装了一个方法。这次知道了一个函数式接口,为什么里面那么多包含了一堆默认方法和静态方法呢?就是为了这个目的。我们再试一个,Predicate中就有这样一个默认方法。@FunctionalInterfacepublicinterfacePredicate{booleantest(Tt);defaultPredicateand(Predicateother){Objects.requireNonNull(other);return(t)->test(t)&&other.test(t);}它可以用来做什么?我告诉你,如果没有这个方法,有一段代码你可以这样写。Predicatep=s->(s!=null)&&!s.isEmpty()&&s.length()<5;如果使用这种方法,可以变成下面的优雅形式。PredicatenonNull=s->s!=null;PredicatenonEmpty=s->s.isEmpty();PredicatesshorterThan5=s->s.length()<5;Predicatep=nonNull.and(nonEmpty).and(shorterThan5);亲身体验。方法引用,我们回头看看刚才Student::getId的写法。当方法体中只有一个方法调用时,这种简化是可能的。比如这个a->a.getId()只是调用了Student对象的getId()方法,所以可以写成Student::getId的形式。再看几个例子();FunctiontoLength=User::getName;Consumerprinter=s->System.out.println(s);Consumerprinter=System.out::println;如果是构造方法还可以简化。Supplier>newListOfStrings=()->newArrayList<>();Supplier>newListOfStrings=ArrayList::new;总结学习理解和编写Lambda表达式,不要忘记前三个步骤。1.确认Lambda表达式的类型。2.找到要实现的方法。3.实施方法。Lambda表达式的类型是函数式接口,要实现的方法是函数式接口中唯一的抽象方法。这个方法的实现方式是参数块+小箭头+方法体,其中参数块和方法体都可以在一定程度上简化它的写法。是不是很简单!以上代码示例均来源于官方教程。英语好的同学可以看看。最科学的Lambda表达式教程。今天这篇文章在https://dev.java/learn/tutorial/getting-to-know-the-language/lambda-expressions/lambdas.html,主要讲的是如何写Lambda表达式。至于原理,后面再说。这里介绍一下,你认为Lambda表达式是匿名类的简化吗?按照官方的说法,Lamda表达式在某些情况下是一种更简单的匿名类编写方式,但从字节码层面来说,它们是完全不同的。到底是怎么回事?带着这个问题,我们来给自己挖个坑。下面说说Lambda表达式背后的实现原理。【责任编辑:庞桂玉电话:(010)68476606】