GVKun编程网logo

Java lambda 表达式(javalambda表达式实现原理)

1

对于Javalambda表达式感兴趣的读者,本文将会是一篇不错的选择,我们将详细介绍javalambda表达式实现原理,并为您提供关于(转)Java8:一文掌握Lambda表达式、-source1.5

对于Java lambda 表达式感兴趣的读者,本文将会是一篇不错的选择,我们将详细介绍javalambda表达式实现原理,并为您提供关于(转)Java 8:一文掌握 Lambda 表达式、-source 1.5 中不支持 lambda 表达式 (请使用 -source 8 或更高版本以启用 lambda 表达式)、5.8 java 11 增强的 Lambda 表达式、C++11 lambda 表达式 (lambda expression)的有用信息。

本文目录一览:

Java lambda 表达式(javalambda表达式实现原理)

Java lambda 表达式(javalambda表达式实现原理)

Lambda 表达式的介绍

Lambda 表达式是 Java8 中最重要的新功能之一。使用 Lambda 表达式可以替代只有一个抽象函数的接口实现,告别匿名内部类,代码看 起来更简洁易懂。Lambda 表达式同时还提升了对集合、框架的迭代、 遍历、过滤数据的操作。 

Lambda 表达式的特点 

1:函数式编程

2:参数类型自动推断

3:代码量少,简洁

Lambda 表达式案例 

 

 

 

 Lambda 表达式应用场景 

任何有函数式接口的地方 

函数式接口 

只有一个抽象方法(Object 类中的方法除外)的接口是函数式接口 

Supplier 代表一个输出

Consumer 代表一个输入 BiConsumer 代表两个输入

Function 代表一个输入,一个输出(一般输入和输出是不同类型的) UnaryOperator 代表一个输入,一个输出(输入和输出是相同类型的)

BiFunction 代表两个输入,一个输出(一般输入和输出是不同类型的) BinaryOperator 代表两个输入,一个输出(输入和输出是相同类型的)

方法的引用 

方法引用是用来直接访问类或者实例的已经存在的方法或者构造 方法,方法引用提供了一种引用而不执行方法的方式,如果抽象 方法的实现恰好可以使用调用另外一个方法来实现,就有可能可 以使用方法引用 

方法引用的分类

 

静态方法引用:如果函数式接口的实现恰好可以通过调用一个静 态方法来实现,那么就可以使用静态方法引用

实例方法引用:如果函数式接口的实现恰好可以通过调用一个实 例的实例方法来实现,那么就可以使用实例方法引用

对象方法引用:抽象方法的第一个参数类型刚好是实例方法的类 型,抽象方法剩余的参数恰好可以当做实例方法的参数。如果函 数式接口的实现能由上面说的实例方法调用来实现的话,那么就 可以使用对象方法引用

构造方法引用:如果函数式接口的实现恰好可以通过调用一个类 的构造方法来实现,那么就可以使用构造方法引用 

 

 

参考代码:https://gitee.com/lm970585581/code_base/tree/master/java8

(转)Java 8:一文掌握 Lambda 表达式

(转)Java 8:一文掌握 Lambda 表达式

原文:https://zhuanlan.zhihu.com/p/90815478

 

List list = function.apply(10);

使用 Lambda 表达式,我们一般可以这样写:

Function<Integer, ArrayList> function = n -> new ArrayList(n);

使用「引用构造方法」的方式,我们可以简写成这样:

Function<Integer, ArrayList> function = ArrayList::new;

4. 自定义函数接口

自定义函数接口很容易,只需要编写一个只有一个抽象方法的接口即可,示例代码:

@FunctionalInterface
public interface MyInterface<T> {
    void function(T t);
}

上面代码中的 @FunctionalInterface 是可选的,但加上该注解编译器会帮你检查接口是否符合函数接口规范。就像加入 @Override 注解会检查是否重写了函数一样。

5. 实现原理

经过上面的介绍,我们看到 Lambda 表达式只是为了简化匿名内部类书写,看起来似乎在编译阶段把所有的 Lambda 表达式替换成匿名内部类就可以了。但实际情况并非如此,在 JVM 层面,Lambda 表达式和匿名内部类其实有着明显的差别。

5.1 匿名内部类的实现

匿名内部类仍然是一个类,只是不需要我们显式指定类名,编译器会自动为该类取名。比如有如下形式的代码:

public class LambdaTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World");
            }
        }).start();
    }
}

编译之后将会产生两个 class 文件:

LambdaTest.class
LambdaTest$1.class

使用 javap -c LambdaTest.class 进一步分析 LambdaTest.class 的字节码,部分结果如下:

public static void main(java.lang.String[]);
  Code:
     0: new           #2                  // class java/lang/Thread
     3: dup
     4: new           #3                  // class com/example/myapplication/lambda/LambdaTest$1
     7: dup
     8: invokespecial #4                  // Method com/example/myapplication/lambda/LambdaTest$1."<init>":()V
    11: invokespecial #5                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
    14: invokevirtual #6                  // Method java/lang/Thread.start:()V
    17: return

可以发现在 4: new #3 这一行创建了匿名内部类的对象。

5.2 Lambda 表达式的实现

接下来我们将上面的示例代码使用 Lambda 表达式实现,代码如下:

public class LambdaTest {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello World")).start();
    }
}

此时编译后只会产生一个文件 LambdaTest.class,再来看看通过 javap 对该文件反编译后的结果:

public static void main(java.lang.String[]);
  Code:
     0: new           #2                  // class java/lang/Thread
     3: dup
     4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
     9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
    12: invokevirtual #5                  // Method java/lang/Thread.start:()V
    15: return

从上面的结果我们发现 Lambda 表达式被封装成了主类的一个私有方法,并通过 invokedynamic 指令进行调用。

因此,我们可以得出结论:Lambda 表达式是通过 invokedynamic 指令实现的,并且书写 Lambda 表达式不会产生新的类。

既然 Lambda 表达式不会创建匿名内部类,那么在 Lambda 表达式中使用 this 关键字时,其指向的是外部类的引用。

6. 优缺点

优点:

  • 可以减少代码的书写,减少匿名内部类的创建,节省内存占用。
  • 使用时不用去记忆所使用的接口和抽象函数。

缺点:

  • 易读性较差,阅读代码的人需要熟悉 Lambda 表达式和抽象函数中参数的类型。
  • 不方便进行调试。

-source 1.5 中不支持 lambda 表达式   (请使用 -source 8 或更高版本以启用 lambda 表达式)

-source 1.5 中不支持 lambda 表达式 (请使用 -source 8 或更高版本以启用 lambda 表达式)

Warning:scala: skipping Scala files without a Scala SDK in module(s) JavaSparkTest

 

Error:(19, 23) java: -source 1.5 中不支持 lambda 表达式
  (请使用 -source 8 或更高版本以启用 lambda 表达式)

5.8 java 11 增强的 Lambda 表达式

5.8 java 11 增强的 Lambda 表达式

[TOC] Lambda 表达式支持将代码块作为方法的参数,Lambda 表达式允许使用更加简洁的代码来创建一个只有一个抽象方法的接口(这种接口被称为函数式接口)的实例。 #一、Lambda 表达式入门 —— 为了避免匿名内部类的繁琐 我们前面介绍了 Command 表达式的例子: 定义一个处理数组元素的接口

package one;
public interface Command
{
	//接口里定义的Process方法用于封装“处理行为”
	void process(int element);
}

定义一个处理数组的类

package two;
import one.Command;
public class ProcessArray
{
	public void process(int[] target,Command cmd)
	{
		for(var t:target)
		{
			cmd.process(t);
		}
	}
}

1、通过匿名类来调用 Commad 处理数组

import one.Command;
import two.ProcessArray;
class CommandTest1 
{
	public static void main(String[] args) 
	{
		var pa=new ProcessArray();
		int[] a={1,5,9,7};
		pa.process(a,new Command(){
			public void process(int element)
			{
				System.out.println("数组元素的平方:"+element*element);
			}
		});
	}
}
---------- 运行Java捕获输出窗 ----------
数组元素的平方:1
数组元素的平方:25
数组元素的平方:81
数组元素的平方:49

输出完成 (耗时 0 秒) - 正常终止

2、Lambda 表达式来简化匿名内部类对象

import one.Command;
import two.ProcessArray;
class CommandTest2 
{
	public static void main(String[] args) 
	{
		var pa=new ProcessArray();
		int[] a={1,5,9,7};
		pa.process(a,(int element)->
		System.out.println("数组元素的平方:"+element*element));
	}
}

