java8特性之“看不懂的lambda”

如果你看到下面一堆莫名其妙的代码,别人说这是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);

下面我将带你一步一步读懂这个代码

要写出这样的代码,你需要弄懂以下四个知识点

  1. lambda表达式(闭包)
  2. 函数式接口
  3. 方法引用(reference)
  4. 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容