详解 lambda

简介: 本文主要从lambda的引入原因,lambda介绍,lamba用法及底层实现原理等几方面详细介绍了lambda的相关知识。

一 前言

2014年3月19日java8正式发布,随之而来的lambda表达式也正式进入人们的视野,7年过去了,lambda表达式早已经成为java日常开发的一部分,lambda表达式为java带来的影响也日渐加深,下面让我们再来一起回顾下lambda相关知识。

二 为什么引入lambda

在回顾lambda之前,让我们再来探讨下,为什么需要引入lambda表达式,主要是为了解决什么问题。

2.1 代码更加简洁,灵活

日常开发中,一个众所周知的问题就是,需求是一直变化的,无论你开发什么业务,需求总是会变化的,为了适应这些变化最大限度减少工作量,我们尽量封装代码,以便复用,然而现有的语法对于这种支持是有限的,下面举个简单例子说明下,假如现在我有一批产品:

1 第一个需求,我需要筛选出所有颜色为绿色的产品

publicstaticList<Product>filterGreenProducts(List<Product>productList) {
List<Product>result=newArrayList<>(); 
for(Productproduct: productList){
if( "green".equals(product.getColor() ) { 
result.add(product);
         }
     }
returnresult;
}


2 对方法封装下,加入一个参数,传入具体的颜色,以便后续可以筛选其他颜色

publicstaticList<Product>filterColorProducts(List<Product>productList,Stringcolor) {
List<Product>result=newArrayList<>(); 
for(Productproduct: productList){
if(color.equals(product.getColor() ) { 
result.add(product);
         }
     }
returnresult;
}


3 现在需求变化了,我需要获取价格大于100的所有产品,上面的方法突然无法复用了,你只能再去重新写一个方法,为了后面复用,你也加了一个价格的参数

publicstaticList<Product>filterPriceProducts(List<Product>productList,doubleprice) {
List<Product>result=newArrayList<>(); 
for(Productproduct: productList){
if(price>product.getPrice() ) { 
result.add(product);
         }
     }
returnresult;
}


4 当然还会有一堆其他业务变更需求,比如筛选金牌会员的产品,筛选价格大于100的绿色产品等等,这时会发现,上述这种代码写法,都无法重用,无法适应需求的变化。

5 当然还可以通过引入策略模式的思想去解决这些问题

publicinterfaceProductPredicate{
booleantest (Productproduct);
}
publicclassProductPriceWeightPredicateimplementsProductPredicate{ 
publicbooleantest(Productproduct){
returnproduct.getPrice() >100;
 }
}
publicclassProductColorPredicateimplementsProductPredicate{ 
publicbooleantest(Productproduct){
return"green".equals(product.getColor());
 }
}
publicclassProductColorAndPricePredicateimplementsProductPredicate{ 
publicbooleantest(Productproduct){
return"green".equals(product.getColor()) &&product.getPrice() >100;
 }
}
publicstaticList<Product>filterProducts(List<Product>productList,ProductPredicatep){
List<Product>result=newArrayList<>();
for(Productproduct: productList){
if(p.test(product)){ 
result.add(product);
    }
 }
returnresult;
}

6 如上述代码所示,策略模式的确可以适应这种需求变化,但是随着需求的变更,需要创建大量的类,而且也不能很好的适应多维度的筛选。当然我们还可以不创建类,而是直接使用匿名内部类的方式来实现这些需求,但也同样会造成代码非常臃肿,而且也不能更好的适应更多的变化。

List<Product>redProducts=filteProducts(inventory, newProductPredicate() { 
publicbooleantest(Productproduct){
return"red".equals(product.getColor());
 }
});

7 上述的策略模式或使用匿名内部类,都表达出了一种行为参数化的思想,即我们进一步,把筛选这个行为进行抽象,然后再各个使用的地方调用,然而现有语法,无法让这种思想能有更优雅的写法,以及更灵活的处理。比如如果还需要加上对产品的排序,转换值等等,处理起来都比较麻烦。所以lambda的引入,很好的解决了这个问题,下面让我们来看看,如果使用lambda表达式,会怎么写:

List<Product>redProducts=filter(productList, (Productproduct) ->"green".equals(product.getColor()));
List<Product>redProducts=filter(productList, (Productproduct) ->product.Price()>100);

通过代码的对比,可以明显看出lambda的优势之处,将使代码更加简洁和灵活。

2.2 StreamApI

lambda的引入也是为了StreamApI的引入做好基础。至于StreamApI 这里就不再扩展去说了

2.3 函数式编程思想

从面向过程编程到面向对象编程,期间积累了无数人的经验和总结,也淘汰了太多曾经优秀的编程语言,java在面向对象编程中,也占据了非常核心的位置,然而新的函数式编程思想已经开始兴起并逐渐走向成熟,lambda的引入以及Java 8中的主要变化反映了它开始远离常侧重改变现有值的经典面向对象思想,而向函数式编程领域转变。语言需要不断改进以跟进硬件的更新或满足程序员的期待。


下面让我再正式开始介绍下lambda,或者说回顾下lambda。

三 lambda 简介

lambda 一词其实是来自于学术界开发出来的一套用来描述 计算的λ演算法,是一套从数学逻辑中发展,以变量绑定和替换的规则,来研究函数如何抽象化定义、函数如何被应用以及递归的形式系统。它由数学家阿隆佐·邱奇在20世纪30年代首次发表。这里就不再介绍。

3.1 行为参数化

前面已经简单的说过行为参数化的思想,这里再总结下,简单来说行为参数化是应对不断变化的需求,应当把变化的行为参数化,可直接传递。这种模式可以把一个行为(一段代码)封装起来,并通过传递和使用创建的行为将方法的行为参数化。前面提到过,这种做法类似于策略设计模式。能够轻松地适应不断变化的需求。

java的值以前由原始值,对象引用等组成,程序执行时只能传递值,现在把函数也正式加入到值里面,可通过参数传递函数。

3.2 lambda定义

Lambda表达式可理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

lambda语法:

(parameters) -> expression

(parameters) -> { statements; }

箭头前是参数,可以为空,只有(),箭头后面是具体的lambda主体,可单个,如果有多个语句需要用花括号包围。

下面是lambda具体使用案例:

牛牛截图20210907204555.png


3.3 函数式接口

3.3.1 介绍

lambda具体使用需要与函数式接口配合,下面再介绍下函数式接口:

简单来说函数式接口就是只定义一个抽象方法的接口,可以包含其他默认方法。一般用@FunctionalInterface注解进行标注,如果你用@FunctionalInterface定义了一个接口, 而它却不是函数式接口的话,编译器将返回一个提示原因的错误。表明存在多个抽象方 法。请注意,@FunctionalInterface不是必需的,但对于为此设计的接口而言,使用它是比较好的做法。

比如前面的ProductPredicate接口 就是一个函数式接口。

Lambda表达式允许你直接以内联的形式为函数式接口的抽象方 法提供实现,并把整个表达式作为函数式接口的实例,当然直接使用匿名内部类也是可以的,只不过就会比较臃肿。

3.3.2 函数式描述符

函数式接口的抽象方法的签名基本上就是Lambda表达式的签名,我们将这种抽象方法叫作函数描述符,比如(Project, Project) -> int 表示这个lambda接受两个Project类作为参数且返回int的函数。

lambda表达式需要从赋值的上下文、方法调用的上下文(参数和返回值),以及类型转换的上下文中获得目标类型,对应的函数式接口方法签名 也必须符合这些参数类型及返回值类型,不然就无法编译通过,当然这种对应关系,也会让一个lambda可以对应多个函数式接口。比如下面两种方式都是可以的

// Predicate返回了一个booleanPredicate<String>p=s->list.add(s);
// Consumer返回了一个voidConsumer<String>b=s->list.add(s);


同时lambda也可以通过上下文推断出参数的类型,从而可以省略类型

Comparator<Product>c=  (Producta1, Producta2) ->a1.getWeight().compareTo(a2.getWeight()); ←─没有类型推断Comparator<Product>c=  (a1, a2) ->a1.getWeight().compareTo(a2.getWeight()); ←─有类型推断


3.3.3 常用函数式接口

函数式接口构造比较简单,可以自己定义,Java API也为我们提供了一些常用的函数式接口,下面介绍几种比较常用的函数式接口。

1 Predicate

java.util.function.Predicate接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。

比如说前面对产品列表的筛选,就可以改成直接使用Predicate接口。

@FunctionalInterfacepublicinterfacePredicate<T>{
booleantest(Tt);
}
publicstaticList<Product>filterProducts(List<Product>productList,Predicatep){
List<Product>result=newArrayList<>();
for(Productproduct: productList){
if(p.test(product)){ 
result.add(product);
    }
 }
returnresult;
}
List<Product>redProducts=filter(productList, (Productproduct) ->product.Price()>100);


2 Consumer

java.util.function.Consumer定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。

@FunctionalInterfacepublicinterfaceConsumer<T>{
voidaccept(Tt);
}
publicstatic<T>voidforEach(List<T>list, Consumer<T>consumer){
for(Ti: list){
consumer.accept(i);
 }
}
forEach(productList, (Productproduct) ->System.out.println(product.getName()); ←─Lambda是Consumer中accept方法的实


3 Function

java.util.function.Function接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口。

@FunctionalInterfacepublicinterfaceFunction<T, R>{
Rapply(Tt);
}
publicstatic<T, R>List<R>map(List<T>list,Function<T, R>f) {
List<R>result=newArrayList<>();
for(Ts: list){
result.add(f.apply(s));
 }
returnresult;
}
List<Integer>l=map(productList,(Productproduct) ->product.getName());←─Lambda是Function接口的apply方法的实现


下面总结了Java API中提供的最常用的函数式接口及其函数描述符,可供参考

牛牛截图20210907205506.png

拆箱和装箱会造成很多不必要的消耗,所以其中有不少函数式接口,都是专门针对基本数据类型加的,以便在输入和输出都是原始类型时避免自动装箱的操作。比如,在下面的代码中,使用IntPredicate就避免了对 值1000进行装箱操作,但要是用Predicate就会把参数1000装箱到一 个Integer对象中。

当然这也是java后续需要改进的地方,即泛型中不能使用基本数据类型,当然这也是历史原因造成的,后面如果可以泛型具体化,将会方便很多,但目前直到最新的jdk16,也没看到有这方面的改进。

publicinterfaceIntPredicate{
booleantest(intt);
}
IntPredicateevenNumbers= (inti) ->i%2==0;
evenNumbers.test(1000); ←─true(无装箱)Predicate<Integer>oddNumbers= (Integeri) ->i%2==1;
oddNumbers.test(1000); ←─false(装箱)


3.4 方法引用

方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。在一些情况下,比 起使用Lambda表达式,它们更易读,也更自然。

3.4.1 方法引用。

(1) 指向静态方法的方法引用

例如Integer的parseInt方法,写 作Integer::parseInt。

(2) 指向任意类型实例方法的方法引用

例如String的length方法,写 作String::length。

(3) 指向现有对象的实例方法的方法引用

例如Product的weight方法,写作product::weight

3.4.2 构造函数的引用

类的构造函数也可以直接使用这种方式简写比如

//无参数构造函数Supplier<Product>c1=Product::new; ←─构造函数引用指向默认的Product()构造函数Producta1=c1.get(); ←─调用Supplier的get方法将产生一个新的Product//一个参数构造函数Function<Integer, Product>c2= (weight) ->newApple(Product);用要求的重量创建一个Product的Lambda表达式Producta2=c2.apply(110);调用该Function函数的apply方法,并给出要求的重量,将产生一个新的Product对象//多个参数构造函数......

 

不同的构造函数简写方式相同,但是返回的函数类型却不同,如果有多个参数的构造函数需要接受,需要自己去实现一些函数式接口。

3.5 复合应用

Java API 提供的很多函数式接口都提供了可以进行复合的默认方法,即把多个简单的lambda复合成复杂的表达式,也可以把多个lambda组成流水线进行操作,常见的复合操作如下所示:

//比较器productList.sort(Comparator.comparing(Product::getWeight).reversed().thenComparing(Product::price)); 
//谓词Predicate<Product>catCodePredicate= (Productproduct) ->product.getCatCode() >100;
Predicate<Product>predicate=catCodePredicate.and(a->a.getPrice>100).or(a->a.getWeight() >150); 
//函数Function<Integer, Integer>f=x->x+1;
Function<Integer, Integer>g=x->x*2;
Function<Integer, Integer>h=f.andThen(g); ←─数学上会写作g(f(x))
Function<Integer, Integer>k=h.compose(g); ←─数学上会写作h(g(x))
intresult=h.apply(1);


四 lambda的使用

前面已经简单的介绍了下lambda的基本使用方法,下面再简单介绍下lambda常用使用场景

4.1 改善代码的可读性及灵活性

4.1.1 重构代码,用lambda表达式取代匿名类

lambda最常用的一个重构点就是取代匿名类了,匿名类太过繁琐,可读性比较差,可将实现单一抽象方法的匿名类替换成lambda,但替换的时候需要注意以下两点:

1 在匿名类中,this代表的是类自 身,但是在Lambda中,它代表的是包含类。其次,匿名类可以屏蔽包含类的变量,而 Lambda表达式不能。

2 两个不同的匿名类如果函数式接口的上下文一致,直接使用lambda替换时会引起误解。比如如下代码:

interfaceTask{
publicvoidexecute();
}
publicstaticvoiddoSomething(Runnabler){ r.run(); }
publicstaticvoiddoSomething(Taska){ a.execute(); }
//使用lambda时,无法确认具体调用的是哪个方法doSomething(() ->System.out.println("Danger danger!!"));


4.1.2 使用Stream

lambda最常见的使用场景其实就是结合SreamAPI进行使用,所以也有人认为,lambda的引入主要就是为了Stream,不过Stream已经是很常用的技术,也都很熟悉了,这里就不再扩展。

4.1.3 使用方法引用代替lambda

   方法引用非常简洁清晰,尽量多使用方法引用,如果lambda主体中内容较多,建议单独新建方法,通过方法引用调用,更加简洁清晰。否则直接使用lambda反而让代码更加臃肿,难以理解。

4.1.4 环绕执行

lambda是传递行为的,所以如果一些方法,前后都有着同样的固定业务代码,仅仅只是一些行为不同,那完全可以将行为抽象出来,用lambda代替,合并成一个方法。比如对资源处理的相关方法,模板方法等等。

4.1.5 延迟执行

lambda还有个特点就是延迟执行,如果你发现你需要频繁地从客户端代码去查询一个对象的状态(比如判断日志的级别),只是为了传递参数、调用该对象的一个方法(比如输出一条日志),那么可以考虑实现一个新的方法,以Lambda或者方法表达式作为参数,新方法在检查完该对象的状态之后才调用原来的方法。

4.1.6 替换一些设计模式代码

设计模式可以让代码更加灵活清晰,但也会因此带来大量代码,比如策略模式和模板方法模式,如果只是简单的一些策略算法,往往也需要新建大量代码,反而会变的臃肿,如果用lambda直接代替,会使代码简洁很多。比如前面所列举的对产品列表进行筛选处理的业务,用lambda替换策略模式代码后,代码更加简洁,也比之前灵活。但需要注意,如果本身算法比较复杂,代码较多,替换成lambda反而会适得其反,需要自己掌握平衡。

4.2 函数式编程

前面介绍过lambda是引入的函数式编程思维,函数式编程是一个很大的概念,简单来说就是一种使用函数进行编程的方式,是对声明式编程的具体实践。这里不再过多论述,但我们也可以在使用lambda的时候,应用一些函数式编程技巧:

4.2.1 高级函数

如果函数能满足下面任一要求就 可以被称为高阶函数(higher-order function): 1 接受至少一个函数作为参数。 2 返回的结果是一个函数。

高级函数中,函数不仅可以作为参数传递,还可以作为结果返回,能赋值给本地变量,也可以插入到某个数据结构。

interfaceTask{
publicvoidexecute();
}
publicstaticvoiddoSomething(Runnabler){ r.run(); }
publicstaticvoiddoSomething(Taska){ a.execute(); }
//使用lambda时,无法确认具体调用的是哪个方法doSomething(() ->System.out.println("Danger danger!!"));


使用高级函数,可以扩大lambda使用场景,把多个函数结合在一起使用,使代码更加灵活。但使用高级函数时需要注意,最好是只写无副作用的高级函数,无副作用是指如果一个方法既不修改它内嵌类的状态,也不修改其他对象的状态,使用return返回所有的计算结果,那么我们称其为纯粹的或者无副作用的。如果一个高级函数是有副作用的,那就可能会发生无法预料的问题,也不好定位。

4.2.2 科里化

科里化 是一种将具备多个参数(比如,x和y)的函数f转化为使用一个参数的函数g, 并且这个函数的返回值也是一个函数,它会作为新函数的一个参数。后者的返回值和初 始函数的返回值相同,即f(x,y) = (g(x))(y)。

比如:

//三个参数原始方法staticdoubleconverter(doublex, doublef, doubleb) {
returnx*f+b;
   }
//可转为两个参数的方法staticDoubleUnaryOperatorcurriedConverter(doublef, doubleb){
return (doublex) ->x*f+b;
   }
//调用DoubleUnaryOperatorconvert=curriedConverter(9.0/5, 32);
doublenum=convert.applyAsDouble(1000);


通过科里化,我们可以模块化函数、提高代码重用性的技术。

函数式编程中还有持久化数据结构,延迟列表,模式匹配等特性,但Java支持的不太友好,其中模式匹配,直到目前最新的java16版本,也没有引入,所以这里就不再过多展开。


lambda还有一些比较有用知识点,比如怎么对lambda跟踪测试,使用lambda构建DSL语言等等,这里就不再扩展,下面我们直接讨论下lambda的底层实现原理。

五 lambda底层实现

5.1 匿名内部类的底层实现

      在了解lambda底层实现前,我们先回顾下匿名内部类的底层实现,编译器会为每个匿名类生成一个新的.class文件。这些新生成的类文件的文件名通常以ClassName$1这种形式呈现,其中ClassName是匿名类出现的类的名字,紧跟着一个美元符号和一个数字。然而生成大量的类文件是不利的,因为每个类文件在使用之前都需要加载和验证,这会直接影响应用的启动性能。而且每个新的匿名类都会为类或者接口产生一个新的子类型。

如果将Lambda表达式转换为匿名类,每个Lambda表达式都会产生一个新的类文件,如果你为了实现一个比较器,使用了一百多个不同的Lambda表达式,这意味着该比较器会有一百多个不同的子类型。这种情况下,JVM的运行时性能调优会变得更加困难。

可以通过 java -c -v 查看任何类文件的字节码和常量池。

假如我们现在有这样一个类:

publicclassInnerClassTest {
publicstaticvoidtestInnerClass(){
Consumer<String>consumer=newConsumer<String>() {
@Overridepublicvoidaccept(Strings) {
System.out.println(s+"innner class");
            }
        };
consumer.accept("test ");
    }
publicstaticvoidmain(String[] args) {
testInnerClass();
    }
}


那可以看下底层类的字节码:

{
publiccom.InnerClassTest();
descriptor: ()Vflags: ACC_PUBLICCode:
stack=1, locals=1, args_size=10: aload_01: invokespecial#1// Method java/lang/Object."<init>":()V4: returnLineNumberTable:
line10: 0publicstaticvoidtestInnerClass();
descriptor: ()Vflags: ACC_PUBLIC, ACC_STATICCode:
stack=2, locals=1, args_size=00: new#2// class com/InnerClassTest$13: dup4: invokespecial#3// Method com/InnerClassTest$1."<init>":()V7: astore_08: aload_09: ldc#4// String test11: invokeinterface#5,  2// InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V16: returnLineNumberTable:
line13: 0line19: 8line20: 16publicstaticvoidmain(java.lang.String[]);
descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:
stack=0, locals=1, args_size=10: invokestatic#6// Method testInnerClass:()V3: returnLineNumberTable:
line23: 0line24: 3}
SourceFile: "InnerClassTest.java"InnerClasses:
static#2; //class com/InnerClassTest$1


jvm会在编译器就生成一个LambdaTest$$1类,字节码直接操作new,创建一个InnerClass$1类型的对象引用。然后压入栈里面。

5.2 lambda的底层实现

那么lambda底层的字节码,会怎么处理呢?

我们把上面的方法中的匿名内部类换成lambda,编译后,再查看类及字节码信息:

publicclassLambdaTest {
publicstaticvoidtestLambda(){
Consumer<String>consumer=a->System.out.println(a+"lambda");
consumer.accept("test ");
    }
publicstaticvoidmain(String[] args) {
testLambda();
    }
}
// javap -c -p 查看所有类和成员publicclasscom.LambdaTest {
publiccom.LambdaTest();
publicstaticvoidtestLambda();
publicstaticvoidmain(java.lang.String[]);
privatestaticvoidlambda$testLambda$0(java.lang.String);
}
// javap -c -v 查看所有字节码信息{
publiccom.LambdaTest();
descriptor: ()Vflags: ACC_PUBLICCode:
stack=1, locals=1, args_size=10: aload_01: invokespecial#1// Method java/lang/Object."<init>":()V4: returnLineNumberTable:
line10: 0publicstaticvoidtestLambda();
descriptor: ()Vflags: ACC_PUBLIC, ACC_STATICCode:
stack=2, locals=1, args_size=00: invokedynamic#2,  0// InvokeDynamic #0:accept:()Ljava/util/function/Consumer;5: astore_06: aload_07: ldc#3// String test9: invokeinterface#4,  2// InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V14: returnLineNumberTable:
line13: 0line14: 6line15: 14publicstaticvoidmain(java.lang.String[]);
descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:
stack=0, locals=1, args_size=10: invokestatic#5// Method testLambda:()V3: returnLineNumberTable:
line18: 0line19: 3}
SourceFile: "LambdaTest.java"InnerClasses:
publicstaticfinal#68=#67of#71; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandlesBootstrapMethods:
0: #28invokestaticjava/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Methodarguments:
#29 (Ljava/lang/Object;)V#30invokestaticcom/LambdaTest.lambda$testLambda$0:(Ljava/lang/String;)V#31 (Ljava/lang/String;)V


