Java Lambda表达式应用介绍,帮助大家快速掌握Lambda

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
CollectionremoveIf(), spliterator(), stream(), parallelStream(), forEach()
ListreplaceAll(), sort()
MapgetOrDefault(), 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呢?主要原因有亮点

  1. 简洁(后面看代码就能体会到)
  2. 对于多核友好。调用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:

  1. 使用Collectors.toMap()生成的收集器,用户需要指定如何生成Map的key和value。
  2. 使用Collectors.partitioningBy()生成的收集器,对元素进行二分区操作时用到。
  3. 使用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有一定的优化作用。具体的你们可以看我在文章开头附上的那片文章,他在后边有讲到这些。如果有时间我也会在以后的文章继续讲解这些知识。谢谢大家~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值