目录我是扬灵,一个后端程序员,熟悉我的朋友都叫我夜猫,希望大家一起学习,共同进步,欢迎一健三连
- 🚩 Lambda表达式与函数式接口
- ❤ Lambda表达式的本质是什么?
- ❤ 什么是函数式接口?
- ❤ 为什么要引入Lambda表达式
- ❤ 如何对学生信息进行查询?
- 🍼 问题背景
- 🍼 以前是怎么做的?
- 🍼 有了Stream怎么做?
- ❤ 自己写一个函数式接口并应用
- 🍼 我们写的这个程序的利弊?
- ❤ Java中提供的函数式接口
- 🚩 工作实战
- 🍼 统计每个班的学生人数
- 🍼 找出每个班级男生人数
- 🍼 对所有学生按年龄进行排序,如果年龄相同,按照身高排序
- 🍼 对每个班的学生按年龄进行排序,如果年龄相同,按照身高排序
- 🚩 为什么不用SQL,用Stream呢?
随着jdk的升级换代,现在jdk8可以说是工作当中最常用的版本了,很多小伙伴对它在使用层面上很熟悉,但是一深问原理性的东西就有点模棱两可了。用了这么久的Lambda表达式,你知道它的本质是什么么?
Lambda表达式的本质其实就是函数式接口的匿名实现
函数式接口指的是只有一个抽象方法的的接口
,有人说,接口中default
修饰的方法影响一个接口是否为函数式接口么?不影响,因为default方法有方法体,称不上是抽象方法.
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 | 消费型 | T | void | 对类型为T的参数应用 *** 作 |
Supplier T | 供给型 | void | T | 返回类型为T的对象 |
Function T,R | 函数型 | T | R | 对类型T进行 *** 作,返回R类型的结果 |
Predicate T | 断定型 | T | Boolean | 判断类型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的风险,所以具体使用哪种方案,我们要结合自己的业务场景来进行选择和判断。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)