从上面信息码可以看出,lambda表达式底层实现与匿名内部类有很大不同,首先编译时lambda并没有被编译成一个单独的类,而是生成了一个私有的静态方法lambdatestLambda0(java.lang.String),其次在运行时,在调用lambda的时候,会生成一个匿名内部类,调用之前生成的私有静态方法,从而完成lambda的底层调用逻辑。

而生成这些代码就需要lambda元工厂LambdaMetafactory类,每个lamda的调用都会通过LambdaMetafactory生成链接CallSite,再结合字节码命令invokedynamic,进行调用。

造成lambda和匿名内部类底层出现很大区别的原因之一,就是lambda底层是使用的invokedynamic命令。字节码指令invokedynamic最初被JDK7引入,用于支持运行于JVM上的动态类型语言。执行方法调用时,invokedynamic添加了更高层的抽象,使得一部分逻辑可以依据动态语言的特征来决定调用目标。

使用invokedynamic指令可以延迟Lambda表达式到字节码的转换,最终这一操作被推迟到了运行时。换句话说,以这种方式使用invokedynamic,可以将实现Lambda表达式的这部分代码的字节码生成推迟到运行时。

这种设计选择带来了一系列好结果。

1 Lambda表达式的代码块到字节码的转换由高层的策略变成了纯粹的实现细节。它现在可以动态地改变,或者在未来版本中得到优化、修改,并且保持了字节码的后向兼容性。

