参照学习链接原文章链接
三步理解Lambda
确认 Lambda 表达式类型
-
能用 Lambda 表达式来表示的类型,必须是一个函数式接口=> 定义=>只有一个抽象方法的接口.例如,以下为一个标准的函数式接口。
@FunctionalInterface public interface Runnable { public abstract void run(); }
@FunctionalInterface // 此注释仅在编译期帮你检查你这个接口是否符合函数式接口的条件,比如你没有任何抽象方法,或者有多个抽象方法,编译是无法通过的
// 没有实现任何抽象方法的接口 @FunctionalInterface public interface MyRunnable {} // 编译后控制台显示如下信息 Error:(3, 1) java: 意外的 **@FunctionalInterface** 注释 MyRunnable 不是函数接口 在接口 MyRunnable 中找不到抽象方法
-
Java 8 之后接口中是允许使用默认方法和静态方法的,而这些都不算抽象方法,所以也可以加在函数式接口里。例如以下的实例
@FunctionalInterface public interface Consumer<T> { void accept(T t); // 抽象方法 default Consumer<T> andThen(Consumer<? super T> after) {...} // 默认方法 }
只有一个抽象方法(accept),还有一个默认方法(方法体的代码省略了),这个也不影响它是个函数式接口。再看一个更复杂的,多了静态方法,这同样也是个函数式接口,因为它仍然只有一个抽象方法。同样的可以判断以下是是否符合只有一个抽象方法的函数式接口
@FunctionalInterface public interface Predicate<T> { // 抽象方法 boolean test(T t); // 默认方法 default Predicate<T> and(Predicate<? super T> other) {...} default Predicate<T> negate() {...} default Predicate<T> or(Predicate<? super T> other) {...} // 静态方法 static <T> Predicate<T> isEqual(Object targetRef) {...} static <T> Predicate<T> not(Predicate<? super T> target) {...} }
一句话 认准
- 一个抽象方法
- 接口
找到要实现的方法
-
实现上文中的抽象方法
@FunctionalInterface public interface Runnable { public abstract void run(); // 抽象方法 } @FunctionalInterface public interface Consumer<T> { void accept(T t); // 抽象方法 default Consumer<T> andThen(Consumer<? super T> after) {...} } @FunctionalInterface public interface Predicate<T> { boolean test(T t); // 抽象方法 default Predicate<T> and(Predicate<? super T> other) {...} default Predicate<T> negate() {...} default Predicate<T> or(Predicate<? super T> other) {...} static <T> Predicate<T> isEqual(Object targetRef) {...} static <T> Predicate<T> not(Predicate<? super T> target) {...} }
实现这个方法
-
匿名类方法(非lambda方法)
Predicate<String> predicate = new Predicate<String>() { @Override public boolean test(String s) { return s.length() != 0; } };
-
lambda表达式方法
Predicate<String> predicate = (String s) -> {return s.length() != 0;};
这个 Lambda 语法由三部分组成:
参数块:就是前面的 (String s),就是简单地把要实现的抽象方法的参数原封不动写在这。
小箭头:就是 -> 这个符号。
代码块:就是要实现的方法原封不动写在这。
-
对2的代码逐步进行简写
-
参数块
-
(String s) 里面的类型信息是多余的,可以由编译器推导,去掉它。简写为:
Predicate<String> predicate = (s) -> {return s.length() != 0;};
-
当只有一个参数时,括号也可以去掉,简写为:
Predicate<String> predicate = s -> {return s.length() != 0;};
-
-
代码块
-
方法体中只有一行代码,可以把花括号和return去掉
Predicate<String> predicate = s -> s.length() != 0;
-
-
-
lambda实现Runnable接口
-
函数式接口确认=》 √符合要求
@FunctionalInterface public interface Runnable { public abstract void run(); }
-
确定参数要实现的方法=》 run() 方法
-
lambda实现 参数部分,代码部分
Runnable runnable = () -> System.out.println("I'm running");
因为runnable抽象方法没有返回值,所以()不能省略
-
进一步讨论,在平时为进程新建线程的时候。”八股文“语法为
// 基于匿名类实现函数式接口 Runnable runnable = new Runnable(){ @Override public void run() { System.out.println("I'm running"); } } // 声明线程类 Thread thread = new Thread(runnable); // 启动线程 thread.start();
在此我们使用lambda进行替换
new Thread(()-> System.out.println("I'm running")).start();
-
完整实例
public class Runnable_test { // 方法1 public static void func_1() { Runnable runnable = new Runnable(){ @Override public void run() { System.out.println("traditional func"); } }; Thread thread = new Thread(runnable); thread.start(); } // 方法2 public static void func_2() { Runnable runnable = () -> System.out.println("lambda func1"); Thread thread = new Thread(runnable); thread.start(); } // 方法3 public static void func_3() { // Thread thread = new Thread(() -> System.out.println("I'm running")); // thread.start(); new Thread(()-> System.out.println("lambda func2")).start(); } public static void main(String[] args) { func_1(); func_2(); func_3(); } }
-
多个入参的情况,分析处理。
-
根据上面提到的方法,判断符合只有一个抽象方法的接口,则可以进行改写
@FunctionalInterface public interface BiConsumer<T, U> { void accept(T t, U u); }
Biconsumer<Random,Integer> randomNumberPrinter = (random,number)->{ for(int i=0;i<number;i++){ System.out.println("next random = " + random.nextInt()); } } randomNumberPrinter.accept(new Random(314L), 5));
-
在多参数的基础上,增加返回值
@FunctionalInterface public interface BiFunction<T, U, R> { R apply(T t, U u); // default methods removed } BiFunction<String, String, Integer> findWordInSentence = (word, sentence) -> sentence.indexOf(word);
返回值为 sentence.indexOf(word)
-
-
Stream编程
学生list实例
将student list 转换成一个 map,key 是 student 对象的 id,value 就是 student 对象本身。
List<Student> studentList = gen();
Map<String, Student> map = studentList.stream()
.collect(Collectors.toMap(Student::getId, a -> a, (a, b) -> a));
把 Lambda 表达式的部分提取出来
Collectors.toMap(Student::getId,a->a,(a,b)->a)
我们还没见过 :: 这种形式,先打回原样
Collectors.toMap(a -> a.getId(), a -> a, (a, b) -> a)
Collectors.toMap的方法源码
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction)
{
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
入参有三个,分别是Function,Function,BinaryOperator,其中 BinaryOperator 只是继承了 BiFunction 并扩展了几个方法,我们没有用到,所以不妨就把它当做BiFunction。针对Function R apply(T t) BiFunction R apply(T t, U u)以下进行拆分
- 第一个参数**a -> a.getId()**就是 R apply(T t) 的实现,入参是 Student 类型的对象 a,返回 a.getId()
- 第二个参数a -> a也是 R apply(T t) 的实现,入参是 Student 类型的 a,返回 a 本身
- 第三个参数**(a, b) -> a**是 R apply(T t, U u) 的实现,入参是Student 类型的 a 和 b,返回是第一个入参 a,Stream 里把它用作当两个对象 a 和 b 的 key 相同时,value 就取第一个元素 a
其中第二个参数 a -> a 在 Stream 里表示从 list 转为 map 时的 value 值,就用原来的对象自己,你肯定还见过这样的写法。
Collectors.toMap(a -> a.getId(), Function.identity(), (a, b) -> a)
为什么可以这样写,给你看 Function 类的全貌你就明白了。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
...
static <T> Function<T, T> identity() {
return t -> t;
}
}
看到了吧,identity 这个方法,就是帮我们把表达式给实现了,就不用我们自己写了,其实就是包了个方法。这回知道一个函数式接口**,为什么有好多还要包含一堆默认方法和静态方法了吧?就是干这个事用的。**
predict实例
我们再来试一个,Predicate 里面有这样一个默认方法。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
}
它能干嘛用呢?我来告诉你,如果没有这个方法,有一段代码你可能会这样写。
Predicate<String> p =
s -> (s != null) &&
!s.isEmpty() &&
s.length() < 5;
如果利用上这个方法,就可以变成如下这种优雅形式。
Predicate<String> nonNull = s -> s != null;
Predicate<String> nonEmpty = s -> s.isEmpty();
Predicate<String> shorterThan5 = s -> s.length() < 5;
Predicate<String> p = nonNull.and(nonEmpty).and(shorterThan5);
方法引用
针对 Student::getId 这种写法。当方法体中只有一个方法调用时,就可以作这样的简化。
比如这个 a -> a.getId() 就只是对 Student 对象上 getId() 这个方法的调用,那么就可以写成 Student::getId 这种形式。
再看几个例子
Function<String, Integer> toLength = s -> s.length();
Function<String, Integer> toLength = String::length;
Function<User, String> getName = user -> user.getName();
Function<String, Integer> toLength = User::getName;
Consumer<String> printer = s -> System.out.println(s);
Consumer<String> printer = System.out::println;
如果是构造方法的话,也可以简化。
Supplier<List<String>> newListOfStrings = () -> new ArrayList<>();
Supplier<List<String>> newListOfStrings = ArrayList::new;
总结
学会理解和写 Lamda 表达式,别忘了最开始的三步。
1. 确认 Lamda 表达式的类型
2. 找到要实现的方法
3. 实现这个方法
Lamda 表达式的类型就是函数式接口,要实现的方法就是函数式接口里那个唯一的抽象方法,实现这个方法的方式就是参数块 + 小箭头 + 方法体,其中参数块和方法体都可以一定程度上简化它的写法。
今天的文章主要就是讲怎么写出 Lamda 表达式,至于原理,之后再说。这里提个引子,你觉得 Lamda 表达式是匿名类的简化么?按照官方的说法,Lamda 表达式在某些情况下就是匿名类的一种更简单的写法,但是从字节码层面看,完全不同