优点:
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 易于"并发编程"
lambda表达式
对某些匿名内部类的写法进行简化,是函数式编程思想的一个重要体现。让我们不用关注是什么对象。而是更关注我们对数据进行了什么操作。
省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号可以省略
- 以上这些规则都记不住也可以省略不记
快捷键
匿名内部类–>lambda表达式
alt+enter lambda表达式—>匿名内部类
基本格式
(参数列表)->{代码}
使用示例
示例1:
现有方法定义如下,其中IntBinaryOperator是一个接口。
//方法的入参是IntBinaryOperator对,此对象有applyAsInt抽象方法,给抽象方法传参
public static int calculateNum(IntBinaryOperator operator){
int a = 10;
int b = 20;
return operator.applyAsInt(a,b);
}
使用匿名内部类的写法调用
@Test
public void test2(){
//调用calculateNum方法,抽象方法的具体功能编写
int i = calculateNum(new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return left + right;
}
});
System.out.println("结果:" + i);
使用lambda表达式
@Test
public void test2(){
int i1 = calculateNum((int left, int right) -> { return left + right;});
}
// 再省略int i2 = calculateNum(Integer::sum); (方法引用)
示例2
public static void printNum(IntPredicate predicate){
int[] arr = {1,2,3,4,5,6,7,8,9};
for (int i : arr) {
//给抽象方法传参,根据抽象方法的结果做处理
if(predicate.test(i)){
System.out.print(i);
System.out.print(",");
}
}
}
使用匿名内部类的写法调用
@Test
public void test3(){
printNum(new IntPredicate() {
@Override
public boolean test(int value) {
return value%2 == 0;
}
});
}
使用lambda表达式
@Test
public void test3(){
printNum((int value) ->{return value %2 == 0;});
}
示例3:现有方法定义如下,其中Function是一个接口
public static<R> R typeConver(Function<String,R> function){
String str = "12345";
return function.apply(str);
}
用匿名内部类的写法
@Test
public void test4(){
//第一个参数:入参类型,第二个参数:返回值类型
Integer int1 = typeConver(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
});
System.out.println("结果:"+int1);
}
Lambda写法:
@Test
public void test4(){
Integer int2 = typeConver(value -> Integer.parseInt(value));
}
//再省略Integer int2 = typeConver(Integer::parseInt);(方法引用)
实例4:现有方法定义如下,其中IntConsumer是一个接口
public static void foreachArr(IntConsumer consumer){
int[] arr = {1,2,3,4,5,6,7,8,9};
for (int i : arr) {
consumer.accept(i);
}
}
用匿名内部类的写法
@Test
public void test5(){
foreachArr(new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
});
}
Lambda写法:
@Test
public void test5(){
foreachArr(value -> System.out.println(value));
}
//再省略foreachArr(System.out::println);
Stream流
Java8的Stream使用的是函数式编程模式,可以被用来对集合或数组进行链状流式的操作。可以更方便的让我们对集合或数组操作。
注意事项
- 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
- 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
- 不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)
实体类准备
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
实体类:
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Author {
//id
private Long id;
//姓名
private String name;
//年龄
private Integer age;
//简介
private String intro;
//作品
private List<Book> books;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Book {
//id
private Long id;
//书名
private String name;
//分类
private String category;
//评分
private Integer score;
//简介
private String intro;
}
数据初始化
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);
Author 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);
List<Author> authorList = new ArrayList<>(Arrays.asList(author,author2,author3,author4));
return authorList;
}
创建流
单列集合:集合对象.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> stream2 = Stream.of(arr);
双列集合map:转成单列集合后再创建
Map<String,Integer> map = new HashMap<>();
map.put("蜡笔小新",19);
map.put("黑子",17);
map.put("日向翔阳",16);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
中间操作
filter:对流中的元素进行条件过滤
判断结果为true,留在流里,false过滤掉
@Test
public void test03() {
List<Author> authors = getAuthors();
authors.stream().filter(author -> author.getName().length()>1).forEach(author -> System.out.println(author.getName()));
}
map:对流中的元素进行计算或转换
把一个对象转换成另一个对象来作为流中的元素
@Test
public void test04() {
//打印所有作家的姓名
List<Author> authors = getAuthors();
authors.forEach(author -> System.out.println(author.getName()));
//转换成只有name的流
authors.stream().map(author -> author.getName()).forEach(s -> System.out.println(s));
//转换成只有age的流,对age进行操作
authors.stream().map(Author::getAge).map(age->age+10).forEach(System.out::println);
}
distinct:去重操作
需要实体类重写equals和hashCode方法
@Test
public void test05() {
List<Author> authors = getAuthors();
authors.stream().map(Author::getName).distinct().forEach(System.out::println);
authors.stream().distinct().forEach(author -> System.out.println(author.getName()));
}
sorted 排序
- 空参,需要实体类继承Comparable接口
public class Author implements Comparable<Author>{
属性省略.....
@Override
public int compareTo(Author o) {
//本体-比较 升序
return this.getAge()-o.getAge();
}
}
@Test
public void test06() {
List<Author> authors = getAuthors();
//空参
authors.stream().distinct().sorted().forEach(author-> System.out.println(author.getAge()));
}
- 有参,自己写比较接口
@Test
public void test06() {
List<Author> authors = getAuthors();
//实参
authors.stream().distinct().sorted((o1, o2) -> o1.getAge()-o2.getAge()).forEach(author -> System.out.println(author.getAge()));
authors.stream().distinct().sorted(Comparator.comparingInt(Author::getAge)).forEach(author -> System.out.println(author.getAge()));
}
limit:只要几个元素
//只要年龄最大的两个作家的姓名
@Test
public void test07() {
List<Author> authors = getAuthors();
authors.stream().distinct().sorted((o1, o2) -> o2.getAge()-o1.getAge()).limit(2).forEach(author -> System.out.println(author.toString()));
}
skip:跳过几个元素
//去重,降序,不要年龄最大的那个
@Test
public void test08() {
List<Author> authors = getAuthors();
authors.stream().distinct().sorted((o1,o2)-> o2.getAge()-o1.getAge()).skip(1).forEach(author-> System.out.println(author.toString()));
}
flatMap
map只能把一个对象转换为另一个对象来作为流中的元素,而flatMap可以把一个对象转换成多个对象作为流中的元素
@Test
public void test09() {
List<Author> authors = getAuthors();
//map()方法之后的返回对象是Stream<List<Book>>
authors.stream()
.map(author -> author.getBooks())
.forEach(new Consumer<List<Book>>() {
@Override
public void accept(List<Book> books) {
System.out.println(books);
}
});
//返回值为一个stream流并且获取书籍名称
//flatMap()方法之后的返回对象是Stream<Book>
authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct().forEach(book -> System.out.println(book.getName()));
//打印现有数据的所有分类,要求对分类进行去重,不能出现哲学,爱情的格式
authors.stream().
flatMap(author -> author.getBooks().stream())
.flatMap(book -> Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(System.out::println);
}
终结操作
forEach:对流中的元素进行遍历操作
通过传入的参数去指定对遍历到的元素进行什么具体操作
输出所有作家的名字
@Test
public void test10() {
List<Author> authors = getAuthors();
//authors.stream().distinct().forEach(author-> System.out.println(author.getName()));
authors.stream().map(Author::getName).distinct().forEach(System.out::println);
}
count:获取当前流中元素的个数
//作家的所出书籍的数目,注意删除重复元素
@Test
public void test11() {
List<Author> authors = getAuthors();
long count = authors.stream().flatMap(author -> author.getBooks().stream()).distinct().count();
System.out.println(count);
}
min&max:获取流中的最值
//min max分别获取作家的所出书籍的最高分和最低分并打印
@Test
public void test12() {
List<Author> authors = getAuthors();
Optional<Integer> max = authors.stream().flatMap(author -> author.getBooks().stream()).map(Book::getScore).max((score1, score2) -> score1 - score2);
//Optional<Integer> max = authors.stream().flatMap(author -> author.getBooks().stream()).map(Book::getScore).max(Comparator.comparingInt(score -> score));
System.out.println(max.get());
Optional<Integer> min = authors.stream().flatMap(author -> author.getBooks().stream()).map(author -> author.getScore().distinct().min((score1, soure2) -> score1 - soure2);
//Optional<Integer> min = authors.stream().flatMap(author -> author.getBooks().stream()).map(Book::getScore).distinct().min(Comparator.comparingInt(score -> score));
System.out.println(min.get());
}
collect:把流转换成一个集合
public void test13() {
List<Author> authors = getAuthors();
//获取一个存放所有作者名字的list集合
List<String> collect = authors.stream().map(Author::getName).distinct().collect(Collectors.toList());
System.out.println(collect);
//获取一个所有书名的set集合
Set<String> collect1 = authors.stream().flatMap(author -> author.getBooks().stream()).map(Book::getName).distinct().collect(Collectors.toSet());
System.out.println(collect1);
//获取一个map集合,map的key为坐着名,value为list<Book>
//map的key和value分别是两个function函数
Map<String, List<Book>> collect2 = authors.stream().distinct().collect(Collectors.toMap(Author::getName, Author::getBooks));
System.out.println(collect2);
}
查找与匹配
anyMatch:任意一个匹配
//判断是否有年龄在29以上的作家 anyMatch,任意一个匹配
List<Author> authors = getAuthors();
//boolean b = authors.stream().map(Author::getAge).anyMatch(age -> age > 29);
boolean b = authors.stream().anyMatch(author -> author.getAge() > 29);
System.out.println(b);
allMatch:所有都匹配
boolean b1 = authors.stream().allMatch(author -> author.getAge() > 18);
System.out.println(b1);
noneMatch:没有一个匹配
//判断作家是否都没有超过100岁 所有都没有noneMatch
boolean b2 = authors.stream().noneMatch(author -> author.getAge() > 100);
System.out.println(b2);
findFirst:获取流中的第一个元素
//findFirst 获取流中的第一个元素
//Optional<Author> first = authors.stream().sorted(((o1, o2) -> o1.getAge()-o2.getAge())).findFirst();
Optional<Author> first = authors.stream().sorted((Comparator.comparingInt(Author::getAge))).findFirst();
first.ifPresent(author -> System.out.println(author.getName()));
findAny:获取任意一个元素
没办法保证获取的一定是流中的第一个元素
//获取任意一个年龄大于18的作家,如果存在就输出他的名字
Optional<Author> any = authors.stream().filter(author -> author.getAge()>98).findAny();
any.ifPresent(author -> System.out.println(author.getName()));
reduce 归并
- reduce对流中的数据按照制定的计算方式计算出一个结果(缩减操作)
- reduce的作用是把stream中的元素给组合起来,可以传入一个初始值,会按照我们的计算方式依次拿流中的元素和在初始化值的基础上进行计算,计算结果再和后面的元素计算。
一个参数
//reduce一个参数的内部代码
boolean foundAny = false;
T result = null;
for (T element : this stream) {
if (!foundAny) {
foundAny = true;
result = element;
}else
result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();
示例:
@Test
public void test16() {
List<Author> authors = getAuthors();
//使用reduce求所有作者年龄的和
Optional<Integer> all = authors.stream().map(Author::getAge).reduce(Integer::sum);
all.ifPresent(System.out::println);
//使用reduce求所有作者中年龄的最小值
Optional<Integer> min = authors.stream().distinct().map(Author::getAge).reduce((result, element) -> result > element ? element : result);
min.ifPresent(System.out::println);
}
两个参数
//reduce两个参数的内部计算:
T result = identity;
for(T element: this stream)
result = accumulator.apply()result,element
return result;
示例:
@Test
public void test15() {
List<Author> authors = getAuthors();
Integer all = authors.stream().distinct().map(Author::getAge).reduce(0, Integer::sum);
System.out.println(all);
//使用reduce求所有作者中年龄的最大值
Integer max = authors.stream().distinct().map(Author::getAge).reduce(Integer.MIN_VALUE, (result, element) -> result > element ? result : element);
System.out.println(max);
//使用reduce求所有作者中年龄的最小值
Integer min = authors.stream().distinct().map(Author::getAge).reduce(Integer.MAX_VALUE, (result, element) -> result < element ? result : element);
System.out.println(min);
}
Optional
Optional就好像是包装类,可以把我们的具体数据封装Optional对象内部。然后我们去使用Optional中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。
创建对象
1.使用Optional的静态方法ofNullable来把数据封装成一个Optional对象。
Mybatis从3.5版本可以也已经支持Optional了。我们可以直接把dao方法的返回值类型定义成Optional类型,MyBastis会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。
public static Optional<Author> getAuthorOptional(){
ArrayList<Book> list = new ArrayList<>();
list.add(new Book(2L, "书名", "类型", 90, "intro"));
list.add(new Book(1L, "书名", "类型", 90, "intro"));
Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",list);
return Optional.ofNullable(author);
}
2.如果确定一个对象不是空的则可以使用Optional的静态方法of来把数据封装成Optional对象。(如果传入null报空指针异常)
Author author = new Author();
Optional<Author> authorOptional = Optional.of(author);
3.如果一个方法的返回值类型是Optional类型。而如果我们经判断发现某次计算得到的返回值为null,这个时候就需要把null封装成Optional对象返回。这时则可以使用Optional的静态方法empty来进行封装。
Optional.empty()
示例:
/**
* Optional.of() 需要传参 永不为空,否则报异常
* optional的返回值为Null,需要封装为optional对象 Optional.empty()进行封装
*/
public static Optional<Author> getAuthorOptional2(){
Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
return author == null?Optional.empty():Optional.of(author);
}
}
ifPresent:安全消费值
判断其内封装的数据是否为空,不为空时才会执行具体的消费代码,避免空指针异常
authorOptional.ifPresent(author -> System.out.println(author.getName()));
get:获取值(不推荐)
当Optional内部的数据为空的时候会出现异常
orElseGet:安全获取值
参数设置默认返回值,有值返回没值返回默认值
@Test
public void test2(){
Author author = getAuthorOptional().orElseGet(() -> new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null));
System.out.println(author.getName());
}
orElseThrow:异常处理
不为空获取该数据,为空根据传入的参数来创建异常抛出,Spring异常统一处理
@Test
public void test2(){
try {
Author author1 = getAuthorOptional().orElseThrow((Supplier<Throwable>) () -> new RuntimeException("数据为null"));
System.out.println(author1);
} catch (Throwable e) {
e.printStackTrace();
}
}
filter:过滤
filter()返回类型为Optional
符合条件,留下,进行后续操作
为空或者不符合条件的,筛掉
@Test
public void test3(){
getAuthorOptional().filter(author -> author.getAge() > 88).ifPresent(author -> System.out.println(author.getName()));
}
isPresent:判断(不推荐)
为空返回值为false,如果不为空,返回值为true,推荐ifPresent
@Test
public void test4(){
boolean present = getAuthorOptional().isPresent();
System.out.println(present);
}
map:数据转换
转换得到的数据也还是被Optional包装好的,保证了我们的使用安全
@Test
public void test5(){
getAuthorOptional().map(author -> author.getBooks()).ifPresent(books -> System.out.println(books));
}
函数式接口
只有一个抽象方法的接口我们称之为函数接口。
常见函数式接口
- Consumer 消费接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。 - Function 计算转换接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回 - Predicate 判断接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果 - Supplier 生产型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回
Predicate中常用的默认方法
and相当于&&
//年纪>17且姓名长度>1
@Test
public void test18() {
List<Author> authors = getAuthors();
authors.stream().distinct()
.filter(((Predicate<Author>) author -> author.getAge() > 17)
.and(author -> author.getName().length()>1))
.forEach(author -> System.out.println(author.getName()));
authors.stream().filter(author -> author.getName().length()>1 && author.getAge()>17).forEach(author -> System.out.println(author.getName()));
}
or相当于||
//年纪>17或者长度<2的作家
@Test
public void test17() {
List<Author> authors = getAuthors();
authors.stream().filter(author -> author.getAge()>17 || author.getName().length()<2).forEach(author-> System.out.println(author.getName()));
authors.stream().filter(((Predicate<Author>) author -> author.getAge() < 17)
.or(author -> author.getName().length()<2)).
forEach(author -> System.out.println(author.getName()));
}
- negate相当于!
//作家的年龄不大于17岁
@Test
public void test19() {
List<Author> authors = getAuthors();
authors.stream().filter(((Predicate<Author>) author -> author.getAge() > 17).negate()).forEach(author -> System.out.println(author.getAge()));
authors.stream().filter(author -> !(author.getAge()<17)).forEach(author -> System.out.println(author.getName()));
}
方法引用
基本格式
类名或者对象名::方法名
引用类的静态方法
格式:类名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。
代码:
authorStream.map(author -> author.getAge()).map(age->String.valueOf(age));
优化后结果:
authorStream.map(author -> author.getAge()).map(String::valueOf);
引用类的实例方法
格式:类名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。
代码:
interface UseString{String use(String str,int start,int length);}
public static String subAuthorName(String str, UseString useString){
int start = 0;
int length = 1;
return useString.use(str,start,length);
}
public static void main(String[] args) {
subAuthorName("三更草堂", new UseString() {
@Override
public String use(String str, int start, int length) {
return str.substring(start,length);
}
});
}
//三更草堂 b站up主,欢迎关注,我只是看视频后记个笔记
优化后结果:
public static void main(String[] args) {
subAuthorName("三更草堂", String::substring);
}
引用对象的实例方法
格式:对象名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法。
代码:
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName()).forEach(name->sb.append(name));
优化后结果:
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName()).forEach(sb::append);
构造器引用
格式:类名::new
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。
代码:
List<Author> authors = getAuthors();
authors.stream().map(author -> author.getName())
.map(name->new StringBuilder(name))
.map(sb->sb.append("-三更").toString())
.forEach(str-> System.out.println(str));
优化后结果:
List<Author> authors = getAuthors();
authors.stream().map(author -> author.getName())
.map(StringBuilder::new)
.map(sb->sb.append("-三更").toString())
.forEach(str-> System.out.println(str));
基本数据类型优化
避免自动装箱拆箱时间消耗,Stream提供针对基本数据类型的方法。
mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble
@Test
public void test27() {
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getAge())
.map(age -> age + 10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);
authors.stream()
.mapToInt(author -> author.getAge())
.map(age -> age + 10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);
}
并行流
当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。
1.parallel方法可以把串行流转换成并行流
@Test
public void test28() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = stream.parallel()
//peek()方法:断点调试
.peek(new Consumer<Integer>() {
@Override
public void accept(Integer num) {
System.out.println(num+Thread.currentThread().getName());
}
})
.filter(num -> num > 5)
.reduce((result, ele) -> result + ele)
.get();
System.out.println(sum);
}
2.通过parallelStream直接获取并行流对象
List<Author> authors = getAuthors();
authors.parallelStream()
.map(author -> author.getAge())
.map(age -> age + 10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);