一. Lambada 表达式
1. 简介
Lambda 表达式是一种匿名函数(**对 Java 而言这并不完全正确,但为了便于理解,姑且这么认为**),简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。
你可以将其想做一种速记,在你需要使用某个方法的地方写上它。当某个方法只使用一次,而且定义很简短,使用这种速记替代之尤其有效,这样,你就不必在类中费力写声明与方法了。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。
2. 为什么需要 Lambda 表达式?
本质上来说,编程关注两个维度:数据和数据上的操作。
面向对象的编程泛型强调让操作围绕数据,对象至上,这样可以实现以类为单位的重用,当为类添加新的数据类型时,原有代码无需修改。
函数式编程是一种不同的编程模型,它以操作(函数)为中心,强调变量不变性。函数式编程的准则是不依赖外部的数据,也不改变外部数据的值。这一特性满足了多核并行程序设计的需求,因此能简化并行程序开发。在函数式编程语言中,函数是一等公民,它们可以独立存在,你可以将其赋值给一个变量,或将他们当做参数传给其他函数。函数式编程提供了一种强大的功能——闭包,相比于传统的编程方法有很多优势,闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。
在 Java 中,函数无法单独存在,必须依托于对象。Lambda 表达式是Java8对函数式编程的支持,是可以传递执行的代码块,他们为java的函数式编程提供了有力支持,但是他们必须依附于一类特别的对象类型——函数式接口(functional interface)。
3 函数式接口
函数式接口是只包含一个抽象方法声明的接口。java.lang.Runnable
就是一种函数式接口,在 Runnable 接口中只声明了一个方法 void run(),我们使用匿名内部类来实例化函数式接口的对象,有了 Lambda 表达式,这一方式可以得到简化。
每个 Lambda 表达式都能隐式地赋值给函数式接口,例如,我们可以通过 Lambda 表达式创建 Runnable 接口的引用。
Runnable r = () -> System.out.println("hello world");
编译器会把lambada表达式当做一个函数来编译,这样我们可以将它传入Thread的构造函数中,只需要1行代码
new Thread(() -> System.out.println("hello world"));
而使用匿名内部类来创建Thread的话,需要6行代码
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello world")
}
});
@FunctionalInterface 注解
在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:
@FunctionalInterface
public interface Functional {
void method();
}
不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
4. Lambda 表达式的结构
Java 中的 Lambda 表达式通常使用 (argument) -> (body) 语法书写,例如:
(arg1, arg2...) -> { body }
(Type arg1, Type arg2...) -> { body }
常见的写法如下
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> {System.out.print(s)}
- 一个 Lambda 表达式可以有零个或多个参数
- 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)与(a)效果相同
- 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
- 空圆括号代表参数集为空。例如:() -> 42
- 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a
- Lambda 表达式的主体可包含零条或多条语句 例如:(a, b) ->{a=1;b=2;System.out.print(b-a);}
- 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致
- 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空
5. 常见的函数式接口例子
5.1 Runnable 任务(常用)
```java Runnable runnable = ()->{ System.out.println(Thread.currentThread().getName()); };
Thread thread = new Thread(runnable); thread.start();
- Runnable只有一个run函数,且run函数没有参数
<a name="pLpNG"></a>
### 5.2 Comparator 比较器(常用)
lambada表达式实现Comparator
List
//另一种方式,利用Comparator下的comparingInt方法,结合lambada表达式,更加优雅 list.sort(comparingInt(a -> a));
<a name="D0llI"></a>
### 5.3 集合的操作(常用)
List的遍历
```java
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(2);
list.forEach((value)->System.out.println(value));
Map的遍历
Map<String, Integer> items = new HashMap<>();
items.put("A", 10);
items.put("B", 20);
items.put("C", 30);
items.put("D", 40);
items.put("E", 50);
items.put("F", 60);
items.forEach((k,v)->System.out.println("Item : " + k + " Count : " + v));
5.4 Consumer接口
Consumer接口接收一个参数,不返回参数
public static void consumerFun(int value, Consumer<Integer> c) {
c.accept(value);
}
//调用
consumerFun(1,(value)->{
System.out.println(value);
});
5.5 BinConsumer接口
与Consumer接口一样,只不过接收两个参数,返回0个参数
public static void binConsumerFun(String a, String b, BiConsumer<String, String> binc) {
binc.accept(a, b);
}
//调用
binConsumerFun("hello", "maskwang", (a,b)->{
System.out.println(a+b)
});
5.6 Predication
作用接收一个参数,返回一个boolean值
public static boolean predicateFun(int value, Predicate<Integer> pre) {
return pre.test(value);
}
//调用
System.out.println(predicateFun(3, x->x==3));
5.7 Supplier
作用是接收0个参数,返回一个值
public static int supplierFun(Supplier<Integer> supplier) {
return supplier.get();
}
//调用
System.out.println(supplierFun(()->1));
6. Lambda表达式与匿名内部类的联系和区别
联系:
- 都可以直接访问 “effectively final” 的局部变量,以及外部类的成员变量(包括实例变量和类变量)。
- 都可以直接调用从接口继承得到的默认方法。
“effectively final”:局部内部类和匿名内部类访问的局部变量必须由final修饰,java8开始,可以不加final修饰符,由系统默认添加。java将这个功能称为:Effectively (有效) final
区别:
- 所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
- 使用限制不同
- 如果接口中有且仅有一一个抽象方法,可以使用L ambda表达式,也可以使用匿名内部类
- 如果接口中多 于一个抽象方法,只能使用匿名内部类,而不能使用L ambda表达式
- 实现原理不同
- 匿名内部类:编译之后,产生-一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
7. 引申:方法引用
方法引用也是JDK8的新特性,通常与Lambda表达式联合使用,可以直接引用已有Java类或对象的方法。一般有四种不同的方法引用:
- 构造器引用。语法是Class::new,或者更一般的Class< T >::new,可无参,可有参数。方法签名保持一致;
- 静态方法引用。语法是Class::static_method,要求方法签名保持一致;
- 特定类的任意对象方法引用。它的语法是Class::method。要求方法签名保持一致;
- 特定对象的方法引用,它的语法是instance::method。要求方法签名保持一致。与3不同的地方在于,3是在列表元素上分别调用方法,而4是在某个对象上调用方法,将列表元素作为参数传入;
ArrayList::new () -> new ArrayList<>()
String::valueOf x -> String.valueOf(x)
Object::toString x -> x.toString()
x::toString () -> x.toString()
8. 总结
Lambda 表达式赋予了 Java 相较于其他函数式编程语言缺失的特性,结合虚拟扩展方法之类的特性,Lambda 表达式能写出一些极好的代码。希望能在以后写代码的过程中,把这些用上去,使整个代码看起来很简洁.