Day19:别说那麽多废话,讲重点

Lambda在刚开始学Java一定会很不想碰,会觉得好不容易对Java有点熟悉了,结果又搞出一整陀新的语法,心想,反正不用Lambda也都可以把逻辑写出来,能跑就好了啦,改天再学吧!
结果好死不死,我第一份工作上工後,一打开Service类别,满满的Stream操作搭配Lambda语法......

到现在总是摸熟了,老实说,就回不去原本不用Lambda的写法了,尤其是在集合的操作上,真的是好用,赞。

  1. Lambda

百闻不如一见,理论学再多不如实战一回,我们这边就来用经典的Arrays.sort(T[] a, Comparator<> c)来比较一波:(Arrays.sort())

  • 匿名类别(Anonymous Class)
String[] osArr = {"Windows", "Mac", "Linux"};
Comparator<String> byLength = new Comparator<String>({
    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
});
Arrays.sort(osArr, byLength);
  • Lambda
String[] osArr = {"Windows", "Mac", "Linux"};
Arrays.sort(osArr, (s1, s2) -> s1.length() - s2.length());

Lambda是不是简洁很多? 不过简洁不是只是偷懒,我们要思考的点在於,为什麽少写了那些东西程序一样可以跑?
关键就在於方法签章。我们可以看到,最关键的地方就是compare方法输入什麽以及输出什麽,在字串的比较上我们的compare方法要输入2个字串引数,最後会输出一个int整数,所以Lambda让我们可以只写这些最关键的地方,箭头(->)左边就是输入,右边就是输出,而且JVM会很聪明知道因为在sort方法第一个引数我们放的是String[],所以後面的Comparator也会是String系列的型别,所以我们在输入的部分甚至还不用表明型别,只写了(s1, s2)。

还有一个问题需要讨论一下,就是为什麽JVM知道我们的Lambda描述是在描述Comparator的compare方法? 介面又不是只能有一个方法,如果有两个勒?
答对了! 如果一个介面有2个方法时,还就真的不能这样用Lambda描述呢!
而这种只有一个方法需要实作的介面,就称为功能介面(Functional Interface),举凡Comparator, Runnable, Callable等等,而这些介面官方也会标注上@FunctionalInterface,我们自己也可以定义这类的介面并贴上@FunctionalInterface。

  1. Stream

介绍完Lambda後就一定要紧接着认识Stream,因为Stream真的是太好用了,写起来又爽,看起来又酷炫。
假设我们有一个字串阵列,我们想把它里面的元素转换为数字,并且只保留偶数,在中间要print一下过滤後的元素确保程序运作正常,而且过滤後的元素还要很罗唆的再加上1。这样的需求再还不会Stream时大概就会用for回圈这样写吧:

String[] arr = {"1", "2", "3", "4", "5"};
List<String> strings = Arrays.asList(arr);

List<Integer> numbersForLoop = new ArrayList<>();
for(String str : strings) {
    if(Integer.valueOf(str) % 2 == 0) {
        System.out.println(str);
        numbersForLoop.add(Integer.valueOf(str) + 1);
    }
}

需要使用for回圈搭配if条件式来写。

如果使用Stream的话:

String[] arr = {"1", "2", "3", "4", "5"};
List<String> strings = Arrays.asList(arr);

List<Integer> numbersLambda = strings.stream()
    .filter(str -> Integer.valueOf(str) % 2 == 0)
    .peek(str -> System.out.println(str))
    .map(str -> Integer.valueOf(str) + 1)
    .collect(Collectors.toList());

是不是看起来就很酷? 感觉很清爽,每个动作都变成一行,一目了然;而且也不用先在scope外面创一个空的List然後过程中装进去,这个步骤写久了就很不爽快,用stream就可以直接collect成一个List,真的爽。

若我们查看Collection官方API会发现在Java8以後新增了这个方法,会回传一个Stream< E >,E代表Collection里面各个元素的型别。这代表说我们平常用的ArrayList, HashSet等等,都可以呼叫这个方法,把一个集合转换为一条管线(Stream)。

接着我们来看看Stream官方API,可以看到里面有很多种管线操作,这边就把刚刚范例用到的说明一下。

filter(Predicate<? super T> predicate),里面要放入Predicate介面型态的实例,这是什麽鬼呢:
Predicate,原来是个Functional Interface! 那它需要继承的那个方法是啥:
boolean test(T t),是一个放入某某类别参数後,会回传boolean的方法。

说明到这边应该满清楚了吧!这时候可以回头看看范例,所以filter的操作就是我们要丢入一个实作Predicate的实例,内部的test方法实作内容会将我们管线传入的元素运算成一个boolean,true的话就代表可以继续往下其他操作,false就代表这个元素要被过滤掉了!

peek(Consumer<? super T> action),里面要放入Consumer介面型态的实例:
Consumer,一样是个Functional Interface,需要继承实作的方法是:
void accept(T t),没有回传值的一个方法。

代表说管线经过peek时元素的状态都不会被改变,会保留原样地继续往下流,不过在这中间我们就可以拿各个元素做一些事情,在范例中就是把元素打印到console。

map(Function<? super T,? extends R> mapper),里面需要Function型别的实例:
Function,Functional Interface,需要继承实作的方法是:
R apply(T t),会回传R型态,可以注意到传入的参数型态是T,这代表说经过这个方法运作後,可以产出一个不同於传入参数型别的回传值!如果只用基本型别(Primitive Type)来想可能不会觉得有什麽,但是如果把类别型别(Class Type)的概念纳进来,就会发现,wow,可以做好多事事情了。

所以在map的操作中我们就把String型别的元素转换成Integer,并且加上了1。

而管线操作方法很多都是和官方的Functional Interface一起搭配使用,这就代表我们可以用Lambda语法简洁的表达出我们想要的实作方式!

最後的collect()就是如何收集这些管线产物的方法,这边我们用了Collectors.toList(),那就是我们决定把一个个元素用List蒐集起来罗。


<<:  Day 18 - Using ASCX File to Create Pagination Function with ASP.NET Web Forms C# 建立使用者控制项 - 制作分页功能

>>:  Flutter体验 Day 24-sqflite

Security 是什麽酷东西啊

本篇要介绍的是读者在大四上学期所修的一门课,也因为这门课的关系我才开始接触网路安全以及 AIS3,如...

[Day 4] 三大法人资料(FinMind )

前言 如果是从上一篇来的读者可能会很奇怪,不是说要用爬虫抓三大法人资料吗?怎麽换标题了,而且连题目的...

Python 演算法 Day 15 - Imbalanced Data

Chap.II Machine Learning 机器学习 https://yourfreetemp...

Day29|常见的三种工作流程 - Git flow、GitHub Flow 与 Gitlab Flow

在制作专案时,大多都是与他人共同协作,当一起开发的人越来越多时,就更需要有一套规则或模式来进行合作,...

Day 5:认识CSS+CSS tag

在上一篇,我们学会如何用HTML写出'Hello World!',而这一篇,我将会教大家怎麽帮HTM...