2 没有带来额外的开销,没有额外的字段,也不需要进行静态初始化,而这些如果不使用Lambda,就不会实现。

3 对无状态非捕获型Lambda,我们可以创建一个Lambda对象的实例,对其进行缓存,之后对同一对象的访问都返回同样的内容。这是一种常见的用例,也是人们在Java 8之前就惯用的方式;比如,以static final变量的方式声明某个比较器实例。

4 没有额外的性能开销,因为这些转换都是必须的,并且结果也进行了链接,仅在Lambda首次被调用时需要转换。其后所有的调用都能直接跳过这一步,直接调用之前链接的实现。

如果想进一步了解lambda底层的实现,可以参考这个文档:

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html

六 总结

本文主要从lambda的引入原因,lambda介绍,lamba用法及底层实现原理等几方面详细介绍了lambda的相关知识。lambda本质是一种对行为的抽象,所以使用lambda时,很重要的一点就是要学会对行为进行抽象。lambda的引入对于java来说是一项重大的变革,不仅体现在优雅的代码上,还有结合Stream对多核cpu的利用,以及函数式思想的引入。

Java也由此开始慢慢接入函数式编程思想。函数式编程思想也是我本文多次提到,并在4.2中简单的做了个介绍,并未展开,但这其实才是需要深入研究的。

