文章目录
Lambda简介
Lambda表达式语法图

什么是Lambda?
Lambda表达式是一段可以传递的代码,它的核心思想是将面向对象中的传递数据变成传递行为。
Lambda表达式也可以看作是匿名内部类的简便写法。
对于一个Java变量,我们可以赋给其一个值:

现在我们想把一块代码赋给一个Java变量,把右边那块代码,赋给一个叫做aBlockOfCode的Java变量:

在Java8之前,这个是做不到的。但是Java8问世之后,利用Lambda特性,就可以做到了:

当然,这个并不是一个很简洁的写法。所以,为了使这个赋值操作更加优雅, 我们可以移除一些没用的声明:

这样,我们就成功的非常优雅的把一块代码赋给了一个变量。而这块代码,或者说这个被赋给一个变量的函数,就是一个Lambda表达式。
但是这里仍然有一个问题,就是变量aBlockOfCode的类型应该是什么?
在Jav 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是那段代码,需要是这个接口的实现。
简而言之就是,Lambda表达式本身就是一个接口的实现。
我们给上面的aBlockOfCode加上一个类型:

这种只有一个接口函数需要被实现的接口类型,我们叫它函数式接口。为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成非函数接口,我们可以在这个上面加上一个声明@FunctionalInterface, 这样别人就无法在里面添加新的接口函数了:

这样,我们就得到了一个完整的Lambda表达式声明:

Lambda表达式有什么作用?
最直观的作用就是使得代码变得异常简洁。
可以对比一下Lambda表达式和传统的Java对同一个接口的实现:

这两种写法本质上是等价的。但是显然,Java 8中的写法更加优雅简洁。并且,由于Lambda可以直接赋值给一个变量,我们就可以直接把Lambda作为参数传给函数,而传统的Java必须有明确的接口实现的定义,初始化才行:

有些情况下,这个接口实现只需要用到一次。传统的Java 7必须要求你定义一个污染环境的接口实现MyInterfaceImpl,而相较之下Java 8的Lambda, 就显得干净很多。
Lambda基础语法
在lambda中我们遵循如下的表达式来编写:
expression = (variable) -> action
- variable: 这是一个变量,一个占位符。像x,y,z,可以是多个变量;
- action: 这里我称它为action, 这是我们实现的代码逻辑部分,它可以是一行代码也可以是一个代码片段。
可以看到Java中lambda表达式的格式:参数、箭头、以及动作实现,当一个动作实现无法用一行代码完成,可以编写 一段代码用{}包裹起来。
lambda表达式可以包含多个参数,例如:
int sum = (x, y) -> x + y;
这时候我们应该思考这段代码不是之前的x和y数字相加,而是创建了一个函数,用来计算两个操作数的和。 后面用int类型进行接收,在lambda中为我们省略去了return。
Lambda示例
public interface LambdaInter {
int add(int x, int y);
}
public class JDK8Demo {
public static void main(String[] args) {
LambdaInter lambdaInter = (x, y) -> {
return x + y;
};
int add = lambdaInter.add(8, 9);
System.out.println(add);
}
}
总结:
lambda 可以隐式的创建只有一个抽象方法的匿名内部类。
在Java编程中,方法入参传递的对象是一个接口,这时需要匿名实现这个接口并实现抽象方法。如果这个接口只有一个抽象方法,就能使用lambda方式替换。
@FunctionalInterface
interface GreetingService {
void sayMessage(String message);
}
上面的代码可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的)。
Lambda表达式中的异常处理
Java 8 lambda表达式中的异常处理
如何优雅的处理Lambda中异常?
在使用Lambda开发过程中,如果发现Lambda表达式中如果存在检查异常,我们只能使用try-catch去处理异常,那么这个Lambda表达式就会看起来非常臃肿。
在Lambda中使用try-catch处理异常:
list.forEach(e -> {
try{
Thread.sleep(1000L);
}catch (InterruptedException ex){
System.err.println("发生异常 :"+ex.toString());
}
});
这样处理的话看着非常臃肿,破坏了Lambda简洁的效果。其实我们还可以选择throw的方式将异常抛出去。
但在Lambda表达式中只能抛出非检查异常。
检查异常是不能抛出去的,编译无法通过。所以只能使用try-catch处理这个异常。
// 非检查异常 编译通过
list.forEach(e-> {
throw new RuntimeException();
});
// 检查异常 编译不通过
list.forEach(e -> {
throw new IOException();
});
使用包装方法处理非检查异常:
import lombok.extern.slf4j.Slf4j;
import java.util.function.Consumer;
// 对lambda异常进行处理的包装类
@Slf4j
public class HandlingConsumerWrapper {
public static <T> Consumer<T> handlingConsumerWrapper(Consumer<T> throwingConsumer){
return param -> {
try{
throwingConsumer.accept(param);
}
catch (Exception ex){
log.error("在lambda中发生异常!", ex);
}
};
}
}
public static void main(String[] args) {
List<String> list = Arrays.asList("海", "上");
// 非检查异常 编译通过
list.forEach(HandlingConsumerWrapper.handlingConsumerWrapper(param->{
Integer.valueOf(param);
}));
}
上面这种方法并不能处理检查异常。
自定义一个函数接口来处理检查异常:
// 封装consumer接口异常的接口
public interface ThrowingConsumer<T, E extends Exception> {
//对异常选择上抛,原consumer接口没有上抛,才不好处理异常
void accept(T t) throws Exception;
}
import lombok.extern.slf4j.Slf4j;
import java.util.function.Consumer;
@Slf4j
public class HandlingConsumerWrapper {
public static <T, E extends Exception> Consumer<T> handlingConsumerWrapper(ThrowingConsumer<T, E> throwingConsumer){
return param -> {
try{
throwingConsumer.accept(param);
}
catch (Exception ex){
log.error("在lambda中发生异常!", ex);
}
};
}
}
我们定义了一个函数接口ThrowingConsumer,ThrowingConsumer的方法和Consumer接口一致,只不过抛出了异常,这样我们才可以在包装方法中处理检查异常。
将检查异常转化为非检查异常抛出去:
import lombok.extern.slf4j.Slf4j;
import java.util.function.Consumer;
@Slf4j
public class HandlingConsumerWrapper {
public static <T, E extends Exception> Consumer<T> handlingConsumerWrapper(ThrowingConsumer<T, E> throwingConsumer) throws E{
return param -> {
try{
throwingConsumer.accept(param);
}
catch (Exception ex){
// 将检查异常作为非检查异常抛出去
throwAsUnchecked(ex);
}
};
}
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {
throw (E) exception;
}
}
处理Lambda的工具类:
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class LambdaExceptionUtil {
@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
void accept(T t) throws E;
}
@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
void accept(T t, U u) throws E;
}
@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
R apply(T t) throws E;
}
@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
T get() throws E;
}
@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
void run() throws E;
}
/**
* .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println));
*/
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
return t -> {
try {
consumer.accept(t);
} catch (Exception exception) {
throwAsUnchecked(exception);
}
};
}
public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
return (t, u) -> {
try {
biConsumer.accept(t, u);
} catch (Exception exception) {
throwAsUnchecked(exception);
}
};
}
/**
* .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName))
*/
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
return t -> {
try {
return function.apply(t);
} catch (Exception exception) {
throwAsUnchecked(exception);
return null;
}
};
}
/**
* rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
*/
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
return () -> {
try {
return function.get();
} catch (Exception exception) {
throwAsUnchecked(exception);
return null;
}
};
}
/**
* uncheck(() -> Class.forName("xxx"));
*/
public static void uncheck(Runnable_WithExceptions t) {
try {
t.run();
} catch (Exception exception) {
throwAsUnchecked(exception);
}
}
/**
* uncheck(() -> Class.forName("xxx"));
*/
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier) {
try {
return supplier.get();
} catch (Exception exception) {
throwAsUnchecked(exception);
return null;
}
}
/**
* uncheck(Class::forName, "xxx");
*/
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
try {
return function.apply(t);
} catch (Exception exception) {
throwAsUnchecked(exception);
return null;
}
}
@SuppressWarnings("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {
throw (E) exception;
}
}
函数式编程
函数式编程是种编程方式,并不是Java8的新特性,只是Java8内置了很多函数式编程的类,它将电脑运算视为函数的计算。将业务逻辑细化,抽象,封装成一个个功能函数,并借助语言自带的高阶函数api,将整个业务流程转化为函数之间的相互调用,这就是函数式编程。
与命令式编程相比,函数式编程更加强调做什么,在函数式编程中我们只需要关注做什么样的事情,不需要关注具体的实现细节。
例子:查找一个数组中的最小值;
import java.util.stream.IntStream;
public class MainMain{
public static void main(String[] args) throws Exception {
int[] nums = {33,55,-55,90,-666,90};
// 命令式编程
int min = Integer.MAX_VALUE;
for (int i : nums){
if (min > i){
min = i;
}
}
System.out.println("命令式编程实现:" + min);
// 函数式编程实现
int asInt = IntStream.of(nums).min().getAsInt();
System.out.println("函数式编程实现:" + asInt);
}
}
说明:
使用命令式编程,需要用程序告诉计算机我们每一步需要做什么最终才能实现我们想要的结果;
如果我们使用函数式编程我们只需要关注我们最终要实现的结果即可,而且从代码量的角度来看,也简洁了不少;
函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,一般通过@FunctionalInterface这个注解来表明某个接口是一个函数式接口。
函数式接口可以被隐式转换为 lambda 表达式。
Runnable就是一个函数式接口,来看看jdk中的Runnable源码
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
函数式编程入门
Java8中函数式编程语法能够精简代码。
使用Consumer作为示例,它是一个函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出。
现在我们要定义一个Consumer对象,传统的方式是这样定义的:
Consumer c = new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
};
而在Java8中,针对函数式编程接口,可以使用Lambda表达式定义:
Consumer c = (o) -> {
System.out.println(o);
};
上面已说明,函数式编程接口都只有一个抽象方法,因此在采用这种写法时,编译器会将这段函数编译后当作该抽象方法的实现。
如果接口有多个抽象方法,编译器就不知道这段函数应该是实现哪个方法的了。
语法说明:
=后面的函数体我们就可以看成是accept函数的实现。- 输入(入参):
->前面的部分,即被()包围的部分。此处只有一个输入参数,实际上输入是可以有多个的,如两个参数时写法:(a, b);当然也可以没有输入,此时直接就可以是()。 - 函数体:->后面的部分,即被{}包围的部分;可以是一段代码。
- 输出(返回值):函数式编程可以没有返回值,也可以有返回值。如果有返回值时,需要代码段的最后一句通过return的方式返回对应的值。
当函数体中只有一个语句时,可以去掉{}进一步简化:
Consumer c = (o) -> System.out.println(o);
然而这还不是最简的,由于此处只是进行打印,调用了System.out中的println静态方法对输入参数直接进行打印,因此可以简化成以下写法:
Consumer c = System.out::println;
它表示的意思就是针对输入的参数将其调用System.out中的静态方法println进行打印。
JDK8中新增的函数式接口
在java.util.function包中提供了很多函数式接口。
具体分为四大类如下:

