如果你看到下面一堆莫名其妙的代码,别人说这是lambda表达式,
然后百度搜索lambda,一头雾水,
其实不怪你,因为这里面还有方法引用、Stream流等小知识点,
如果只弄懂了lambda,也看不懂。O(∩_∩)O哈哈~
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> strings filterStrings = strings.stream()
.filter(string ->!string.isEmpty())
.collect(Collectors.joining(", "));
filterStrings.forEach(System.out::println);
下面我将带你一步一步读懂这个代码
要写出这样的代码,你需要弄懂以下四个知识点
- lambda表达式(闭包)
- 函数式接口
- 方法引用(reference)
- Stream流
lambda表达式
Lambda 表达式,也可称为闭包,允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
格式如下
(parameters) ->{ statements; }
如果statements只有一行代码
(parameters) -> expression
如果statements没有返回值,statements只有一行代码
() -> expression
如果parameters只有一个,statements只有一行代码
x -> expression
于是最简单和常用的forEach方法就出来了
List<GoodsVo> goodsVos = new ArrayList<>();
goodsVos.forEach(goodsVo -> System.out.println(goodsVo));
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
但是问题又来了,凡事有个道理,你这边怎么加个箭头就能够编译过了,这个箭头又有什么神奇的地方,我以前学java没见过这个啊!!!
下面你需要了解java8新特性之函数式接口
函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}
那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
GreetingService greetService1 = message -> System.out.println("Hey " + message);
greetService1.sayMessage("guys");
所以我们看看forEach源码,
/**
* Performs the given action for each element of the {@code Iterable}
* until all elements have been processed or the action throws an
* exception. Unless otherwise specified by the implementing class,
* actions are performed in the order of iteration (if an iteration order
* is specified). Exceptions thrown by the action are relayed to the
* caller.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* for (T t : this)
* action.accept(t);
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
再看看Consumer
/**
* Represents an operation that accepts a single input argument and returns no
* result. Unlike most other functional interfaces, {@code Consumer} is expected
* to operate via side-effects.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #accept(Object)}.
*
* @param <T> the type of the input to the operation
*
* @since 1.8
*/
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
我们再看看我们的forEach中的代码
goodsVos.forEach(goodsVo -> System.out.println(goodsVo));
作为方法,传进forEach方法中(这是lambda功能)
然后这个方法实现了Consumer的accept方法,action.accept(t);
执行的就是System.out.println(t)
(这是函数式接口的功能)
想一想为什么函数式接口中只能有一个抽象方法,为的就是你不用自己写类实现这个接口,然后重写这个方法了,这种脏活累活,我函数式接口(@FunctionalInterface)帮你干了。
再想想为什么goodsVo就是指的goodsVos中的一个对象,为什么不能自己随便定义几个变量?
原因是forEach方法定义死的,你只能这么写,action.accept(t)
中就一个入参,你写多了编译会报错。
你要想想自由,自己写函数式接口,里面定义一个抽象方法,想写几个写几个。
然后自己实现去╮( ̄▽ ̄)╭
方法引用
方法引用通过方法的名字来指向一个方法。
- 构造器引用:它的语法是Class::new
- 静态方法引用:它的语法是Class::static_method
- 特定类的任意对象的方法引用:它的语法是Class::method
- 特定对象的方法引用:它的语法是instance::method
我们看之前的forEach方法加上reference
goodsVos.forEach(System.out::println);
这里就是System.out::println指向的就是System.out.println(String s)方法,再加上goodsVo就是作为println的参数,所以一并省去,就是省到让你看不懂。
Stream
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
生成stream的方式
stream() − 为集合创建串行流。
parallelStream() − 为集合创建并行流。
所以我们来看下开始的方法,
就是将集合list生产stream流,然后使用filter方法,里面用到了lambda表达式和函数式接口,然后用collect最终操作,生成前面处理的结果。
最后forEach循环打印出来。
常用的stream流的操作方法
- Filter
接收一个lambda表达式作为过滤条件,返回一个stream流
// 过滤出data集合中小于20岁的男性
List<PersonModel> collect1 = data
.stream()
.filter(person -> ("男".equals(person.getSex())&&person.getAge()<20))
.collect(toList());
- map
map生成的是个一对一映射,for的作用
// 取出data集合中PersonModel对象的名字作为一个新的集合
List<String> collect1 = data.stream().map(PersonModel::getName).collect(toList());
- flatMap
和map类似,但不同的是可以返回一对多的的映射
//返回类型不一样
List<String> collect = data.stream()
.flatMap(person -> Arrays.stream(person.getName().split(" "))).collect(toList());
List<Stream<String>> collect1 = data.stream()
.map(person -> Arrays.stream(person.getName().split(" "))).collect(toList());
//用map实现
List<String> collect2 = data.stream()
.map(person -> person.getName().split(" "))
.flatMap(Arrays::stream).collect(toList());
//另一种方式
List<String> collect3 = data.stream()
.map(person -> person.getName().split(" "))
.flatMap(str -> Arrays.asList(str).stream()).collect(toList());
- reduce
/**
* Performs a <a href="package-summary.html#Reduction">reduction</a> on the
* elements of this stream, using the provided identity value and an
* <a href="package-summary.html#Associativity">associative</a>
* accumulation function, and returns the reduced value. This is equivalent
* to:
* <pre>{@code
* T result = identity;
* for (T element : this stream)
* result = accumulator.apply(result, element)
* return result;
* }</pre>
*
* but is not constrained to execute sequentially.
*
* <p>The {@code identity} value must be an identity for the accumulator
* function. This means that for all {@code t},
* {@code accumulator.apply(identity, t)} is equal to {@code t}.
* The {@code accumulator} function must be an
* <a href="package-summary.html#Associativity">associative</a> function.
*
* <p>This is a <a href="package-summary.html#StreamOps">terminal
* operation</a>.
*
* @apiNote Sum, min, max, average, and string concatenation are all special
* cases of reduction. Summing a stream of numbers can be expressed as:
*
* <pre>{@code
* Integer sum = integers.reduce(0, (a, b) -> a+b);
* }</pre>
*
* or:
*
* <pre>{@code
* Integer sum = integers.reduce(0, Integer::sum);
* }</pre>
*
* <p>While this may seem a more roundabout way to perform an aggregation
* compared to simply mutating a running total in a loop, reduction
* operations parallelize more gracefully, without needing additional
* synchronization and with greatly reduced risk of data races.
*
* @param identity the identity value for the accumulating function
* @param accumulator an <a href="package-summary.html#Associativity">associative</a>,
* <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function for combining two values
* @return the result of the reduction
*/
T reduce(T identity, BinaryOperator<T> accumulator);
identity:初始值
accumulator:函数式接口,传递两个值,返回一个值
实际上,reduce方法的作用就是将identity作为accumulator的一个参数,stream的遍历值作为
accumulator的另一个参数,在stream的遍历中执行accumulator.apply方法
// 0+1+2+3+4
Integer reduce1 = Stream.of(1, 2, 3, 4)
.reduce(0, (x, y) -> x + y);
System.out.println(reduce1);
// reference
Integer reduce1 = Stream.of(1, 2, 3, 4)
.reduce(0, Integer::sum);
System.out.println(reduce1);
- collect
collect在流中生成列表,map,等常用的数据结构
List<String> collect = data.stream()
.map(PersonModel::getName)
.collect(Collectors.toList());
最后,对于stream中方法的灵活运用能够很方便的处理各种集合(list、map),能过滤,能排序,能得到最大最小值,能求和等等,还可以对初始集合中的数据处理,得到一个新的集合。
综上,lambda是个很好的对新手,后期运维人员搞事情的语法糖。
菜鸟教程中讲的很不错,我就是看这个看懂的。
参考:https://www.runoob.com/java/java8-new-features.html