Stream和Lambda表达式还不熟悉?那你真的应该看看这篇文章了

在这里插入图片描述

我是扬灵,一个后端程序员,熟悉我的朋友都叫我夜猫,希望大家一起学习,共同进步,欢迎一健三连

🚩 Lambda表达式与函数式接口

❤ Lambda表达式的本质是什么?

随着jdk的升级换代,现在jdk8可以说是工作当中最常用的版本了,很多小伙伴对它在使用层面上很熟悉,但是一深问原理性的东西就有点模棱两可了。用了这么久的Lambda表达式,你知道它的本质是什么么?
Lambda表达式的本质其实就是函数式接口的匿名实现

❤ 什么是函数式接口?

函数式接口指的是只有一个抽象方法的的接口,有人说,接口中default修饰的方法影响一个接口是否为函数式接口么?不影响,因为default方法有方法体,称不上是抽象方法.

❤ 为什么要引入Lambda表达式

Java在JDK1.8中引入了Lambda表达式,它允许我们将函数作为方法的参数进行传递,这在java以前的版本中是不支持的,如果你写过JavaScript,那你一定对下面的代码不陌生:

function f1() {
    console.log("hello javascript")
}

function f2(fun) {
    fun();
}

f2(f1);//hello javascript

上面的代码定义了两个函数,其中f2接收一个参数,这个参数可以是任意类型,在我们这个例子里它是一个函数,在f2中,我们执行了外界传递的函数,在这个例子里我们传递的参数是f1
通过上面的例子,我们就可以对Lamada表达式的好处进行一个猜测,既然在JDK1.8中允许我们传递一个函数式接口-- 只有一个方法的接口,那么好处显而易见–我们可以在参数中定义数据处理的逻辑

❤ 如何对学生信息进行查询?

🍼 问题背景

首先我们,准备了一些学生信息,如下所示:

List<Student> students = Arrays.asList(
            new Student("张三", "男", 18, 1.78, 1),
            new Student("李四", "男", 26, 1.83, 2),
            new Student("王五", "男", 24, 1.72, 1),
            new Student("七七", "女", 19, 1.65, 2),
            new Student("小红", "女", 18, 1.63, 1),
            new Student("小花", "女", 27, 1.70, 2),
            new Student("芳芳", "女", 28, 1.67, 3));

现在我们的需求是:找出所有的女生

🍼 以前是怎么做的?

在没有Lamada表达式以前,我们需要这么来做:

  • 声明一个新的结合来保存所有女孩的值
  • 遍历students,判断如果对象的性别是女,就把这个对象添加到新的集合中
  • 返回新的集合
 ArrayList<Student> resultList = new ArrayList<>();
        for (Student student : students) {
            if ("女".equals(student.getGender())) {
                resultList.add(student);
            }
        }
        System.out.println(resultList);

你会发现整个代码还是比较麻烦的,下面我们看看有了Lambda表达式和Stream,我们该怎么做

🍼 有了Stream怎么做?
List<Student> resultList = students.stream().filter(item -> "女".equals(item.getGender())).collect(Collectors.toList());
System.out.println(resultList);

上面的代码是把students 转化成流,然后调用Java提供的filter方法,它的参数是一个函数式接口

Stream<T> filter(Predicate<? super T> predicate);
@FunctionalInterface
public interface Predicate<T> {
}

最后把函数式接口处理完毕的流收集成一个List.通过这个简单的例子,我们可以感受到Stream搭配Lambda表达式的便捷性。

❤ 自己写一个函数式接口并应用

上面我们提到了,函数式接口就是只有一个抽象方法的接口,使用@FunctionalInterface注解标注它,下面我们自己来写一个

package com.nightcat.myInterface;

@FunctionalInterface
public interface StringInterface<T> {
    T get(T t);
}

我们上面声明了一个函数式接口,里面的get 方法的参数和返回值一样,也就是说我们只对入参进行处理,而不改变它的类型
接下来我们写一个方法,方法的参数有两个:自定义的函数式接口需要函数式接口处理的参数

package com.nightcat.test;