这段代码代码与创建匿名内部类时实现的 process (int element) 方法完全相同,只是不需要 new Xxx (){} 的繁琐形式,不需要指出重写方法的名字,也不需要指出重写方法的返回值类型,只需要给出重写方法括号以及括号里的形参列表即可。 ##3、Lambda 语句的组成 Lambda 表达式主要用于代替匿名内部类的繁琐语法。它由三部分组成: 1、形参列表。形参列表允许是省略类型。如果形参列表只有一个参数,甚至连形参列表的圆括号也可以省略。 2、箭头 (->) 3、代码块。如果代码块只有一条语句允许省略代码块的花括号;如果只有一条 return 语句,甚至可以省略 return 关键字。Lambda 表达式需要返回值,而他的代码块仅有一条省略了 return 语句,Lambda 表达式会自动返回这条语句的值。 Lambda 表达式的集中简化形式:

interface Eatable
{
	void taste();//public abstract
}
interface Flyable
{
	void fly(String weather);
}
interface Addable
{
	int add(int a,int b);
}

public class LambdaQs
{
	//调用该方法需要Eatable对象
	public void eat(Eatable e)
	{
		System.out.println(e);
		e.taste();
	}
	//调用该方法需要Flyable对象
	public void drive(Flyable f)
	{
		System.out.println("我正在驾驶:"+f);
		f.fly("[碧空如洗的晴天]");
	}
	//调用该方法需要Addable对象
	public void test(Addable add)
	{
		System.out.println("3加5的和为:"+add.add(3,5));
	}
	public static void main(String[] args)
	{
		var lq=new LambdaQs();
		//Lambda语句只有一条语句,可以省略花括号
		lq.eat(()->System.out.println("苹果味道不错!"));

		//Lambda表达式形参列表只有一个形参,可以省略圆括号
		lq.drive(weather->{
			System.out.println("今天天气是"+weather);
			System.out.println("直升机平稳飞行");});
		
		//Lambda只有一条语句时,可以省略花括号
		//代码块只有一条语句,即使该表达式需要返回值,也可以省略return关键字
		lq.test((a,b)->{return (a+b);});
		lq.test((a,b)->a+b);
	}
}
---------- 运行Java捕获输出窗 ----------
LambdaQs$$Lambda$1/0x0000000801201040@72ea2f77
苹果味道不错!
我正在驾驶:LambdaQs$$Lambda$2/0x0000000801201840@eed1f14
今天天气是[碧空如洗的晴天]
直升机平稳飞行
35的和为:8
35的和为:8

输出完成 (耗时 0 秒) - 正常终止

lq.eat () 使用不带形参列表的匿名方法,由于该 Lambda 表达式只有一条代码,因此可以省略花括号; lq.drive () 的 Lambda 表达式的形参列表只有一个形参,因此省略了形参列表的圆括号; lq.test () 的 Lambda 表达式的代码块只有一行语句,这行语句的返回值作为该代码块的返回值。

二、lambda 表达式与函数式接口

Lambda 表达式的类型,也成为 “目标类型(target type)”,Lambda 表达式的目标类型必须是 “函数式接口(functional interface)”。<font color=red> 函数式接口代表只包含一个一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但只能声明一个抽象方法。</font>

2.1 匿名内部类和 Lambda 表达式的适用情况

如果采用匿名内部类语法来创建函数式接口,且只需要实现一个抽象方法,在这种情况下,即可采用 Lambda 表达式来创建对象,该表示创建出来的对象的目标类型就是函数式接口。 注: Java 8 专门为函数式接口提供了 @FunctionalInterface 注解,该注解用于方法在接口定义前面,该注解对程序功能没有任何影响,它用于告诉编译器执行更严格的检查 —— 检查该接口必须是函数式接口,否则编译就会出错。 下面程序使用匿名内部类:

/*@FunctionalInterface
 *A 不是函数接口
 * 在 接口 A 中找到多个非覆盖抽象方法
 */
interface A
{
	public void test1();
	public void test2();
	default void test3()//接口中的默认方法
	{
		System.out.println("接口A中的默认方法");
	}
} 
public class 适用匿名内部类
{
	public void test(A a)
	{
		System.out.println("接口A含有两个抽象方法和一个默认方法,此时适合用匿名内部类");
		a.test1();
		a.test2();
		a.test3();
	}
	public static void main(String[] args)
	{
		var p=new 适用匿名内部类();
		p.test(new A()
		{
			public void test1()
			{
				System.out.println("接口中的抽象方法1");
			}
			public void test2()
			{
				System.out.println("接口中的抽象方法2");
			}
		});
	}
}
接口A含有两个抽象方法和一个默认方法,此时适合用匿名内部类
接口中的抽象方法1
接口中的抽象方法2
接口A中的默认方法