每个类型接口都有分支接口,接口默认入参、出参类型是泛型,分支接口就是用来限定入参、出参类型或可指定多个入参的。
比如:限定Consumer的入参类型为Long,Consumer默认是只有一个入参的,BiConsumer可以有两个入参。
Consumer
Consumer是消费的意思,即针对某个东西我们来使用它,因此它包含有一个输入而无输出的accept接口方法。
接口定义:

andThen方法专门用来连续执行Consumer的accept。
Consumer也有很多分支接口,比如:LongConsumer(限定入参数类型为Long)、BiConsumer(有两个入参)。
public static <T> void consumerDemo(T param, Consumer<T> consumer) {
consumer.accept(param);
}
public static void main(String[] args) {
consumerDemo("James", param-> {
System.out.println("My name is " + param);
});
Consumer<String> c1 = (param)->{
System.out.println(param + " c1");
};
Consumer<String> c2 = (param)->{
System.out.println(param + " c2");
};
Consumer<String> c3 = (param)->{
System.out.println(param + " c3");
};
// 连续执行Consumer的Accept方法
c1.andThen(c2).andThen(c3).accept("Tom");
}
运行结果:
My name is James
Tom c1
Tom c2
Tom c3
Supplier
Supplier是提供者的意思,无传入参数,有返回值,直接理解成一个创建对象的工厂就可以了。
接口定义:

class User{ }
public static Integer supply(Supplier<Integer> supplier) {
return supplier.get();
}
public static void main(String[] args) {
Integer supply = supply(() -> {
return (int) (Math.random() * 100);
});
System.out.println(supply);
Supplier<String> supplier = String::new;
System.out.println(supplier.get());
Supplier<User> userSupplier = User::new;
User user = userSupplier.get();
}
可以看到,这个接口,只是为我们提供了一个创建好的对象,提供者,提供一个对象。
Function
Function是函数的意思,也可以表示功能的意思。
而函数经常是有输入输出的,因此它含有一个apply方法,包含一个输入与一个输出。
接口定义:

@Test
public void test() {
Function<Integer, Integer> f1 = s -> {
System.out.println("f1...run...");
return s++;
};
Function<Integer, Integer> f2 = s -> {
System.out.println("f2...run...");
return s * 2;
};
/**
* 下面表示在执行compose时,先执行f2,后执行f1, 并且f2的输出当作f1的输入。
* 相当于以下代码:
* Integer a = f2.apply(1)
* Integer b = f1.apply(a)
*/
Function<Integer, Integer> compose = f1.compose(f2);
System.out.println(compose.apply(1));
System.out.println("---黄金分割线---");
// 面表示在执行andThen时,先执行f1,后执行f2, 并且f1的输出当作f2的输入。
Function<Integer, Integer> andThen = f1.andThen(f2);
System.out.println(andThen.apply(1));
System.out.println("---黄金分割线---");
// identity方法会返回一个不进行任何处理的Function,即输出与输入值相等;
System.out.println(Function.identity().apply("hello"));
}
运行结果:

Predicate
Predicate是断定意思,判断某个东西是否满足某种条件; 因此它包含test方法,根据输入值来做逻辑判断,其结果为True或者False。

@Test
public void test() {
Predicate<String> p1 = o -> o.equals("test");
Predicate<String> p2 = o -> o.startsWith("t");
System.out.println(p1.test("test"));
// negate: 做取反处理
System.out.println(p1.negate().test("test"));
// and: 针对同一输入值,多个Predicate均返回True时返回True,否则返回False;
System.out.println(p1.and(p2).test("test"));
// or: 针对同一输入值,多个Predicate只要有一个返回True则返回True,否则返回False
System.out.println(p1.or(p2).test("test"));
}
双冒号(::)运算符的使用
什么是双冒号?
双冒号::运算符在Java 8中被用作方法引用(method reference),方法引用是与Lambda表达式相关的一个重要特性,是Lambda表达式最精简的写法。
官方解释翻译:使用Lambda表达式会创建匿名方法,但有时候需要使用一个Lambda表达式只调用一个已经存在的方法(不做其它), 所以这才有了方法引用!
个人理解双冒号的用法
(参数1,参数2) -> 类名.方法名(参数1,参数2)
等价于:
类名::方法名
双冒号相当于是把函数式接口的方法的入参作为类名.方法名()的参数执行。
Java8中方法引用的语法
- 静态方法引用(static method)语法:classname::methodname 例如:Person::getAge
- 对象的实例方法引用语法:instancename::methodname 例如:System.out::println
- 对象的超类方法引用语法: super::methodname
- 类构造器引用语法: classname::new 例如:ArrayList::new
- 数组构造器引用语法: typename[]::new 例如: String[]:new
静态方法语法
public class MainMain {
public static void main(String[] args) throws Exception {
List<String> list = Arrays.asList("aaaa", "bbbb", "cccc");
// 静态方法语法 ClassName::methodName
list.forEach(MainMain::print);
}
public static void print(String content){
System.out.println(content);
}
}
类实例方法语法
public class MainMain {
public static void main(String[] args) throws Exception {
List<String> list = Arrays.asList("aaaa", "bbbb", "cccc");
// 对象实例语法:instanceRef::methodName
list.forEach(new MainMain()::print);
}
public void print(String content){
System.out.println(content);
}
}
超类方法语法
public class Example extends BaseExample{
@Test
public void test(){
List<String> list = Arrays.asList("aaaa", "bbbb", "cccc");
// 对象的超类方法语法:super::methodName
list.forEach(super::print);
}
}
public class BaseExample {
public void print(String content){
System.out.println(content);
}
}
类构造器语法
public class Example{
@Test
public void test(){
InterfaceExample com = Example::new;
Example bean = com.create();
System.out.println(bean);
}
}
public interface InterfaceExample {
Example create();
}
带参数的构造器,示例如下:
public class Example{
private String name;
Example(String name){
this.name = name;
}
public static void main(String[] args) {
InterfaceExample com = Example::new;
Example bean = com.create("hello");
System.out.println(bean.name);
}
}
public interface InterfaceExample {
Example create(String name);
}
注意:Example 类并没有implements InterfaceExample接口!!!
数组构造器语法
public class Example{
public static void main(String[] args) {
Interface <Integer, Example[]> function = Example[]::new;
Example[] array = function.create(4); //这里的4是数组的大小
for(Example e:array){
System.out.println(e);
}
}
}
@FunctionalInterface
public interface Interface<A, T> {
T create(A a);
}
接口的默认方法和静态方法
在Java语言中,一个接口中定义的方法必须由实现类提供实现。但是当接口中加入新的API时, 实现类按照约 定也要修改实现,而Java8的API对现有接口也添加了很多方法,比如List接口中添加了sort方法。 如果按照之前的做法,那么所有的实现类都要实现sort方法,JDK的编写者们一定非常抓狂。
幸运的是我们使用了Java8,这一问题将得到很好的解决,在Java8种引入新的机制,支持在接口中声明方法同时提供实现。
默认方法案例:
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
Java 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}
public static void main( String[] args ) {
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
defaulable = DefaulableFactory.create( OverridableImpl::new );
}
由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。
尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。
使用Collections工具类排序集合
List<Employee> employees = Arrays.asList(new Employee(23), new Employee(30));
Collections.sort(employees, (x, y) -> {
return Integer.compare(x.getAge(), y.getAge());
});
List<User> list = Arrays.asList(new User(23), new User(56));
// 按自然序排序
Collections.sort(list, Comparator.comparing(User::getAge));
// 按自然序逆序
Collections.sort(list, Comparator.comparing(User::getAge).reversed());
346

被折叠的 条评论
为什么被折叠?



