Lambda表达式Stream流
一、Lambda表达式
2.1 概述
Lambda是IDK8中一个语法糖。他可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个重要体现。让我们不用关注是什
么对象。而是更关注我们对数据进行了什么操作。
2.2 基本格式
(参数列表)->{代码}
例一
我们在创建线程并启动时可以使用匿名内部类的写法:
new Thread(new Runnable(){@overridepublic void run(){System.out.printin("你知道吗 我比你 想象的 更想在你身边”);
}).start();
可以使用Lambda的格式对其进行修改:
new Thread(()->{System.out.print]n("你知道吗 我比你想象的 更想在你身边");}).start();
2.3 省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号可以省略
- 以上这些规则都记不住也可以省略不记
二、Stream流
2.1 概述
Java8的stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作。可以更方便的让我们
对集合或数组操作。
2.2 案例准备
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Author {
//id
private Long id;
//姓名
private String name;
//年龄
private Integer age;//简介
private String intro;
//作品
private List<Book> books ;
}
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Book {private Long id;private String name;private String category;private Integer score;private String intro;
}
public class StreamDemo {private static List<Author> getAuthors() {// 数据初始化Author author = new Author(1L, "蒙多", 33, "一个从菜刀中明悟哲理的祖安人", null);Author author2 = new Author(2L, "亚拉索", 15, "狂风也追逐不上他的思考速度", null);Author author3 = new Author(3L, "易", 14, "是这个世界在限制他的思维", null);// 修正:将重复ID的作者改为唯一IDAuthor author4 = new Author(3L, "易", 14, "是这个世界在限制他的思维", null);// 书籍列表List<Book> books1 = new ArrayList<>();List<Book> books2 = new ArrayList<>();List<Book> books3 = new ArrayList<>();books1.add(new Book(1L, "刀的两侧是光明与黑暗", "哲学,爱情", 88, "用一把刀划分了爱恨"));books1.add(new Book(2L, "一个人不能死在同一把刀下", "个人成长,爱情", 99, "讲述如何从失败中明悟真理"));books2.add(new Book(3L, "那风吹不到的地方", "哲学", 85, "带你用思维去领略世界的尽头"));// 修正:移除重复的书籍// books2.add(new Book(3L, "那风吹不到的地方", "哲学", 85, "带你用思维去领略世界的尽头"));books2.add(new Book(4L, "吹或不吹", "爱情,个人传记", 56, "一个哲学家的恋爱观注定很难把他所在的时代理解"));books3.add(new Book(5L, "你的剑就是我的剑", "爱情", 56, "无法想象一个武者能对他的伴侣这么的宽容"));books3.add(new Book(6L, "风与剑", "个人传记", 100, "两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));// 修正:移除重复的书籍// books3.add(new Book(6L, "风与剑", "个人传记", 100, "两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));author.setBooks(books1);author2.setBooks(books2);author3.setBooks(books3);author4.setBooks(books3);//Arrays.asList() 是 Java 标准库中的一个静态方法,用于将一组元素转换为一个固定大小的列表。// 这里它将四个 Author 对象转换为列表,并作为参数传递给 ArrayList 的构造函数。List<Author> authorList = new ArrayList<>(Arrays.asList(author, author2, author3, author4));return authorList;}
}
2.3 快速入门
2.3.1 需求
我们可以调用getAuthors方法获取到作家的集合。现在需要打印所有年龄小于18的作家的名字,并且要注意去重。
代码实现
public static void main(String[] args) {List<Author> authors = getAuthors();authors.stream()//要把集合先转换成stream对象.distinct()//去重.filter(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getAge() < 18;}}).forEach(new Consumer<Author>() {@Overridepublic void accept(Author author) {System.out.println(author.getName());}});
}
2.4 常用操作
2.4.1 创建流
单列集合:集合对象.stream()
List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();
数组:Arrays.stream(数组)或者使用Stream.of来创建
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> arr1 = Stream.of(arr);
双列集合:转换成单列集合后再创建
private static void test04() {HashMap<String, Integer> map = new HashMap<>();map.put("蜡笔小新",19);map.put("黑子",17);map.put("迪迦",14);Set<Map.Entry<String, Integer>> entries = map.entrySet();Stream<Map.Entry<String, Integer>> stream = entries.stream();stream.filter(entry -> entry.getValue() > 15).forEach(new Consumer<Map.Entry<String, Integer>>() {@Overridepublic void accept(Map.Entry<String, Integer> entry) {System.out.println(entry.getKey() + " : "+entry.getValue());}});
}
2.4.2 中间操作
filter
可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。
map
可以把对流中的元素进行计算或转换。
private static void test04() {HashMap<String, Integer> map = new HashMap<>();map.put("蜡笔小新",19);map.put("黑子",17);map.put("迪迦",14);Set<Map.Entry<String, Integer>> entries = map.entrySet();Stream<Map.Entry<String, Integer>> stream = entries.stream();stream.filter(entry -> entry.getValue() > 15).forEach(new Consumer<Map.Entry<String, Integer>>() {@Overridepublic void accept(Map.Entry<String, Integer> entry) {System.out.println(entry.getKey() + " : "+entry.getValue());}});
}
distinct
可以去除流中的重复元素
private static void test06() {List<Author> authors = getAuthors();authors.stream().distinct().forEach(a -> System.out.println(a.getName()));
}
需要注意的是,去重时是通过Object的equals方法来实现的。
实际操作时可以通过给实体类添加**@EqualsAndHashCode**来实现
sorted
可以对流中的元素进行排序。
例如:
对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
private static void test07() {List<Author> authors = getAuthors();authors.stream().distinct().sorted((o1, o2) -> o2.getAge() - o1.getAge()).forEach(author -> System.out.println(author.getName() +" 的年龄是"+ author.getAge()));
}
sorted有空参和有参两个重载形式,空参要求元素实现了Comparable接口,有参要求传入Comparator。
limit
可以设置流的最大长度,超出的部分将被抛弃
例如:
对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名。
private static void test08() {List<Author> authors = getAuthors();authors.stream().distinct().sorted((o1, o2) -> o2.getAge() - o1.getAge()).limit(2).forEach(author -> System.out.println(author.getName() +" 的年龄是"+ author.getAge()));}
skip
跳过流中的前n个元素,返回剩下的元素
例如:
打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。
private static void test09() {List<Author> authors = getAuthors();authors.stream().distinct().sorted((o1,o2) -> o2.getAge()-o1.getAge()).skip(1).forEach(a -> System.out.println(a.getName() + "的年龄是" + a.getAge()));
}
flatMap
map只能把一个对象转换成另一个对象来作为流中的元素。二flatMap可以把一个对象转换成多个对象作为流中的元素。
例一:
打印所有书籍的名字,要求对重复的元素进行去重。
//匿名内部类
private static void test10() {List<Author> authors = getAuthors();authors.stream().flatMap(new Function<Author, Stream<Book>>() {@Overridepublic Stream<Book> apply(Author author) {return author.getBooks().stream();}}).distinct().forEach(book -> System.out.println(book.getName()) );
}
例二:
打印现有数据的有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情
private static void test11() {List<Author> authors = getAuthors();authors.stream().flatMap(author -> author.getBooks().stream()).distinct().flatMap(book -> Arrays.stream(book.getCategory().split(","))).distinct().forEach(s -> System.out.println(s));
}
2.4.3 终结操作
forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体操作。
count
可以用来获取当前流中元素的个数
例如:
打印这些作家的所出书籍的数目,注意删除重复元素
private static void test12() {List<Author> authors = getAuthors();long count = authors.stream().flatMap(author -> author.getBooks().stream()).distinct().count();System.out.println(count);
}
max&min
可以用来获取流中的最值
例子:分别获取这些作家的所出书籍的最高分和最低分并打印。
private static void test13() {List<Author> authors = getAuthors();Optional<Integer> maxScore = authors.stream().flatMap(author -> author.getBooks().stream()).map(book -> book.getScore()).max((o1, o2) -> o1 - o2);System.out.println("最大值:" + maxScore.get());Optional<Integer> min = authors.stream().flatMap(author -> author.getBooks().stream()).map(book -> book.getScore()).min((o1, o2) -> o1 - o2);System.out.println("最小值:" + min.get());
}
collect
把当前流转换成一个集合
例子:
获取一个存放所有作者姓名的List集合用**流.collect(Collectors.toList())**其中Collectors.中有很多方法
private static void test16() {List<Author> authors = getAuthors();List<String> collect = authors.stream().map(author -> author.getName()).collect(Collectors.toList());collect.forEach(System.out::println);
}
获取一个所有书名的Set集合
private static void test17() {List<Author> authors = getAuthors();Set<Book> collect = authors.stream().flatMap(author -> author.getBooks().stream()).distinct().collect(Collectors.toSet());System.out.println(collect);
}
获取一个map集合,map的kek为作者名,value为List
private static void test18() {//获取一个map集合,map的kek为作者名,value为List<Book>List<Author> authors = getAuthors();Map<String, List<Book>> collect = authors.stream().distinct().collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
}
查找和匹配
anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型。
例子:
判断是否有年龄在29以上的作家
private static void test19() {List<Author> authors = getAuthors();System.out.println(authors.stream().anyMatch(author -> author.getAge() > 29));
}
allMatch
可以用来判断是否都符合匹配条件,结果为Boolean类型。如果都符合结果为true,否则结果为false。
例子:
判断是否所有作家都大于十岁
private static void test20() {List<Author> authors = getAuthors();System.out.println(authors.stream().allMatch(author -> author.getAge() > 10));
}
noneMatch
可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为true,否则结果为false
findAny
获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素。
例子:
获取任意一个大于18的作家,如果存在就输出他的名字。
private static void test21() {List<Author> authors = getAuthors();Optional<Author> any = authors.stream().filter(author -> author.getAge() > 9).findAny();any.ifPresent(author -> System.out.println(author.getName()));
}
findFirst
获取流中的第一个元素。
例子:
获取一个年龄最小的作家,并输出他的姓名。
private static void test22() {List<Author> authors = getAuthors();Optional<Author> first = authors.stream().sorted((o1, o2) -> o1.getAge() - o2.getAge()).findFirst();first.ifPresent(author -> System.out.println(author.getName()));
}
reduce归并
对流中的数据按照你指定的计算方式计算出一个结果
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,他会按照我们的计算方式依次拿流中的元素和初始值进行计算,计算结果再和后面的元素计算。
可以类比一下reduce两个参数重载形式:
例子:
求出所有作者年龄之和
private static void test23() {List<Author> authors = getAuthors();Integer sum = authors.stream().distinct().map(author -> author.getAge()).reduce(0, (result, element) -> result + element);System.out.println(sum);//62
}
使用reduce求年龄最大值
private static void test24() {List<Author> authors = getAuthors();Integer reduce = authors.stream().distinct().map(Author::getAge).reduce(Integer.MIN_VALUE, (result,element)->result>element?result:element);System.out.println(reduce);
}
使用reduce求年龄最小值
private static void test25() {List<Author> authors = getAuthors();Integer reduce = authors.stream().distinct().map(Author::getAge).reduce(Integer.MAX_VALUE, (result,element)->result<element?result:element);System.out.println(reduce);
}
reduce一个参数的重载形式(Optional reduce(BinaryOperator accumulator);)实际就是把流中第一个元素赋值给result,源代码:
boolean foundAny = false;
T result = null;
for (T element : this stream) {if (!foundAny) {foundAny = true;result = element;}elseresult = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();
用一个参数的reduce实现求年龄最小值:
private static void test26() {List<Author> authors = getAuthors();Optional<Integer> reduce = authors.stream().distinct().map(Author::getAge).reduce((integer, integer2) -> integer < integer2 ? integer:integer2);reduce.ifPresent(System.out::println);
}
2.5 注意事项
惰性求值:如果没有终结操作,中间操作是不会得到执行的。
流是一次性的:一旦一个流对象经过一个终结操作后,这个流就不能再被使用。
不会影响原数据:我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。
三、Optional
4.1 概述
我们在编写代码的时候出现最多的就是空指针异常。所以在很多情况下我们需要做各种非空的判断。
尤其是对象中的属性还是个对象的情况下。这种判断会更多。
而过多的判断语句会让我们的代码显得臃肿不堪。
所以在JDK8中引入了Optional,养成使用Optional的习惯后你可以写出更优雅的代码来避免空指针异常。
并且在很多函数式编程相关的API中也都用到了Optional,如果不会使用Optional也会对函数式编程的学习造成影响。
4.2 使用
4.2.1 创建对象
搭配**ifPresent()**消费数据
我还是想坚持我不轻易结束的原则,既然我们相遇了,就珍惜彼此。
四、函数式接口
5.1 概述
只有一个抽象方法的接口我们称之为函数接口。
JDK的函数式接口都加上了@functionalnterface 注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式
接口。
五、方法引用
我们在使用lambda时,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。
6.1 推荐用法
我们在使用lambda时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。当我们方法引用使用的多了慢慢的也可以直接写出方法引用。
6.2 基本格式
类名或者对象名::方法名