下面定义的接口 B 只有一个抽象方法,是函数式接口,此时适合用 Lambda 表达式:

@FunctionalInterface
interface B
{
	void test1(String msg);//抽象方法,默认public abstract
	default void test2()//接口中的默认方法
	{
		System.out.println("接口A中的默认方法");
	}
} 
public class LambdaFor
{
	public void test(B b)
	{
		System.out.println("接口A含有1个抽象方法和一个默认方法,是函数式接口");
		b.test1("函数式接口A中的抽象方法");
		b.test2();
	}
	public static void main(String[] args)
	{
		var p=new LambdaFor();
		p.test((msg)->
			System.out.println(msg));
	}
}
---------- 运行Java捕获输出窗 ----------
接口A含有1个抽象方法和一个默认方法,是函数式接口
函数式接口A中的抽象方法
接口A中的默认方法

输出完成 (耗时 0 秒) - 正常终止

2.2 使用 Lambda 表达式赋值给对象

用于 Lambda 表达式的结果就是被当成对象,因此程序中完全可以使用 Lambda 表达式进行赋值。我们知道接口不能创建实例,接口中只能定义常量,因此接口不存在构造器和初始化块。接口不能创建实例,但是通过 Lambda 表达式我们可以创建一个 “目标类型” 并把它赋值给函数式接口的对象。 例如:

@FunctionalInterface
interface Runnable
{
	void printNum();
}
public class RunnableTest
{
	public static void main(String[] args)
	{
		//Runnable接口中只包含一个无参数的构造器方法
		//Lambda表达式代表的匿名方法实现了Runnable接口中唯一的无参数方法
		//因此下面的方法创建了一个Runnable的对象
		Runnable r=()->{
			for(int i=0;i<10;i++)
				System.out.print(" "+i);
		};
		r.printNum();
	}
}
---------- 运行Java捕获输出窗 ----------
 0 1 2 3 4 5 6 7 8 9
输出完成 (耗时 0 秒) - 正常终止

<font color=red>Lambda 表达式实现的匿名方法 —— 因此它只能实现特定函数式接口中唯一方法。这意味着 Lambda 表达式有两个限制: 1、Lambda 表达式的目标类型必须是明确的函数式接口。 2、Lambda 表达式只能为函数式接口创建对象。Lambda 表达式只能实现一个方法,因此他只能为只有一个抽象方法的接口(函数式接口)创建对象。</font> 关于第一点限制举例:

@FunctionalInterface
interface A
{
	void test();
}
class  LambdaLimit1
{
	public static void main(String[] args) 
	{
		//Object a=()->{System.out.println("This is a test!");};
		//上面代码将报错: 不兼容的类型: Object 不是函数接口
		
		//Lambda表达式的目标类型必须是明确的函数式接口
		A a=()->{System.out.println("This is a test!");};
		a.test();//This is a test!
	}
}

从错误信息可以看出,Lambda 表达式的目标类型必须是明确的函数式接口。上述表达式将 Lambda 表达式赋给 Object 变量,编译器只能确定该表达式的类型为 Object,而 Object 并不是函数式接口。 为了保证 Lambda 表达式的目标类型是一个明确的函数式接口,常见有三种方式: 1、将 Lambda 表达式赋值给函数式接口的变量;

    //参考上面的完整程序
    A a=()->{System.out.println("This is a test!");};

2、将 Lambda 表达式作为函数接口类型的参数传给某个方法。

interface A
{
	void test(String msg);
}
public class ATest
{
	public static void med(A a)
	{
		System.out.println("主类的非静态方法");
		a.test("我是传奇");
	}
	public static void main(String[] args)
	{
		ATest.med((msg)->System.out.println(msg));
	}
}
---------- 运行Java捕获输出窗 ----------
主类的非静态方法
我是传奇

输出完成 (耗时 0 秒) - 正常终止

3、使用函数式接口类型对 Lambda 表达式进行强制转换。

Object a=(A)()->{System.out.println("This is a test!");};

