Java8——Lambda 表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。使用 Lambda 表达式可以将代码块作为方法参数,使代码变的更加简洁紧凑。坦白的说,初次看见Lambda表达式瞬间头就大了,为了更好的理解,我们可以把Lambda 表达式当作是一种匿名函数(对 Java 而言这并不完全正确,但现在姑且这么认为),简单地说,就是没有声明的方法,即没有访问修饰符、返回值声明和名字。

Lambda 表达式的结构

Java 中的 Lambda 表达式通常使用 (argument) -> (body) 语法书写,例如:

(arg1, arg2...) -> expression

(arg1, arg2...) -> { body }

(type1 arg1, type2 arg2...) -> { body }

Lambda 表达式的结构说明:

  • 一个 Lambda 表达式可以有零个或多个参数
  • 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)与(a)效果相同
  • 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
  • 空圆括号代表参数集为空。例如:() -> 42
  • 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a
  • Lambda 表达式的主体可包含零条或多条语句
  • 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致
  • 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空

列举几个 Lambda 表达式的例子:

(int a, int b) -> {  return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> { return 3.1415 };

Lambda表达式可能会有返回值,编译器会根据上下文推断返回值的类型。如果lambda的语句块只有一行,不需要return关键字。下面两个写法是等价的:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
和
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );

Lambda 实战

下面我们看看Lambda在开发中的实际使用

替代匿名内部类

1.Runnable 接口

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("The runable now is using!");
}
}).start();
//用lambda
new Thread(() -> System.out.println("It's a lambda function!")).start();

2.Comparator 接口

List names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator() {

@Override

public int compare(String a, String b) {

return b.compareTo(a);

}

});

在 Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8 提供了更简洁的语法,lambda 表达式:

Collections.sort(names, (String a, String b) -> {

return b.compareTo(a);

});

可以看出,代码变得更短且更具有可读性,但是实际上还可以写得更短:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

对于函数体只有一行代码的,我们还可以去掉大括号{}以及 return 关键字,但是我们还可以写得更短点:

names.sort((a, b) -> b.compareTo(a));

List 类本身就有一个sort 方法。并且 Java 编译器可以自动推导出参数类型,所以你可以不用再写一次类型。

3.自定义接口

通过上面的例子可以看出Lambda语法只保留实际用到的代码,把无用代码全部省略。那它对接口有没有要求呢?我们发现这些匿名内部类只重写了接口的一个方法,当然也只有一个方法须要重写。这就是我们上文提到的函数式接口,也就是说只要方法的参数是函数式接口都可以用 Lambda 表达式。

@FunctionalInterface
public interface Comparator<T>{}
@FunctionalInterface
public interface Runnable{}

我们自定义一个函数式接口

@FunctionalInterface
public interface Comparator<T>{}
@FunctionalInterface
public interface Runnable{}
我们自定义一个函数式接口
@FunctionalInterface
public interface LambdaInterface {
void f();
}
//使用
public class LambdaClass {
public static void forEg() {
lambdaInterfaceDemo(()-> System.out.println("自定义函数式接口"));
}
//函数式接口参数
static void lambdaInterfaceDemo(LambdaInterface i){
i.f();
}
}

集合迭代

要遍历数组中的所有元素,通常使用for循环的方法,而使用 Lambda 表达式的方法不止一种。在下面的例子中,我们先是用常用的箭头语法创建 Lambda 表达式,之后,使用 Java 8 全新的双冒号(::)操作符将一个常规方法转化为 Lambda 表达式:

//Java 8之前:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
   System.out.println(n);
}
//Java 8之后:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// 使用Java 8的方法引用更方便,方法引用由::双冒号操作符标示,
// 看起来像C++的作用域解析运算符
list.forEach(System.out::println);

方法的引用

Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。

1、引用构造器方法:

语法:

<函数式接口>  <变量名> = <类>::<new>
//调用
<变量名>.接口方法([实际参数...])

把方法的所有参数全部传递给引用的构造器,请注意构造方法没有参数,根据参数类型自动推断调用的构造器方法;

示例代码:

public interface LambdaTest5 {

    abstract String creatString(char[] c);
}
public class Main {

    public static void main(String[] args) {
        LambdaTest5 lt5 = String::new;
        System.out.println(lt5.creatString(new char[]{'1','2','3','a'}));
    }
}
2、引用类静态方法:

语法:

    <函数式接口>  <变量名> = <类>::<类方法名称>
    //调用
    <变量名>.接口方法([实际参数...])

将调用方法时的传递的实际参数,全部传递给引用的方法,执行引用的方法;

示例代码:

public interface LambdaTest3 { abstract void sort(List<Integer> list,Comparator<Integer> c); } public class Main { public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); list.add(50); list.add(18); list.add(6); System.out.println(list.toString()+"排序之前"); LambdaTest3 lt3 = Collections::sort; lt3.sort(list, (a,b) -> { return a-b; }); System.out.println(list.toString()+"排序之后"); } }

3、引用实例方法:

语法:

    <函数式接口>  <变量名> = <实例>::<实例方法名>
    //调用
    <变量名>.接口方法([实际参数...])

将调用方法时的传递的实际参数,全部传递给引用的方法,执行引用的方法;

示例代码:

public class LambdaClassTest {

    public int add(int a, int b){
        System.out.println("LambdaClassTest类的add方法");
        return a+b;
    }
}



public class Main {

    public static void main(String[] args) {
        LambdaClassTest lct = new LambdaClassTest();
       
        System.out.println(lct.add( 5, 8));
    }
}
4、引用类的实例方法:

定义、调用接口时,需要多传递一个参数,并且参数的类型与引用实例的类型一致

语法:

//定义接口
interface <函数式接口>{
    <返回值> <方法名>(<类><类名称>,[其他参数...]); 
}
<函数式接口>  <变量名> = <类>::<类实例方法名>
//调用
<变量名>.接口方法(类的实例,[实际参数...])

将调用方法时的传递的实际参数,从第二个参数开始(第一个参数指定的类的实例),全部传递给引用的方法,执行引用的方法;

示例代码:

public class LambdaClassTest {

    public int add(int a, int b){
        System.out.println("LambdaClassTest类的add方法");
        return a+b;
    }
}

public interface LambdaTest4 {

    abstract int add(LambdaClassTest lt,int a,int b);
}

public class Main {

    public static void main(String[] args) {
        LambdaTest4 lt4 = LambdaClassTest::add;
        LambdaClassTest lct = new LambdaClassTest();
        System.out.println(lt4.add(lct, 5, 8));
    }
}

Lambda表达式对比匿名类

使用匿名类与 Lambda 表达式的一大区别在于关键词的使用。匿名类的 this 关键字指向匿名类,而lambda表达式的 this 关键字指向写就 Lambda 的外部类。另一个不同点是二者的编译方式。Java编译器将lambda表达式编译成类的私有方法。使用了Java 7的 invoke dynamic 字节码指令来动态绑定这个方法。

Lambda 作用域

在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。若是局部变量没有加final关键字,系统会自动添加,此后再修改该局部变量,会报错;

参考:

http://blog.oneapm.com/apm-tech/226.html

http://www.importnew.com/16436.html

https://blog.csdn.net/u010412719/article/details/53493226

Lambda系列教材 (一)- Java Lambda 表达式教程

Java Lambda 表达式 | 菜鸟教程

https://www.cnblogs.com/liuxiaozhi23/p/10880147.html

https://ifeve.com/java-8-features-tutorial/

JAVA8 十大新特性详解_java8新特性-CSDN博客