1.概述
1.1 为什么学
- 看懂公司代码
- 大数量下处理集合效率高
- 代码可读性高
- 消灭嵌套地狱
1.2 函数式编程思想
1.2.1 概念
面向对象思想需要关注用什么对象完成什么事情,而函数式编程思想就类似于我们数学中的函数。它主要关注的是对数据进行了什么操作。
1.2.2 优点
- 代码简介,开发快速
- 接近自然语言,易于理解
- 易于“并发编程”
2.Lambda 表达式
2.1 概述
Lambda是JDK8中一个语法糖。对一些匿名内部类的写法进行简化,让我们更加关注对数据的处理
2.2 核心原则
可推导可省略
2.3 基本格式
(参数列表)->{代码}
例一
//匿名内部类
new Thread(new Runnable() {
public void run() {
System.out.println("Hello World!");
}
}).start();
//Lambda表达式
new Thread(()-> System.out.println("Hello World!"));
例二
//匿名内部类
int res = calculateNum(new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return left + right;
}
});
private static int calculateNum(IntBinaryOperator operator){
int a = 10;
int b = 20;
return operator.applyAsInt(a,b);
}
//Lambda
int res = calculateNum(((left, right) -> left+right));
private static int calculateNum(IntBinaryOperator operator){
int a = 10;
int b = 20;
return operator.applyAsInt(a,b);
}
2.4 省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号、return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号可以省略
3.Stream流
3.1 概述
java8的stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流失的操作。可以更方便的让我们对集合或数组操作。
3.2 准备
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode //去重的使用,自动重写equals方法
public class Author {
private Long id;
private String name;
private Integer age;
//简介
private String intro;
private List<Book> books;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode //去重的使用,自动重写equals方法
public class Book {
private Long id;
private String name;
private String category;
private Integer score;
private String intro;
}
public List<Author> authors(){
Author a1 = new Author(1L,"a1",18,"a1",null);
Author a2 = new Author(2L,"a2",19,"a2",null);
Author a3 = new Author(3L,"a3",20,"a3",null);
Author a4 = new Author(4L,"a4",21,"a4",null);
List<Book> books1 = new ArrayList<>();
List<Book> books2 = new ArrayList<>();
List<Book> books3 = new ArrayList<>();
List<Book> books4 = new ArrayList<>();
Book b1 = new Book(1L,"b1","哲学,爱情",1,null);
Book b2 = new Book(2L,"b2","个人成长,爱情",2,null);
Book b3 = new Book(3L,"b3","爱情",3,null);
Book b4 = new Book(4L,"b4","个人传记",4,null);
Book b5 = new Book(5L,"b5","爱情,个人传记",1,null);
Book b6 = new Book(6L,"b6","哲学",1,null);
books1.add(b1);
books1.add(b2);
books1.add(b3);
books2.add(b3);
books2.add(b4);
books2.add(b5);
books3.add(b6);
books3.add(b3);
books3.add(b1);
books4.add(b2);
books4.add(b5);
books4.add(b6);
List<Author> authors = new ArrayList<>();
authors.add(a1);
authors.add(a2);
authors.add(a3);
authors.add(a4);
return authors;
}
3.3 快速入门
3.3.1 需求
打印年龄小于20的作家名字,并去重
3.3.2 实现
List<Author> authors = authors();
authors.stream()//集合转换成流
.distinct()//去重
.filter((author)->author.getAge()<20)
.forEach((author)-> System.out.println(author.getName()));
3.4 常用操作
3.4.1 创建流
单列集合 : 集合对象.stream()
数组 : Arrays.stream(数组)或者使用Stream.of(数组)来创建
双列集合 : 转换成单列集合后再创建entrySet
3.4.2 中间操作
-
filter
可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中
-
map
可以把对流中的元素进行计算或转换
List<Author> authors = authors(); authors.stream() .map(author -> author.getAge() + 10) .forEach(age -> System.out.println(age));
List<Author> authors = authors(); authors.stream() .map(author -> author.getName())//将流中的数据用author.name替换 .forEach(name -> System.out.println(name));
-
distinct
可以去除流中的重复元素,依赖于equals方法,所以需要重写equals方法
-
sorted
可以对流中的元素进行排序,如果使用默认sorted,则实现类需要实现Comparable接口并实现方法
例如
对流中的元素按照年龄进行降序排序,并且要求不能有重复元素
List<Author> authors = authors();
authors.stream()
.distinct()
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
.forEach(author -> System.out.println(author));
-
limit
可以设置流的最大长度,超出的部分将被抛弃
例如
对流中的元素按照年龄降序,无重复值,然后打印其中年龄最大的两个作家的名字
List<Author> authors = authors();
authors.stream()
.distinct()
.sorted((o1,o2)->o2.getAge()-o1.getAge())
.limit(2)
.forEach(author -> System.out.println(author));
-
skip
跳过流中的前n个元素,返回剩下的元素
例如
打印除了年龄最大的作家外的其它作家,无重复,并降序
List<Author> authors = authors();
authors.stream()
.distinct()
.sorted((o1,o2)->o2.getAge()-o1.getAge())
.skip(1)//跳过几个
.forEach(author -> System.out.println(author));
-
flatMap
map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流的对象
例一
打印所有书籍的名字。去除
List<Author> authors = authors();
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.forEach(new Consumer<Book>() {
@Override
public void accept(Book book) {
System.out.println(book.getName());
}
});
List<Author> authors = authors();
authors.stream()
.flatMap((Function<Author, Stream<Book>>) author -> author.getBooks().stream())
.distinct()
.forEach(book -> System.out.println(book.getName()));
例二
打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式 : 哲学,爱情
//打印所有数据的分类,要求对分类进行去重。不能出现这种格式 : 哲学 爱情
List<Author> authors = authors();
authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.flatMap(book -> Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(category-> System.out.println(category));
3.4.3 终结操作
-
forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体操作
例子
输出所有作家的名字
List<Author> authors = authors();
authors.stream()
.map(author -> author.getName())
.distinct()
.forEach(name-> System.out.println(name));
-
count
可以用来获取当前流中元素的个数
例子
打印这些作家的所出书籍的数目,注意删除重复元素
List<Author> authors = authors();
long count = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.count();
System.out.println(count);
-
max&min
可以用来获取流中的最值
例子
分别获取这些作家的所出书籍的最高分和最低分
List<Author> authors = authors();
Optional<Integer> max = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getScore())
.max((s1, s2) -> s1 - s2);
System.out.println(max.get());
Optional<Integer> min = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getScore())
.min((s1, s2) -> s1 - s2);
System.out.println(min.get());
-
collect
当前流转换成集合
例子
获取一个存放所有作者名字的list集合
List<String> collect = authors().stream()
.map(author -> author.getName())
.collect(Collectors.toList());
System.out.println(collect);
获取一个所有书名的set集合
Set<String> collect = authors().stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getName())
.distinct()
.collect(Collectors.toSet());
System.out.println(collect);
获取一个map集合,map的key为作者名,value为List
Map<String, List<Book>> collect = authors().stream()
.distinct()
.collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
System.out.println(collect);
查找与匹配
-
anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型
例子
判断是否有年龄在18以上的作家
boolean b = authors().stream()
.anyMatch(author -> author.getAge() > 18);
System.out.println(b);
-
allMatch
可以用来判断是否都符合匹配条件,结果为boolean类型。如果都符合结果为true,否则结果为false
例子
判断是否所有的作家都是成年人
boolean b = authors().stream()
.allMatch(author -> author.getAge() >= 18);
System.out.println(b);
-
noneMatch
可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为true,否则结果为false
例子
判断作家是否都没有超过100岁的
boolean b = authors().stream()
.noneMatch(author -> author.getAge() < 100);
System.out.println(b);
-
findAny
获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素
例子
获取任意一个大于18的作家,如果存在就输出名字
Optional<Author> any = authors().stream()
.filter(author -> author.getAge() > 18)
.findAny();
any.ifPresent(author -> System.out.println(author.getName()));
-
finFirst
获取流中第一个元素
例子
获取一个年龄最小的作家,并输出他的姓名
Optional<Author> first = authors().stream()
.sorted((s1, s2) -> s1.getAge() - s2.getAge())
.findFirst();
first.ifPresent(author -> System.out.println(author.getName()));
-
reduce归并
对流中的数据按照你制定的计算方式计算出一个结果(缩减操作)
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和在初始化值得基础上进行计算,计算结果再和后面的元素计算
T result = identity; for(T element : this stream) result = accumlator.apply(result,element) return result;
其中idenetity就是我们可以通过方法参数传入的初始值,accumlator的apply具体进行什么计算也是我们通过方法参数确定的
例子
使用reduce求所有作者年龄的和
Integer reduce = authors().stream()
.distinct()
.map(author -> author.getAge())
.reduce(0, (result, element) -> result + element);
System.out.println(reduce);
使用reduce求所有作者年龄的最大值
Integer max = authors().stream()
.map(author -> author.getAge())
.distinct()
.reduce(Integer.MIN_VALUE, (result, element) -> result = Math.max(result, element));
System.out.println(max);
使用reduce求所有作者中年龄的最小值
Integer min = authors().stream()
.map(author -> author.getAge())
.distinct()
.reduce(Integer.MAX_VALUE, (result, element) -> result = Math.min(result, element));
System.out.println(min);
reduce一个参数的重载形式内部的计算
boolean foundAny = false;
T result = null;
//将第一个元素作为初始化值
for(T element : this stream){
if(!foundAny){
foundAny = true;
result = element;
}else
result = accumlator.apply(result,element);
}
return foundary?Optional.of(result) : Optional.empty();
如果用一个参数的重载方法求最小值代码如下
Optional<Integer> min = authors().stream()
.map(author -> author.getAge())
.distinct()
.reduce((result, element) -> result = Math.min(result, element));
min.ifPresent(age -> System.out.println(age));
3.5 注意事项
- 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
- 流是一次性的
- 不会影响原有数据
4.Optional
4.1 概述
优雅的避免空指针
4.2 使用
4.2.1 创建对象
无论传入的参数是否为null都不会出现异常
static Author getAuthor(){
return new Author(1L,"灰二",18,"HuiEr",null);
// return null;
}
Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(author);
authorOptional.ifPresent(data -> System.out.println(data.getName()));
4.2.2 完全消费值
Optional<Author> authorOptional = Optional.ofNullable(author);
authorOptional.ifPresent(data -> System.out.println(data.getName()));
4.2.3 获取值
如果我们想获取Optaional中封装到的值可以使用get方法,但是不推荐,因为当封装的值为null时会有异常
4.2.4 安全获取值
-
orElseGet : 获取数据并且设置数据为空的默认值,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建对象作为默认值返回
Optional<Author> authorOptional = Optional.ofNullable(null); Author author = authorOptional.orElseGet(() -> new Author()); System.out.println(author);
-
orElseThrow : 获取数据时,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出
Optional<Author> authorOptional = Optional.ofNullable(null); try { Author author = authorOptional.orElseThrow(() -> new RuntimeException("空指针")); System.out.println(author); }catch (Exception e){ e.printStackTrace(); }
4.2.5 过滤
Author a1 = new Author();
a1.setAge(18);
Optional<Author> a11 = Optional.ofNullable(a1);
a11.filter(author -> author.getAge() > 17)
.ifPresent(author -> System.out.println(author.getAge()));
4.2.6 判断
isPresent : 判断内部封装的数据是否为空,不为空则返回true,为空则返回false
更加推荐使用ifPresent
4.2.7 数据转换
map 和 flatMap
4.3 总结
一般使用Optional的创建、获取操作
5.函数式接口
5.1 概述
只有一个抽象方法的接口我们称之为函数接口
JDK的函数式接口都加上了**@FunctionalInterface**注解进行标识。但是无论是否加上该注解只要接口只有一个抽象方法,都是函数式接口
5.2 常用函数式接口
-
Consumer消费接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费
void accept(T t);
-
Function计算转换接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以再方法中对传入的参数计算或转移,把结果返回
R apply(T t);
-
Predicate判断接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回 结果
boolean test(T t);
-
Supplier生产型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回
T get();
5.3 常用默认方法
Predicate
- and &&
- or ||
- negate 取反
6.方法引用
我们在使用lambda时,只有一行代码,且只调用一个方法,则可以更进一步简化代码
6.1 推荐用法
6.2 基本格式
类名或者对象名::方法名
6.3 语法详解
6.3.1 引用静态方法
格式
类名::方法名
前提
如果我们重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以使用引用类的静态方法
例如:
如下代码就可以简化
class StaticMethod {
static void out() {
System.out.println("静态方法的引用");
}
}
new Thread(()->StaticMethod.out()).start();
new Thread(StaticMethod::out).start();
6.3.2 引用对象的实例方法
格式
对象名::方法名
前提
如果我们重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以使用引用类的成员方法
例如:
如下代码就可以简化
class MemberMethod{
void out(){
System.out.println("成员方法的引用");
}
}
MemberMethod memberMethod = new MemberMethod();
new Thread(memberMethod::out).start();
6.3.3 引用类的实例方法
6.3.4 构造器引用
如果方法体中的一行代码是构造器的话就可以使用构造器引用
格式
类名::new
7.高级用法
基本数据类型优化
- mapToInt
- mapToLong
- mapToDouble
- flatMapToInt
- flatMapToDouble
…
并行流
stream.parallel()