三、在 Lambda 表达式中使用 var

对与 var 声明变量,程序可以使用 Lambda 表达式进行赋值。但由于 var 代表需要由编译器推断的类型,因此使用 Lambda 表达式对 var 表达式定义的变量进行赋值时,必须指明 Lambda 表达式的目标类型。 例如:

var a=(A)()->{System.out.println("This is a test!");};

如果程序需要对 Lambda 表达式的形参列表添加注解,此时就不能省略 Lambda 表达式的形参类型 —— 因为注解只能放在形参类型之前。在 Java 11 之前,程序必须严格声明 Lambda 表达式中的每个形参类型,但实际上编译器完全可以推断出 lambda 表达式中每个形参的类型。

例如:下面程序定义了一个 Predator 接口,该接口中的 prey 方法的形参使用了 @NotNull 注解修饰:

@interface NotNull{}
interface Predator
{
	void prey(@NotNull String animal);
}

接下来程序打算使用 Lambda 表达式来实现一个 Predator 对象。如果 Lambda 表达式不需要对 animal 形参使用 @NotNull 注解,则完全可以省略 animal 形参注解;但如果希望为 animal 形参注解,则必须为形参声明类型,此时可直接使用 var 来声明形参类型。

@interface NotNull{}
interface Predator
{
	void prey(@NotNull String animal);
}
public class PredatorTest
{
	public static void main(String[] args)
	{
		//使用var声明lambda表达式的形参类型
		//这样即可为Lambda表达式的形参添加注解
		Predator p=(@NotNull var animal)->System.out.println("老鹰在抓"+animal);
		p.prey("小鸡");
	}
}
//老鹰在抓小鸡

#四、方法引用和构造器引用 Lambda 表达式的方法引用和构造器引用都需要两个英文冒号::。Lambda 表达式支持如下几种引用方式:

种类 示例 说明 对应的 Lambda 表达式
引用类方法 类名::类方法名 函数式接口中被实现的方法的参数全部传给该类方法作为参数 (a,b...)-> 类名。类方法(a,b...)
引用特定对象的实例方法 特定对象::示例方法名 函数式接口中被实现的方法的参数全部传给该实例方法作为参数 (a,b...)-> 特定对象。实例方法 (a,b...)
引用某类对象的实例方法 类名::实例方法名 函数式接口中被实现的方法的第一个参数作为调用者,后面的参数传给该方法作为参数 (a,b,c...)->a. 实例方法 (b,c...)
引用构造器 类名::new 函数式接口中被实现的方法的全部参数传给该构造器作为参数 (a,b...)->new 类名 (a,b...)
##4.1 引用类方法
@FunctionalInterface
interface Converter
{
	Integer convert(String form);
}

public class ConverterTest
{
	public static void main(String[] args)
	{
		//Lambda表达式只有一条语句,可以省略1花括号:Lambda表达式会把这条代码的值作为返回值
		Converter c=(form)->Integer.parseInt(form);
		System.out.println(c.convert("185"));

		//下面通过引用类方法来实现相同的功能
		Converter cPlus=Integer::valueOf;
		System.out.println(cPlus.convert("140"));
	}
}

##4.2 引用特定对象的实例方法

@FunctionalInterface
interface Converter
{
	Integer convert(String form);
}

public class ConverterTest1
{
	public static void main(String[] args)
	{
		//先使用Lambda表达式来创建一个Converter对象
		Converter c=form->"fkit.org".indexOf(form);//代码块只有一条语句,因此Lambda表达式会把这条代码的值作为返回值
		System.out.println(c.convert("it"));//输出2

		//引用特定对象的特定方法 "fkit.org"是一个String对象
		Converter c1="fkit.org"::indexOf;
		System.out.println(c1.convert("org"));//输出5
	}
}

对于上面的示例方法引用,也就是说,调用 "fkit.org" 对象的 indexOf () 实例方法来实现 Converter 函数式接口中唯一的抽象方法,当调用 Converter 接口中的唯一抽象的方法时,调用参数会传给 "fkit.org" 对象的 indexOf () 实例方法。 ##4.3 引用某类对象的实例方法 先介绍一个函数:public String substring (int beginIndex, int endIndex) 返回字符串索引范围 [beginIndex,endIndex) 的子字符串。