距离Java8发布已过去7年,这7年来为了快速适应变化,Java也加快了自己的发布频率,截止目前,Java已经发布到Java16版本,对于函数式编程思想的引入也越来越多。所以没有哪个编程语言能一直强大,只有永远不断改进变化,生命力才能更加持久。








相关文章
|
6月前
lambda中orElse(null)使用
lambda中orElse(null)使用
150 0
|
11月前
|
Python
lambda
lambda 是一个匿名函数,它通常用于简化代码,使代码更简洁、易读。lambda 函数不需要显式地使用 def 关键字进行定义,可以直接在表达式中使用。它的语法如下:
170 6
|
11月前
|
SQL Java 程序员
聊聊lambda
聊聊lambda
44 1
|
6月前
|
C#
C# Lambda
C# Lambda
41 0
|
11月前
获取lambda
获取lambda
49 0
|
11月前
lambda小技巧
lambda小技巧
55 0
|
安全 架构师 Java
必须掌握的 Lambda 表达式
必须掌握的 Lambda 表达式
5897 1
必须掌握的 Lambda 表达式
|
设计模式 Java API
值得使用Lambda的8个场景,别再排斥它了!
前言 可能对不少人来说,Lambda显得陌生又复杂,觉得Lambda会导致代码可读性下降,诟病Lambda语法,甚至排斥。
|
算法 编译器 容器
lambda
lambda
100 0
|
Java 开发者
lambda让代码更优雅
Lambda表达式是Java 8中引入的一个重要特性,它允许开发者以更简洁的方式编写匿名函数,使得代码更加紧凑和易读。Lambda表达式是函数式编程的一种体现,可以将函数作为方法的参数传递,并且可以使用更简洁的语法实现函数式接口(只有一个抽象方法的接口)的实例化。Lambda表达式的语法形式为 (参数列表) -> {表达式或语句块}。
80 0