JAVA中LAMBDA的应用
本文主要着重讲解LAMBDA表达式的用法,关于LAMBDA的实现可能会在后续的文章中讲解。本文内容基本基于一个公众号的内容,在此基础上进行精简。如果有兴趣深入了解的同学请看链接:关于Java Lambda表达式看这一篇就够了。
Lambda表达式与匿名内部类
之所以在java中用lambda表达式其实就是可以用它来简化代替一些匿名内部类。但是Lambda不能代替所有的匿名内部类。只能用来取代函数接口的简写。
我们先来看一下Java7之前如果想用匿名内部类实现一个线程是怎么写的。
Thread t = new Thread(
new Runnable(){
@Override
public void run(){
//doSomething
}
}
);
上面的代码就是我们需要给Thread类传入一个Runnable的对象。但是这样看起来很繁重。所以Java8通过引用Lambda表达式可以简化为如下:
Thread t = new Thread(()->{//doSomething})
可以看到使用Lambda可以省略类名和方法名。如果重载的方法里面只有一行代码还可以把{}省略。
上面讲的是不带参数的写法,带参数的写法同样简单比如我们想排序一个Collection对象在Java8之前我们需要:
List<Integer> list = Arrays.asList(2,3,5,2,3);
Collections.sort(list, new Comparator<Integer>(){
@Override
public int compare(Integer a, Integer b){
return a-b;
}
});
如果用Lambda的话如下:
List<Integer> list = Arrays.asList(2,3,5,2,3);
Collections.sort(list,(a,b)->a-b);
Lambda可以省略参数类型主要是因为javac的类型推断机制。当有推断失败的时候就要手动的指定类型了。
自定义接口
我们看到Lambda是代替接口的实现。可以被Lambda实现的接口我们叫函数接口。这种接口要保证接口里面只有一个抽象方法。那么我们怎么去编写自己想要的接口呢?其实很简单,具体如下:
@FunctionalInterface
public interface FInterface<T>{
void accept(T t);
}
这里面@FunctionalInterface可以帮我们判断我们所写的接口是不是满足函数接口。
注意this
虽然我们说Lambda是代替了匿名内部类,但是Lambda的实现和匿名内部类的实现是不一样的。具体情况你可以自己写好代码之后通过javac看一下生成的class文件有什么不同或者看我在开头附上的链接。
在匿名内部类使用this指向的是类的本身,而用Lambda表达式this指向的还是外部的类。
Lambda与Collections
在Java8中新增类java.util.function包。里面包含了常用的函数接口。我们看一下各个类中都新增了什么方法:
接口名|Java8新增方法
项目 | Value |
---|---|
Collection | removeIf(), spliterator(), stream(), parallelStream(), forEach() |
List | replaceAll(), sort() |
Map | getOrDefault(), forEach(), replaceAll(), putIfAbsent(), remove(), replace(), computeIfAbsent(), computeIfPresent(), compute(), merge() |
下面我们来逐一的学习这些方法。
Collection中的新方法
1. forEach()
首先是forEach方法。我们先看一下forEach方法的源码:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
可以看到我们需要传给forEach方法一个Consumer实体对象。那Consumer是什么呢?
@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); };
}
}
可以看到Consumer就是我们之前提到的函数接口。所以我们在forEach里面用Lambda表达式可以实现这个接口。这样我们就可以轻松的遍历一遍list里面的元素了。具体如下:
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
list.forEach(a->System.out.println(a));
2. removeIf()
这个方法作用是删除满足filter指定条件的元素。依然是先看一下源码:
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
逻辑很简单就是满足这个filter条件就remove这个元素。而这个filter是一个Predicate接口的实例。我们看一下Predicate接口:
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
/**
* Returns a composed predicate that represents a short-circuiting logical
* AND of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code false}, then the {@code other}
* predicate is not evaluated.
*
* <p>Any exceptions thrown during evaluation of either predicate are relayed
* to the caller; if evaluation of this predicate throws an exception, the
* {@code other} predicate will not be evaluated.
*
* @param other a predicate that will be logically-ANDed with this
* predicate
* @return a composed predicate that represents the short-circuiting logical
* AND of this predicate and the {@code other} predicate
* @throws NullPointerException if other is null
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
* Returns a predicate that represents the logical negation of this
* predicate.
*
* @return a predicate that represents the logical negation of this
* predicate
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}
/**
* Returns a composed predicate that represents a short-circuiting logical
* OR of this predicate and another. When evaluating the composed
* predicate, if this predicate is {@code true}, then the {@code other}
* predicate is not evaluated.
*
* <p>Any exceptions thrown during evaluation of either predicate are relayed
* to the caller; if evaluation of this predicate throws an exception, the
* {@code other} predicate will not be evaluated.
*
* @param other a predicate that will be logically-ORed with this
* predicate
* @return a composed predicate that represents the short-circuiting logical
* OR of this predicate and the {@code other} predicate
* @throws NullPointerException if other is null
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/**
* Returns a predicate that tests if two arguments are equal according
* to {@link Objects#equals(Object, Object)}.
*
* @param <T> the type of arguments to the predicate
* @param targetRef the object reference with which to compare for equality,
* which may be {@code null}
* @return a predicate that tests if two arguments are equal according
* to {@link Objects#equals(Object, Object)}
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
/**
* Returns a predicate that is the negation of the supplied predicate.
* This is accomplished by returning result of the calling
* {@code target.negate()}.
*
* @param <T> the type of arguments to the specified predicate
* @param target predicate to negate
*
* @return a predicate that negates the results of the supplied
* predicate
*
* @throws NullPointerException if target is null
*
* @since 11
*/
@SuppressWarnings("unchecked")
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}
}
可以看到我们这个接口里面只有一个test函数需要实现。所以我们依然可以用Lambda表达式实现:
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5,6));
list.removeIf(a -> a>1);
我们还可以看到在Predicate接口里面还有一些default函数,这些函数可以用来配合多个filter一起使用。这里就不再赘述。大家自己试一试就可以明白,不是很难理解。
3. replaceAll()
这个方法就是对每个元素进行一个操作,之后操作的返回结果替换原来的元素。方法源码:
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
逻辑依然很简单。我们看一下UnaryOperator这个接口:
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
我们发现这个接口并没有apply方法。其实这个方法是放在了Function接口里面:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
我们发现这两个接口里面都有一个identity静态方法。这个方法其实就是返回一个apply函数为返回输入本身的Function对象。所以有的时候我们可以用identity()代替t->t。
4. sort()
这个就不多做讲解了,之前已经给出了例子,大家返回上面看一下就可以了。
5. spliterator()
这个方法和Lambda没什么关系,有兴趣的同学可以看一下这个博客:Spliterator深入解读
6. Stream和parallelStream
这两个方法会在后面详细解释,所以暂时先跳过。
Map中的新方法
在这一部分,我基于HashMap来讲解新增的函数。如果对HashMap的实现没有基础的同学请自己看一下HashMap的实现原理。
1. forEach()
先上forEach()源码:
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (Node<K,V> e : tab) {
for (; e != null; e = e.next)
action.accept(e.key, e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
基本上就是用两个for循环遍历所有的Node。之后把每个node的key和value放入BiConsumer里面的accept方法。BiConsumer的源码就不放了,感兴趣的同学可以自己看一下。
使用forEach的代码如下:
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.forEach((k, v) -> System.out.println(k + "=" + v));
2. getOrDefault()和putIfAbsent()
这两个方法跟Lambda没什么关系就不详细介绍了,但是这个方法一定掌握,因为经常会用到它,所以在这里提及一下。
3. replaceAll()
先看源码:
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Node<K,V>[] tab;
if (function == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (Node<K,V> e : tab) {
for (; e != null; e = e.next) {
e.value = function.apply(e.key, e.value);
}
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
这个方法就是根据BiFunction里面apply函数的返回值更改对应的value。简单的使用就是:
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.replaceAll((k, v) -> v.toUpperCase());
4. merge()
源码:
@Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
if (value == null || remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
Node<K,V> e = first; K k;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
}
if (old != null) {
V v;
if (old.value != null) {
int mc = modCount;
v = remappingFunction.apply(old.value, value);
if (mc != modCount) {
throw new ConcurrentModificationException();
}
} else {
v = value;
}
if (v != null) {
old.value = v;
afterNodeAccess(old);
}
else
removeNode(hash, key, null, false, true);
return v;
} else {
if (t != null)
t.putTreeVal(this, tab, hash, key, value);
else {
tab[i] = newNode(hash, key, value, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
return value;
}
}
源码稍微有点儿长。首先merge需要提供三个参数。k,v和BiFunction。之后根据提供的k找到在hashmap里面对应的value我们叫v2。如果v2不存在就把v和k关联。如果v2存在则把v2和v放到BiFunction的apply函数里面。返回的结果作为新的v。
使用如下:
map.merge(key, value, (v1, v2) -> v1+v2);
5. compute()
源码:
@Override
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
if (remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
if ((first = tab[i = (n - 1) & hash]) != null) {
if (first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else {
Node<K,V> e = first; K k;
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
}
V oldValue = (old == null) ? null : old.value;
int mc = modCount;
V v = remappingFunction.apply(key, oldValue);
if (mc != modCount) { throw new ConcurrentModificationException(); }
if (old != null) {
if (v != null) {
old.value = v;
afterNodeAccess(old);
}
else
removeNode(hash, key, null, false, true);
}
else if (v != null) {
if (t != null)
t.putTreeVal(this, tab, hash, key, v);
else {
tab[i] = newNode(hash, key, v, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
modCount = mc + 1;
++size;
afterNodeInsertion(true);
}
return v;
}
其实和merge很像。区别就是这次放到BiFunction的apply函数里面的参数是key。
可以用compute方法实现merge,代码如下:
map.compute(key, (k,v) -> v==null ? newValue : v.concat(newValue));
6. computeIfAbsent() 和 computeIfPresent()
这两个方法和compute有一点点区别。其实你们看名字就明白了。一个是如果key存在再执行apply方法,之后把k和v放入到map中。另一个就是当map中不存在key的时候调用apply方法,之后更改对应的value值。
Stream API(很牛X)
为什么我们要用Stream API呢?主要原因有亮点
- 简洁(后面看代码就能体会到)
- 对于多核友好。调用parallel()方法就可以。
这个图片展示了java中的四种stream,他们均继承自BaseStream类。IntStream,LongStream,DoubleStream分别对应三种基本类型。Stream对应剩余的类型。
大部分情况我们可以通过调用Collection.stream()得到。但是他与collection本身还是有区别的。具体区别可以看一下原博,我就不细讲了。
1. forEach()
对于这个方法其实和collection里面的forEach很像,我们直接看一下代码就可以了。
Stream<String> stream = Stream.of("I", "love", "you", "too");
stream.forEach(str -> System.out.println(str));
2. filter()
故名思议,过滤。就是把符合条件的元素留下。看一下这个抽象方法:
Stream<T> filter(Predicate<? super T> predicate);
可以看到我们就是传进去一个Predicate接口实现。这个几口我们之前已经讲过。用法如下:
Stream<String> stream= Stream.of("I", "love", "you", "too");
stream.filter(str -> str.length()==3)
.forEach(str -> System.out.println(str));
3. distinct()
去除重复的元素。用法如下:
Stream<String> stream= Stream.of("I", "love", "you", "too", "too");
stream.distinct()
.forEach(str -> System.out.println(str));
4. sorted()
排序。传入一个Comparator。用法如下
Stream<String> stream= Stream.of("I", "love", "you", "too");
stream.sorted((str1, str2) -> str1.length()-str2.length())
.forEach(str -> System.out.println(str));
5. map()
把所有元素经过处理后返回。抽象函数为:
<R> Stream<R> map(Function<? super T,? extends R> mapper)
用法为:
Stream<String> stream = Stream.of("I", "love", "you", "too");
stream.map(str -> str.toUpperCase())
.forEach(str -> System.out.println(str));
6. flatMap()
把多个集合对象拆开变成一个stream。使用样例:
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1,2), Arrays.asList(3, 4, 5));
stream.flatMap(list -> list.stream())
.forEach(i -> System.out.println(i));
Stream的reduce操作
reduce操作就是从众多元素中筛选出一个你想要的值。sum()、max()、min()、count()等都是reduce操作,将他们单独设为函数只是因为常用。
reduce在Stream里面有三种重载的方法分别是:
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
下面讲一下这三个方法有什么区别。
第一个方法应用如下:
List<Integer> numList = Arrays.asList(1,2,3,4,5);
int result = numList.stream().reduce((a,b) -> a + b ).get();
System.out.println(result);
这段代码最后返回的就是元素的累加。
那么第二个重载方法和第一基本一样,区别就是在执行的时候第一个元素并不是list里面的元素而是你放入的元素。
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3));
int x = list.stream().reduce(5,(a,b)->{
System.out.println(a+","+b);
return a+b;
});
System.out.println(x);
这个代码的输出是:
5,1
6,2
8,3
11
可以看到第一个元素是5。最后的结果也是11。
第三个方法作用特别大,就是可以进行类型转换。代码如下:
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3));
Long x = list.stream().reduce(3L,(a, b)->{
System.out.println(a+","+b);
return a+b;
},(a,b)->null);
System.out.println(x);
最后把int值转换成了long类型。最后一个元素用于并行的stream。一般用不到。
Stream里面的collect()
collect的作用就是把stream转换成其他的类比如List,Set,Map。比如:
Stream<String> stream = Stream.of("I", "love", "you", "too");
List<String> list = stream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);// 方式1
//List<String> list = stream.collect(Collectors.toList());// 方式2
System.out.println(list);
我们看到里面有用到[类名]::[方法名]。这种方式就是将类里面的方法当作lambda方法传入进去。
collect()方法有两种重载方法分别是:
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
<R, A> R collect(Collector<? super T, A, R> collector);
上面的样例代码就是用的第一种,我们需要提供三个参数。但是如果我们转换的是常用类的话,Collector这个类已经帮我们实现好了。所以我们用第二个重载方法会简单很多。例如:
Stream<String> stream = Stream.of("I", "love", "you", "too");
List<String> list = stream.collect(Collectors.toList()); // (1)
Set<String> set = stream.collect(Collectors.toSet()); // (2)
上述代码能够满足大部分需求,但由于返回结果是接口类型,我们并不知道类库实际选择的容器类型是什么,有时候我们可能会想要人为指定容器的实际类型,这个需求可通过Collectors.toCollection(Supplier collectionFactory)方法完成。
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));// (3)
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));// (4)
collect()如何生成Map
虽然Map不能转换成stream,但是反过来是可以的。有三种方法可以生成map:
- 使用Collectors.toMap()生成的收集器,用户需要指定如何生成Map的key和value。
- 使用Collectors.partitioningBy()生成的收集器,对元素进行二分区操作时用到。
- 使用Collectors.groupingBy()生成的收集器,对元素做group操作时用到。
下面分别讲述这三个方法都是什么意思
首先第一个方法为:基于元素可以传入两个lambda方法,之后这两个lambda方法的返回值就是key与value。
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3));
Map<Integer, String> map = list.stream().collect(Collectors.toMap(a->a+1,a->a.toString()));
System.out.println(map);
输出为:
{2=1, 3=2, 4=3}
第二个方法就是将元素分为两个部分,根据就是第二个传入的lambda函数。我们可以把它看作一个条件。满足条件就放到第一个集合,不满足就是第二个。
比如:
Map<Boolean, List<Student>> passingFailing = students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
第三个方法其实课sql里面的groupby一样。根据一个lambda函数返回的值不同,进行不同的分类。
代码如下:
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
collect()还可以帮我们进行字符串的拼接:
Stream<String> stream = Stream.of("I", "love", "you");
String joined = stream.collect(Collectors.joining());// "Iloveyou"
String joined = stream.collect(Collectors.joining(","));// "I,love,you"
String joined = stream.collect(Collectors.joining(",", "{", "}"));// "{I,love,you}"
Lambda表达式的用法介绍就全部结束了,但是他的内部原理还要更加的复杂。这里涉及到对于Stream这个接口的实现,也就是ReferencePipeline这个类。他对Stream有一定的优化作用。具体的你们可以看我在文章开头附上的那片文章,他在后边有讲到这些。如果有时间我也会在以后的文章继续讲解这些知识。谢谢大家~