@FunctionalInterface
interface MyTest
{
	String test(String a,int b, int c);
}
class  substringTest
{
	public static void main(String[] args) 
	{
		MyTest m=(a,b,c)->a.substring(b,c);
		System.out.println(m.test("fkjava",1,5));

		//引用某类对象的实例方法
		MyTest mPlus=String::substring;
		System.out.println(mPlus.test("hello world",2,7));//相当于"hello world".substring(2,7)
	}
}
---------- 运行Java捕获输出窗 ----------
kjav
llo w

输出完成 (耗时 0 秒) - 正常终止

##4.4 引用构造器 JFrame 屏幕上 window 的对象,能够最大化、最小化、关闭

import java.awt.*;
import javax.swing.*;
@FunctionalInterface
interface YourTest
{
  JFrame win(String title);
}
public class MethodRefer
{
  public static void main(String[] args)
  {
   // 下面代码使用Lambda表达式创建YourTest对象
//  YourTest yt = (String a) -> new JFrame(a);
   // 构造器引用代替Lambda表达式。
   // 函数式接口中被实现方法的全部参数传给该构造器作为参数。
   YourTest yt = JFrame::new;
   JFrame jf = yt.win("我的窗口");
   System.out.println(jf);
  }
}

#五、Lambda 表达式和匿名内部类的联系和区别 Lambda 表达式与匿名内部类之间存在如下相同点: 1、Lambda 表达式与匿名内部类一样,都可以直接访问 "effectively final" 的局部变量,以及外部类的成员变量,包括实例变量和类变量。 2、Lambda 表达式创建的对象与匿名内部类生成的对象一样,都可以直接从接口中继承的默认方法。

@FunctionalInterface
interface Displayable
{
	void display();
	default int add(int a,int b)
	{
		return a+b;
	}
}

public class LambdaAndInner
{
	private int age=12;
	private static String name="fkit.org";
	public void test()
	{
		var book="疯狂Java讲义";
		Displayable dis=()->{
			//访问"effictively final"的局部变量
			System.out.println("book局部变量为:"+book);
			//访问外部类的实例变量和类变量
			System.out.println("外部类的age实例变量:"+age);
			System.out.println("外部类的name类变量:"+name);
			};
		dis.display();
		//调用方对从接口继承add()方法
		System.out.println(dis.add(3,5));
	}
	public static void main(String[] args)
	{
		var lambda=new LambdaAndInner();
		lambda.test();
	}
}
---------- 运行Java捕获输出窗 ----------
book局部变量为:疯狂Java讲义
外部类的age实例变量:12
外部类的name类变量:fkit.org
8

输出完成 (耗时 0 秒) - 正常终止

与匿名函数相似的是,由于 Lambda 表达式访问了 book 局部变量,因此该局部变量相当于有一个隐式的 final 修饰,因此同样不允许对 book 局部变量重新赋值。当程序使用了 Lambda 表达式创建了 Displayable 对象之后,该对象不仅可调用接口的抽象方法,也可以调用接口中的默认方法,因此同样不允许对 book 局部变量重新赋值。 </font color=red>Lambda 表达式与匿名内部类的区别: 1、匿名内部类可以为内部类可以为任意接口创建实例;但 Lambda 表达式只能为函数式创建实例。 2、匿名内部类可以为抽象类乃至普通类创建实例;但 Lambda 表达式只能为函数式接口创建实例。 3、匿名内部类是实现抽象方法的方法体允许调用接口中定义的默认方法;但 Lambda 表达式的代码块不允许不允许调用接口中的默认方法。</font>

#六、使用 Lambda 表达式调用 Arrays 的类方法 Arrays 类的有些方法需要 Comparator、XxxOperator、XxxFunction 等接口的实例,这些接口都是函数式编程,因此可以使用 Lambda 表达式来调用 Arrays 的方法。