import com.nightcat.myInterface.StringInterface;
import org.junit.Test;

public class FunctionalTest {
    public String toUpperCase(StringInterface<String> si, String target) {
        return si.get(target);
    }

    @Test
    public void test() {
        String s = toUpperCase(str ->
                str.toUpperCase(), "hello,world");
        System.out.println(s);//HELLO WORLD
    }

}

观察我们写的这个程序,我们接受的这个函数没有实现,使我们在调用它的时候,给它提供了一个实现。

🍼 我们写的这个程序的利弊?

我们上面这个例子,在我们使用了函数式接口以后,在处理问题上变得方便了很多,可是我们不能每写一个程序,为了方便就自定义一个函数式接口,这是不现实的。那么Java中给我们内置了大量的函数式接口,我们直接用就可以了

❤ Java中提供的函数式接口

Java中大概提供了以下四种函数式接口:

函数式接口描述参数返回值用途
Consumer T消费型Tvoid对类型为T的参数应用操作
Supplier T供给型voidT返回类型为T的对象
Function T,R函数型TR对类型T进行操作,返回R类型的结果
Predicate T断定型TBoolean判断类型T是否满足某种条件的约束,并返回boolean值

🚩 工作实战

上面讲了很多理论性的东西,接下来我们举几个例子,来实战一下Stream和Lambda表达式

🍼 统计每个班的学生人数

还是以我们上边的例子为背景。我们要统计每个班的学生人数,肯定是对班级进行分组,然后再统计

 Map<Integer, Long> map = students.stream().collect(Collectors.groupingBy(Student::getClazz, Collectors.counting()));
        map.forEach((key, value) -> {
            System.out.println("key " + key);
            System.out.println("value " + value);
        });
🍼 找出每个班级男生人数
  • 先过滤出所有男生
  • 对过滤之后的数据进行分组统计
 Map<Integer, Long> map = students
                .stream()
                .filter(item -> "男".equals(item.getGender()))
                .collect(Collectors.groupingBy(Student::getClazz, Collectors.counting()));
        
        map.forEach((key, value) -> {
            System.out.println("key " + key);
            System.out.println("value " + value);
        });
🍼 对所有学生按年龄进行排序,如果年龄相同,按照身高排序
 List<Student> resultList = students.stream()
                .sorted(Comparator.comparing(Student::getAge)
                        .thenComparing(Student::getHeight))
                .collect(Collectors.toList());
        System.out.println(resultList);
🍼 对每个班的学生按年龄进行排序,如果年龄相同,按照身高排序

这个问题,如果用SQL来做的话,就是分组排序,那么stream也可以直接来做。

 Map<Integer, List<Student>> map = students
                .stream()
                .sorted(Comparator.comparing(Student::getAge).thenComparing(Student::getHeight))
                .collect(Collectors.groupingBy(Student::getClazz));
        System.out.println(map);

🚩 为什么不用SQL,用Stream呢?

好了,我们上面对Stream和Lambda表达式有了一个初步的认识,我们来思考一下:有SQL了,用stream干嘛呀?
因为我们知道,数据库中处理数据的速度是很快的,而且我们可以对数据库中的索引进行优化处理,那么我们使用stream的目的是什么呢?原因主要有以下两点

  • 在数据量不大的前提下,我们把数据拿到内存中来处理,可以方便编码,另外因为我们的数据源有可能不是我们自己编写的SQL语句得到的,可能是其他的业务系统或者是数据中台得到的数据,它们可能来自好几个库,那么这个时候使用先拿到数据之后,同一处理比在SQL中要好的多。
  • 第二个原因就是,我们往往在数据加工的过程中,需要对数据进行一些处理,比如先进行一些业务逻辑的判断,这个时候在程序中处理业务逻辑,也要比在SQL中处理要好很多。
  • 最后,如果我们的数据量特别大,那么我们就不能把数据一股脑的加载到内存,然后用Stream处理,虽然内存的速度很快,但在数据量特别大前提下,我们也会有OOM的风险,所以具体使用哪种方案,我们要结合自己的业务场景来进行选择和判断。
  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜猫nightcat

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值