import java.util.Arrays;
public class LambdaArrays
{
	public static void main(String[] args)
	{
		var arr1 = new String[] {"java", "fkava", "fkit", "ios", "android"};
		Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
                //这行Lambda表达式的目标类型是Comparator,该Comparator指定判断字符串大小的标准:字符串越长,认为该字符串越大
		System.out.println(Arrays.toString(arr1));

		var arr2 = new int[] {3, -4, 25, 16, 30, 18};
		// left代表数组中前一个所索引处的元素,计算第一个元素时,left为1
		// right代表数组中当前索引处的元素
		Arrays.parallelPrefix(arr2, (left, right)-> left * right);
                 //这行Lambda表达式的目标类型是IntBinaryOperator,该对象将会根据前后两个元素来计算当前元素
		System.out.println(Arrays.toString(arr2));
                
		var arr3 = new long[5];
		// operand代表正在计算的元素索引
		Arrays.parallelSetAll(arr3, operand -> operand * 5);
                //这行Lambda表达式的目标类型是IntToLongFunction,该对象将会根据当前索引值计算当前元素的值。
		System.out.println(Arrays.toString(arr3));
	}
}
---------- 运行Java捕获输出窗 ----------
[ios, java, fkit, fkava, android]
[3, -12, -300, -4800, -144000, -2592000]
[0, 5, 10, 15, 20]

输出完成 (耗时 0 秒) - 正常终止

C++11 lambda 表达式 (lambda expression)

C++11 lambda 表达式 (lambda expression)

1. 可调用对象 (callable object) 类别包括:

  函数

  函数指针

  重载了函数调用运算符的类

  lambda 表达式

2.lambda 表达式形式:

  [capture list] (parameter list) -> return type { function body }

  capture list (捕获列表) 是一个 lambda 所在函数中定义的局部变量的列表 (通常为空),空捕获列表表明此 lambda 不使用它所在的函数中的任何局部变量, return type、parameter list 和 function body 与任何普通函数一样,分别表示返回类型、函数列表和函数体,但是与普通函数不同,lambda 必须使用尾置返回来指定返回类型

  尾置返回类型跟在形参列表后面并以一个 -> 符号开头,为了表示函数真正的返回类型跟在形参列表之后,在本应该出现返回类型的地方放置了一个 auto

    auto func (int i) -> int (*)[10]; //func 接受一个 int 类型的实参,返回一个指针,该指针指向含有 10 个整数的数组

  lambda 可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体

    auto f = [ ] { return 42; };

  lambda 的调用方式与普通函数调用方式相同,都是使用调用运算符

    cout << f() << endl;

  lambda 中忽略括号和参数列表等价于指定一个空参数列表

  lambda 中忽略返回类型,可根据函数体中的代码推断出返回类型

3.lambda 不能有默认参数

4. 捕获列表

  以一对 [ ] 开始,可以在其中提供一个以逗号分隔的名字列表,这些名字都是 lambda 它所在函数中定义的

  捕获列表只用于局部非 static 变量, lambda 可以直接使用局部 static 变量和它所在函数之外声明的名字

  与参数传递类似,变量的捕获方式也可以是值或引用

  值捕获的前提是变量可以拷贝,与参数不同,被捕获的变量的值是在 lambda 创建时拷贝,而不是调用时拷贝

  lambda 捕获的都是局部变量,以引用方式捕获的变量与其他任何类型的引用的行为类似,采用引用方式捕获一个变量,就必须确保被引用的对象在 lambda 执行时候时存在的

  如果函数返回一个 lambda ,则与函数不能返回一个局部变量的引用类似,此 lambda 不能包含引用捕获

  尽量减少捕获数据量,避免捕获指针或引用

  隐式捕获: [&, c] (const string &s) { os << s << c; } //os 是隐式捕获,引用捕获方式; c 是显式捕获,值捕获方式

  混合使用隐式捕获和显式捕获时,捕获列表中第一个元素必须是一个 & 或 = ,显式捕获的变量必须使用与隐式捕获不同的方式

  默认情况下,对于一个值拷贝的变量, lambda 不会改变其值,如果要改变一个被捕获变量的值,就必须在参数列表首加上关键字 mutable,就是 mutable 前面必须要有 ()

    auto f = [v] ( ) mutable { return ++v;} 

  一个引用捕获的变量是否可修改历来与此引用指向的是一个 const 类型还是一个非 const 类型

5. C++14、C++17 lambda 新标准待总结

我们今天的关于Java lambda 表达式javalambda表达式实现原理的分享就到这里,谢谢您的阅读,如果想了解更多关于(转)Java 8:一文掌握 Lambda 表达式、-source 1.5 中不支持 lambda 表达式 (请使用 -source 8 或更高版本以启用 lambda 表达式)、5.8 java 11 增强的 Lambda 表达式、C++11 lambda 表达式 (lambda expression)的相关信息,可以在本站进行搜索。

本文标签: