十一、函数式编程
为什么要学?
- 能够看懂公司的代码
- 大数据量下处理集合效率高
- 代码可读性高
- 消灭嵌套地狱
11.1.函数式编程思想
- 概念:面向对象思想需要关注用什么对象完成什么事情。而函数式编程思想就类似于我们数学中的函数。它主要关注的是对数据进行了什么操作。
- 优点
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 易于“并发编程”
11.2.Lambda表达式
- 概述:Lambda是JDK8中一个语法糖,她可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个重要体现。让我们不用关注是什么对象。而是更关注我们对数据进行了什么操作。加强理解:也就是说我们更关注的是参数列表和返回值
- 核心原则:可推导,可省略
- 基本格式:(参数列表)->{代码}
使用前提:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。如:多线程的Runnable接口只有一个抽象方法。
public interface Runnable{
public abstract void run();
}
idea快捷键:alt+enter快捷生成lambda表达式
idea 选中要转换的匿名内部类按住 alt+enter 选择 replace with lambda 快捷生成lambda表达式
案例1:
//正常编写
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello1");
}
}).start();
//lambda优化
new Thread(()->{
System.out.println("hello2");
}).start();
案例2:
public class test {
public static void main(String[] args) {
//案例2
//优化前
int i = test.calculateNum(new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return left + right;
}
});
System.out.println(i);
//lambda优化后
int i2 = calculateNum((int left,int right)->{return left+right;});
System.out.println(i2);
}
public static int calculateNum(IntBinaryOperator operator){
int a = 10;
int b = 20;
return operator.applyAsInt(a,b);
}
}
案例3:
public class test {
public static void main(String[] args) {
//案例3
printNum(new IntPredicate() {
@Override
public boolean test(int value) {
return value%2 == 0;
}
});
//lambda优化
printNum(value->{return value%2 ==0;});
}
public static void printNum(IntPredicate predicate){
int []arr = {1,2,3,4,5,6,7,8,9,10};
for (int i : arr) {
if (predicate.test(i)){
System.out.println(i);
}
}
}
}
案例4:
Function()接口,先使用匿名内部类写法调用。
public class lambdaDemo01 {
public static void main(String[] args) {
//这里调用需要指定Function泛型参数
Integer result = typeConver(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.valueOf(s);
}
});
System.out.println(result);
//转换lambda表达式,我们关注参数,方法体
}
public static <R> R typeConver(Function<String,R> function){
//这里我们查看Function接口源码,该接口接收两个泛型F和R,
String str = "1235";
//apply抽象方法源码查看传入泛型T返回泛型R
R result = function.apply(str);
return result;
}
}
lambda优化1:
//转换lambda表达式,我们关注参数列表,方法体
Integer result = typeConver((String s) -> {
return Integer.valueOf(s);
});
System.out.println(result);
lambda优化2:
String s1 = typeConver((String s) -> {
return s + "B0";
});
System.out.println(s);
案例5
IntConsumer是一个接口,先使用匿名内部类的写法调用
public static void foreachArr(IntConsumer consumer){
int[] arr = {1,2,3,4,5,6,7,8,9,10};
for (int i : arr) {
consumer.accept(i);
}
}
优化
foreachArr((int value)->{
System.out.println(value);
});
lambda表达式简化(注意总结)
- 参数类型可以省略
- 方法只有一个参数时小括号可以省略
- 方法体只有一句代码时,大括号return和唯一语句代码的分号可以省略
public class TestLambda02 {
public static void main(String[] args) {
ILove love = null;
//1.lambda表达式简化,因为它只有一个抽象方法,参数名可以省略,会被自动识别到
love = (int a)->{
System.out.println("我爱你"+a+"年");
};
//2.简化1:省略参数类型
love = (a)->{
System.out.println("我爱你"+a+"年");
};
//3.简化2:简化参数列表的括号
love = a->{
System.out.println("我爱你"+a+"年");
};
//4.简化3:去掉花括号,分号(这里是因为只有一行代码,如果有多行代码不能省略花括号)
love = a->System.out.println("我爱你"+a+"年")
//总结:
// lambda表达式只有一行代码的情况下才能简化花括号
// 前提是接口为函数式接口(接口中只有一个方法)
// 多个函数也可以取消参数类型,要去掉就都去掉,多个参数,参数处必须加上括号
love.love(1000);
}
}
interface ILove{
void love(int a);
}
记不住规则可以省略不急,在使用IDEA编译器时,选中“new 接口名”中的“接口名” alt+enter 选中replace with lambda
11.3.Stream流
Java8的stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作。可以让我们更加方便对集合或数组操作。
注意:此处Stream流是对集合或数组操作,需要与之前学习的IO流区分
11.3.1初识stream流
为方便stream流的讲解,我们这里直接使用一个案例来说明。
Book类
public class Book {
private Long id;
private String name;
//分类
private String category;
private Integer score;
//简介
private String intro;
//方便界面简洁文章可读性,后续需要读者自行重新写getter,setter,有参构造,无参构造,toString方法
}
Author类
public class Author {
private Long id;
private String name;
private Integer age;
//简介
private String intro;
//作品
private List<Book> books;
//同上...并且还需要追加重写hashCode方法和equals方法,方便后续查看多个类的区别
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Author author = (Author) o;
return Objects.equals(id, author.id) && Objects.equals(name, author.name) && Objects.equals(age, author.age) && Objects.equals(intro, author.intro) && Objects.equals(books, author.books);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age, intro, books);
}
//...
}
编写具有获取所有作者信息的测试类
public class StreamDemo {
public static void main(String[] args) {
List<Author> authors = getAuthors();
System.out.println(authors);//输出所有用户信息
}
private static List<Author> getAuthors() {
Author author = new Author(1L,"b0", 33, "hello", null);
Author author2 = new Author(2L,"c0", 15, "hello", null);
Author author3 = new Author(3L,"d0", 32, "hello", null);
Author author4 = new Author(4L,"e0", 16, "hello", null);
//书记列表
List<Book> books1 = new ArrayList<>();
List<Book> books2 = new ArrayList<>();
List<Book> books3 = new ArrayList<>();
books1.add(new Book(1L,"hello o","哲学,法学",88,"描述1"));
books1.add(new Book(2L,"hello o","哲学,计算机学",89,"描述2"));
books2.add(new Book(3L,"hello o","哲学,化学",99,"描述3"));
books2.add(new Book(4L,"hello o","哲学,心理学",83,"描述4"));
books2.add(new Book(5L,"hello o","哲学",82,"描述5"));
books3.add(new Book(6L,"hello o","哲学",72,"描述6"));
books3.add(new Book(7L,"hello o","哲学",89,"描述7"));
books3.add(new Book(8L,"hello o","哲学",65,"描述8"));
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流对集合数据进行筛选过滤
需求:我们调用getAuthors方法获取到作家的集合后,现在需要打印所有 年龄小于18的作家的名字,并且要注意 去重。
修改main方法
public static void main(String[] args) {
List<Author> authors = getAuthors();
authors.stream()//1.将集合数据转换为流
.distinct()//2.使用stram流对数据去重
.filter(new Predicate<Author>() {//使用stream流的过滤器filter,内部传参匿名内部类,并在内部类中test方法体进行条件编写
@Override
public boolean test(Author author) {
return author.getAge()<18;
}
}).forEach(new Consumer<Author>() {//遍历输出所有作者
@Override
public void accept(Author author) {
System.out.println(author.getName());
}
});
}
使用lambda优化
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.filter(author -> author.getAge()<18)
.forEach(author -> System.out.println(author.getName()));
idea debug图形化查看stream流操作流程
11.3.2 stream流常用操作
1.创建流
单列集合:集合对象.stream
List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();
双列集合(本身不能直接转为流):转换成单列集合后创建流
Map<String, Integer> map = new HashMap<>();
map.put("b01",19);
map.put("b02",17);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
数组:使用 Arrays.stream(数组) 或者使用 Stream.of(数组) 来创建流
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream1 = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
2.中间操作
filter:过滤数据
可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。
案例:打印所有名字长度大于1的作家的名字
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.filter(author -> author.getName().length()>1)
.forEach(author -> System.out.println(author.getName()));
map:数据计算或转换
可以把流中的元素进行计算或转换
转换案例:打印所有作家的姓名
未简化前
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
})
.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
简化后
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.map(author -> author.getName())
.forEach(s -> System.out.println(s));
计算案例:将作家的年龄都增加10
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getAge()+10)
.forEach(age -> System.out.println(age));
distinct:去重数据
可以取出流中的重复元素
原理剖析:distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重写equals方法,(底层实现:this == obj,比较的是地址,地址相同才相同,如果我们是自定义的类比较,那么需要重写equals方法,因为类中的数据完全相同我们也认为他是相同的,而不是地址相同才为同一个对象)
案例:打印所有作家的姓名,并且要求其中不能有重复元素
前置要求:Author类必须要先重写equals方法
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.forEach(author -> System.out.println(author.getName()));
sorted:排序
可以对流中的元素进行排序。
案例:对流中的数据按照年龄进行降序排序,并且要求不能有重复的元素。
方法1:使用无参的sorted()方法,流中的元素需要实现Comparable接口
1.目标数据类实现Comparable接口,重写compareTo方法(此处目标类为Author)
public class Author implements Comparable<Author>{
//...
@Override
public int compareTo(Author o) {
return this.getAge()-o.getAge();
}
}
2.实现接口后 测试类中使用无参sorted方法
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted()
.forEach(author -> System.out.println(author.getAge()));
方法2:使用有参的sorted方法
目标类不需要实现Comparable接口
匿名内部类实现
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted(new Comparator<Author>() {
@Override
public int compare(Author o1, Author o2) {
return o1.getAge() - o2.getAge();//升序
}
})
.forEach(author -> System.out.println(author.getAge()));
lambda简化
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted((o1,o2)->o1.getAge() - o2.getAge())
.forEach(author -> System.out.println(author.getAge()));
limit:限制
可以设置流的最大长度,超出的部分将被抛弃。(eg:我的数据是100个我可以设置长度为前10个超出的会被遗弃)
案例:对流中的元素按照年龄进行降序排序,并且要不能有重复的元素,然后打印其中年龄最大的两个作家的姓名。
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted((o1, o2) -> o2.getAge()- o1.getAge())//降序排序
.limit(2)//设置最大长度为2,多余的长度会被遗弃
.forEach(author -> System.out.println(author.getName()));
skip:跳过
跳过流中的前n个元素,返回剩下的元素。
案例:打印除了年龄最大的作家外的其他作家,要求不能有重复的元素,并且按照年龄降序排序。
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted((o1, o2) -> o2.getAge()-o1.getAge())
.skip(1)//跳过第一个元素
.forEach(author -> System.out.println(author.getName()));
flatMap:一个元素转换为多个元素存放到流
map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
案例1:打印所有书籍的名字。要求对重复元素进行去重。
List<Author> authors = getAuthors();
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {//这里默认是Stream<?>类型通配符,但是我们清楚此处是要返回一个Book
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.forEach(new Consumer<Book>() {
@Override
public void accept(Book book) {
}
});
简化后
List<Author> authors = getAuthors();
authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.forEach(book -> System.out.println(book.getName()));
案例2:打印现有的所有分类。要求对分类进行去重,不能出现这种格式:哲学,爱情,而是变为两个分类:哲学 爱情
List<Author> authors = getAuthors();
authors.stream()
.flatMap(new Function<Author, Stream<Book>>() {
@Override
public Stream<Book> apply(Author author) {
return author.getBooks().stream();
}
})
.distinct()
.flatMap(new Function<Book, Stream<?>>() {
@Override
public Stream<?> apply(Book book) {
//数组转换为流对象:Arrays.stream()
return Arrays.stream(book.getCategory().split(","));
}
})
.distinct()
.forEach(category-> System.out.println(category));
简化后
List<Author> authors = getAuthors();
authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.flatMap(book -> Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(category-> System.out.println(category));
idea操作补充–翻译插件
File–>Settings–>Plugins 搜索translation翻译插件安装以后apply即可在文档中英语注释处右击点Translate Documentation
3.终结操作
forEach:遍历
需求:输出所有作家的名字
authors.stream()
.map(author -> author.getName())//此处使用map处理数据,只返回所有作者的姓名
.distinct()
.forEach(s-> System.out.println(s));
count:计数
用来获取当前流中元素的个数
需求:打印这些作家的所出书籍的数目,注意删除重复元素
long count = authors.stream()
.distinct()
.flatMap(author -> author.getBooks().stream())//使用flatMap将一个数据转换为多个元素并存为流
.distinct()
.count();
System.out.println(count);
max/min:求最大值和最小值
用来获取流中的最值
需求:分别获取这些作家所出书籍的最高分和最低分并打印
求出最大值
List<Author> authors = getAuthors();
Optional<Integer> max = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.map(book -> book.getScore())
//这里需要我们去告诉JDK我们需要什么规则去比较,怎么样才是最大的?
.max(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});//这里最后返回值为一个Optional对象,后续会使用到
System.out.println(max.get());
简化
List<Author> authors = getAuthors();
Optional<Integer> max = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.map(book -> book.getScore())
.max((o1, o2) -> o1-o2);
System.out.println(max.get());
最小值
Optional<Integer> min = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.map(book -> book.getScore())
.max((o1, o2) -> o1-o2);
System.out.println(max.get());
collect:将流转换为集合
案例:获取一个存放所有作者名字的List集合
List<Author> authors = getAuthors();
List<String> nameList = authors.stream()
.map(author -> author.getName())
.collect(Collectors.toList());//使用Collectors工具类提供的toList方法进行collect参数配置
System.out.println(nameList);
案例:获取一个所有书名的Set集合
List<Author> authors = getAuthors();
Set<Book> nameSet = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getName())
.collect(Collectors.toSet());//使用Collectors工具类提供的toSet方法进行collect参数配置
System.out.println(nameSet);
案例:获取一个Map集合,Map的key为作者名value为List
List<Author> authors = getAuthors();
Map<String, List<Book>> map = authors.stream()
.distinct()
.collect(Collectors.toMap(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
}, new Function<Author, List<Book>>() {
@Override
public List<Book> apply(Author author) {
return author.getBooks();
}
}));
System.out.println(map);
lambda简化
Map<String, List<Book>> map = authors.stream()
.distinct()
.collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
//此处我们使用Collectors工具类的toMap方法,内部两个参数,告诉它key用什么充当,map用什么充当
System.out.println(map);
查找与匹配
anyMatch:任意匹配
案例:是否有年龄在29岁以上的作家
List<Author> authors = getAuthors();
boolean flag = authors.stream()
.anyMatch(new Predicate<Author>() {
@Override
public boolean test(Author author) {
//可以在这个里面做判断在返回
return author.getAge() > 29;
}
});
System.out.println(flag);
与filter区别,filter是中间操作,会将过滤的数据清除,anyMatch只是匹配判断
简化
boolean flag = authors.stream()
.anyMatch(author -> author.getAge() > 29);
System.out.println(flag);
allMatch:是否所有都匹配
用来判断是否都符合匹配条件,结果为boolean类型,如果都符合结果为true,否则结果为false
案例:判断是否所有的作家都是成年人
boolean flag = authors.stream()
.allMatch(author -> author.getAge() > 18);
System.out.println(flag);
noneMatch:是否都不匹配
判断是否都不符合匹配条件,结果为boolean类型,如果都符合结果为true,否则结果为false
例子:判断作家是否都没有超过100岁的
boolean flag = authors.stream()
.noneMatch(author -> author.getAge() >100);//强化理解:noneMatch不符合这个条件,也就是年龄是不是都没有大于100岁,是的话返回true,否则false
System.out.println(flag);
findAny(用的少)
获取流中的任意一个元素。该方法没有办法保证,所以获取的一定是流中的第一个元素
案例:获取任意一个年龄大于18的作家,如果存在就输出他的名字
Optional<Author> optionalAuthor = authors.stream()
.filter(author -> author.getAge()>18)
.findAny();
//ifPresent判断如果有数据,输出第一个数据
optionalAuthor.ifPresent(author-> System.out.println(author.getName()));
findFirst
获取流中的第一个元素。
案例:获取一个年龄最小的作家,并输出他的名字
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中的元素组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和在初始值进行计算,计算结果再和后面的元素计算。
内部计算方式如下:
T result = null;
for (T element : this stream)
result = accumulator.apply(result, element);//通过这个方法我们自己指定运算规则,内部是+,-,*,/
return result;
其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的。
reduce两个参数的重载方法
案例1:使用reduce求所有作者年龄的和(map+reduce模式,map数据转换后使用reduce计算)
List<Author> authors = getAuthors();
Integer sum = authors.stream()
.distinct()
.map(author -> author.getAge())
.reduce(0, new BinaryOperator<Integer>() {//对比上述内部计算方式理解
@Override
public Integer apply(Integer result, Integer element) {
return result + element;
}
});
System.out.println(sum);
简化
Integer sum = authors.stream()
.distinct()
.map(author -> author.getAge())
.reduce(0, (result,element)->result+element);
System.out.println(sum);
案例2:使用reduce求所有作者中年龄的最大值(max和min方法内部封装了reduce)
List<Author> authors = getAuthors();
Integer min = authors.stream()
.distinct()
.map(author -> author.getAge())
.reduce(0, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result < element ? element : result;
}
});
System.out.println(min);
简化
Integer max = authors.stream()
.distinct()
.map(author -> author.getAge())
//求最大值,此时我们的初始化值要填入Integer的最小值
.reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);
System.out.println(max);
案例3:使用reduce求所有作者中年龄的最小值
List<Author> authors = getAuthors();
Integer min = authors.stream()
.distinct()
.map(author -> author.getAge())
.reduce(Integer.MAX_VALUE, (result, element) -> result > element ? element : result);
System.out.println(min);
reduce一个参数的重载方法
eg:如何快速学习一个方法,查看该方法底层文档注释
分析一个参数底层实现:
boolean foundAny = false;
T result = null;
for (T element : this stream) {
if (!foundAny) {//此处分析出 foundAny用于result的初始化
foundAny = true;
result = element;
}
else
result = accumulator.apply(result, element);//此处根据运算规则
}
return foundAny ? Optional.of(result) : Optional.empty();
需求:使用reduce求出所有作者中年龄的最小值(reduce方法内部只传一个参数)
List<Author> authors = getAuthors();
Optional<Integer> minOptional = authors.stream()
.map(author -> author.getAge())
.reduce((result,element) ->result > element ? element : result);
//单个参数的BinaryOperator方法返回一个Optional对象,防止空指针问题
//若忘记ifPresent内部参数怎么写了,可以实例一个内部类再改写方法
minOptional.ifPresent(age-> System.out.println(age));
注意事项
- 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
- 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
- 不会影响原数据(我们在流中可以对数据做很多处理。但是正常情况下是不会影响到原来集合中的元素的)
11.4 Optional
11.4.1概述
我们在编写代码的时候出现最多的就是空指针异常。所以很多情况下,我们需要做各种非空的判断。
案例:
public class OptionalDemo {
public static void main(String[] args) {
Author author = getAuthor();
//System.out.println(author.getName());//出现空指针异常
//解决方案1,添加判断。缺点:若每个对象都加一个非空判断,后面可能会遗忘,判断过多代码很臃肿,可读性很差
if (author!=null){
System.out.println(author.getName());
}
//解决方案2:Java8引入了Optional对象,养成使用Optional的习惯后可以写出更加优雅的代码来避免空指针异常
//并且很多函数式编程相关API也都用到了Optional,如果不会使用Optional也会对函数式编程的学习造成影响
}
public static Author getAuthor(){
Author author = new Author(1L, "b0", 33, "hello b0", null);
return null;
}
}
11.4.2使用
1.创建对象(频繁):推荐ofNullable
Optional类似包装类我们可以把我们的具体数据封装到Optional对象内部。然后我们去使用Optional中封装好的方法操作封装进去的数据就会风场优雅,避免空执政异常。
我们一般使用Optional的静态方法ofNullable来把数据封装成一个Optional对象。无论传入的参数是否为null都不会出现问题。(推荐)
格式:
public class OptionalDemo {
public static void main(String[] args) {
Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(author);//ofNullable可以传null
authorOptional.ifPresent(author1 -> System.out.println(author));
}
public static Author getAuthor(){
Author author = new Author(1L, "b0", 33, "hello b0", null);
//return null;//此处返回null前面使用也不会触发空指针异常
return author;
}
}
修改案例,我们一般是方法调用后返回一个Optional对象,优化上述方法
public static Optional<Author> getAuthorOptional(){
Author author = new Author(1L, "b0", 33, "hello b0", null);
return Optional.ofNullable(author);
}
强化理解:在实际开发中我们很多数据是从数据库获取的。mybatis3.5已经支持Optional了。我们可以直接把dao方法的返回值定义成Optional类型,mybatis会自己把数据封装成Optional对象返回。
情形1:若不确定一个对象是否为空,可以使用Optional的静态方法of来把数据封装成Optional对象。(不推荐)
Optional<Author> authorOptional = Optional.of(author);//of传参不能传null
情形2:若一个方法返回值类型是Optional类型。若能够发现某次返回结果为null,我们需要把null封装成Optional返回。这时可以使用Optional的静态方法empty进行封装。(不推荐)
public static Optional<Author> getAuthorOptional(){
Author author = new Author(1L, "b0", 33, "hello b0", null);
return author==null?Optional.empty():Optional.ofNullable(author);
}
2.安全消费者(频繁):ifPresent方法
我们获取到Optional对象后肯定是需要对其中数据进行使用,但是我们不知道内部数据是否为空。Optional提供了一个ifPresent静态方法实现判空,不为空才执行消费代码。保护其安全性。
public class OptionalDemo {
public static void main(String[] args) {
Author author = getAuthor();
Optional<Author> authorOptional = getAuthorOptional();
authorOptional.ifPresent(author1 -> System.out.println(author.getName()));
}
public static Optional<Author> getAuthorOptional(){
Author author = new Author(1L, "b0", 33, "hello b0", null);
return author==null?Optional.empty():Optional.ofNullable(author);
}
}
3.获取值:get方法(不推荐)
当我们想要获取自己进行处理可以使用get方法获取,但是不推荐。因为Optional内部的数据为空的时候会抛出NoSuchElementException异常
public class OptionalDemo {
public static void main(String[] args) {
Author author = getAuthor();
Optional<Author> authorOptional = getAuthorOptional();
Author author1 = authorOptional.get();//因为Optinal内部数据为空此处抛出异常
}
public static Optional<Author> getAuthorOptional(){
Author author = new Author(1L, "b0", 33, "hello b0", null);
return Optional.ofNullable(null);//此处设置返回null
}
4.安全获取值:orElseGet
若我们期望安全获取值,不推荐使用get,推荐使用Optional提供的orElseGet方法和orElseThrow
- orElseGet
获取数据并且设置数据为空时的默认值。即:为空时传入设置的默认值。
public class OptionalDemo {
public static void main(String[] args) {
Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional.orElseGet(()->new Author());//在前面获取的Optional数据为空时,此处实例的Author对象就会作为默认值,若不为空则不会使用此处的实例
}
public static Author getAuthor(){
Author author = new Author(1L, "b0", 33, "hello b0", null);
return author;
}
- orElseThrow
获取数据,若数据不为空就能获取到该数据。若为空则根据你传入的参数来创建异常抛出
public class OptionalDemo {
public static void main(String[] args) {
Optional<Author> authorOptional = getAuthorOptional();
try {
Author author1 = authorOptional.orElseThrow(new Supplier<Throwable>() {
@Override
public Throwable get() {//此处方便理解,未转未lambda表达式
return new RuntimeException("数据为null");
}
});
}catch (Throwable throwable){
throwable.printStackTrace();
}
}
public static Optional<Author> getAuthorOptional(){
Author author = new Author(1L, "b0", 33, "hello b0", null);
return Optional.ofNullable(null);//若此处返回值为null将会抛出,上方定义的“数据为null”异常
}
5.过滤
我们可以使用filter方法对数据进行过滤。若原本是有数据,但是不符合条件,也会变成一个无数据的Optional
public static void main(String[] args) {
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {//过滤掉年龄大于100的作者,若没有数据返回一个无数据Optional,后续还可以使用ifPresent安全消费
return author.getAge()>100;
}
}).ifPresent(new Consumer<Author>() {
@Override
public void accept(Author author) {//若数据不为空输出名字
System.out.println(author.getName());
}
});
}
public static Author getAuthor(){
Author author = new Author(1L, "b0", 33, "hello b0", null);
return author;
}
lambda优化
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional
.filter(author1 -> author1.getAge()>100)
.ifPresent(author1 -> System.out.println(author1));
6.判断
我们可以使用isPresent方法进行是否存在数据的判断。如果为空返回false,不为空返回true。但是这种方式不能体现Optional的好处,推荐ifPresent方法
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
if (authorOptional.isPresent()){
System.out.println(authorOptional.get().getName());
}
7.数据转换
Optional提供了map让我们可以对数据进行转换,并且转换的数据是被Optional包装好的,保证了我们的使用安全。
public static void main(String[] args) {
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
Optional<List<Book>> books = authorOptional.map(new Function<Author, List<Book>>() {
@Override
public List<Book> apply(Author author) {
return author.getBooks();
}
});
books.ifPresent(new Consumer<List<Book>>() {
@Override
public void accept(List<Book> books) {
books.forEach(new Consumer<Book>() {
@Override
public void accept(Book book) {
System.out.println(book.getName());
}
});
}
});
}
public static Author getAuthor(){
Author author = new Author(1L, "b0", 133, "hello b0", null);
return author;
}
lambda优化
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
//使用map转换数据类型,将数据封装后返回为Optional
Optional<List<Book>> books = authorOptional.map(author1 -> author1.getBooks());
books.ifPresent(new Consumer<List<Book>>() {
@Override
public void accept(List<Book> books) {
books.forEach(book -> System.out.println(book.getName()));
}
});
11.4.3 Optional总结
日常开发中主要涉及到的内容是将数据创建为Optional对象,和安全消费的问题,使用Optional我们不用担心空指针异常,或者漏掉if else导致空指针异常。
11.5函数式接口
前置说明:
下文11.5.3中用到的getAuthors()方法是获取到一个Author类列表,具体实现在11.3章节。
11.5.1 概述
只有一个抽象方法的接口我们称之为函数接口。
JDK的函数式接口都加上了@FunctionalInterface 注解进行标识,但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
案例:查看Consumer接口源码
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
idea使用技巧:快速定位接口/类所在包的位置
11.5.2 常见函数式接口
接口类型主要由接口内部抽象方法作用来决定的,我们使用函数式接口时主要关注的是参数列表和返回值
Consumer消费接口
根据其中的抽象方法的参数列表和返回值类型,我们可以在方法中对传入的参数进行消费
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);//传入参数以后我们进行消费这个接口,比如输出...
}
Function计算转换接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
* @param t the function argument
* @return the function result
*/
R apply(T t);//我们可以在该方法中对获取的数据T进行转换为R,并返回R类型的数据
}
Predicate判断接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);//对T进行判断然后返回判断结果true or false
}
Supplier生产型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建的对象返回
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
* @return a result
*/
T get();
}
11.5.3 常用的默认方法
and
我们在使用Predicate判断接口时,可能需要进行判断条件拼接,而and方法相当于使用&&来拼接两个判断条件。
例如:
需求:输入作家中 年龄大于17 并且 姓名长度大于1 的作家(此处为了演示and将两个条件分开)
注意:此处使用的数据是11.3章节操作stream流时创建的返回作者集合的方法
List<Author> authors1 = getAuthors();
authors1.stream()
.filter(((Predicate<Author>) author -> author.getAge() > 17)
.and(author -> author.getName().length()>1))
.forEach(author -> System.out.println(author.getName()+"::"+author.getAge()));
强化理解:正常使用场景
public static void printNum2(IntPredicate predicate1,IntPredicate predicate2){
int[] arr = {1,2,3,4,5,6,7,8,9,10};
for (int i : arr) {
//使用IntPredicate对象判断
if (predicate1.and(predicate2).test(i)){//此处使用and代表,传入的两个条件判断都要符合才执行test方法中的条件成功才执行下方输出
System.out.println(i);
}
}
}
//测试方法中编写
printNum2(value -> value % 2 == 0, value -> value>4);//输出6 8 10
//此处使用了lambda简化,但是实质上传入的两个参数是两个IntPredicate对象的匿名内部类
or
需求:打印作家中年龄大于17 或者 姓名的长度小于2的作家
List<Author> authors1 = getAuthors();
authors1.stream()
.filter(((Predicate<Author>) author -> author.getAge() > 17)
.or(author -> author.getName().length()<2))
.forEach(author -> System.out.println(author.getName()));
negate
取反操作
需求:打印作家中年龄不大于17的作家
List<Author> authors1 = getAuthors();
authors1.stream()
.filter(((Predicate<Author>) author -> author.getAge() > 17).negate())
.forEach(author -> System.out.println(author.getAge()));
11.6方法引用
前置说明:
下文11.6.3中用到的getAuthors()方法是获取到一个Author类列表,具体实现在11.3章节
我们在使用lambda时,如果方法体中只有一个方法调用的话(包括构造方法),我们可以用方法引用进一步简化代码。
11.6.1 推荐用法
我们在使用lambda时不需要考虑什么时候用方法引用,用那种方法引用,方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。
使用多了慢慢会习惯直接写出方法引用(不能死磕去强制记忆,而是多用后习惯,见到能知道原理就行,不能本末倒置)
11.6.2 基本格式
格式:类名或者对象名::方法名
关注点:只关注“那一行”代码中的,类名/对象名 以及 调用的其中的静态方法或普通方法
11.6.3 语法详情(了解)
1.引用静态方法(类名::方法名)
其实就是引用类的静态方法
格式:
类名::方法名
使用前提:
如果我们在重写方法的时候,若方法体中只有一行代码并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。
案例:如下代码使用 方法引用 简化
List<Author> authors1 = getAuthors();//获取到一个Author类列表,方法的具体实现在11.3章节
Stream<Author> authorStream = authors1.stream();
authorStream.map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge();//方法体只有一行代码✔这行代码调用了某个类的静态方法❌把抽象方法所有参数都按照顺序传入静态方法❌ 不符合引用静态方法
}
})
.map(new Function<Integer, String>() {
@Override
public String apply(Integer age) {
return String.valueOf(age);//方法体只有一行代码✔这行代码调用了某个类的静态方法✔把重写的抽象方法所有参数按照顺序传入静态方法✔ 类名::静态方法名
}
});
方法引用-静态方法优化:
authorStream.map(author -> author.getAge())//lambda优化
.map(String::valueOf);//方法引用——优化后代码
2.引用对象的实例方法(对象名::方法名)
格式
对象名::方法名
使用前提
如果我们在重写方法的时候,方法体只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们可以引用对象的实例方法。
例如:
List<Author> authors1 = getAuthors();
Stream<Author> authorStream = authors1.stream();
StringBuilder sb = new StringBuilder();
authorStream
.map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();//方法体只有一行代码✔这行代码调用了某个对象的成员方法✔将抽象方法所有参数都按照顺序传入这个成员方法❌
}
})
.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
sb.append(s);//方法体只有一行代码✔这行代码调用了某个对象的成员方法✔将抽象方法所有参数都按照顺序传入这个成员方法✔ 对象成员::方法名
}
});
关键代码优化后
authorStream
.map(author -> author.getName())//
.forEach(sb::append);
3. 引用类实例方法(类名::方法名)
格式
类名::方法名
使用前提
如果我们在重写方法的时候,方法体只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们可以引用对象的实例方法。
例如:案例代码如下
public class MethodDemo {
public interface UseString {
String use(String str,int stat,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) {
String str = subAuthorName("hello", new UseString() {
@Override
public String use(String str, int stat, int length) {
return str.substring(stat, length);//方法体中只有一行代码✔调用第一个参数的成员方法✔重写方法的剩余参数按照顺序全部传入这个成员方法✔
}
});
System.out.println(str);
}
}
优化代码
String str = subAuthorName("hello", String::substring);
System.out.println(str);
4. 构造器引用(类名::new)
如果方法体中的一行代码是构造器的话可以使用构造器引用
格式
类名::new
使用前提
如果我们在重写方法的时候,方法体只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把重写的抽象方法中所有的参数都按照顺序传入了这个构造方法中,这个时候我们可以引用对象的实例方法。
案例:
List<Author> authors = getAuthors();//获取到一个Author类列表,方法的具体实现在11.3章节
authors.stream()
.map(author -> author.getName())
.map(new Function<String, StringBuffer>() {
@Override
public StringBuffer apply(String str1) {
return new StringBuffer(str1);//方法体只有一行代码✔调用了某个类的构造方法✔重写的抽象方法中的所有参数都按照顺序传入构造方法中
}
})
.map(sb->sb.append("-b0").toString())
.forEach(str-> System.out.println(str));
优化
authors.stream()
.map(author -> author.getName())
.map(StringBuffer::new)//构造器引用优化,只关注类名和构造器new
.map(sb->sb.append("-b0").toString())
.forEach(str-> System.out.println(str));
11.7高级用法
基本数据类型优化
我们之前用到的很多Stream的方法都由于使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。但是你一定要知道拆箱和装箱肯定是会消耗时间的。虽然这个时间消耗很小,但是在大量的数据不断的重复装箱、拆箱时,就不能无视这个时间损耗。
以下案例:
authors.stream()
.map(author -> author.getAge())//返回值为Integer包装类类型的stream流
.map(age->age+10)//这里返回Integer,在拿到数据运算时自动拆箱为int计算,计算完以后再进行装箱为Integer类型
.filter(age->age>18)//内部自动拆箱装箱,返回Integer类型数据,后续操作都需拆箱、装箱
.map(age->age+2)
.forEach(System.out::println);
因此为了让我们能够对这部分 装箱拆箱 的时间消耗进行优化,stream提供了很多专门针对基本数据类型的方法
例如:mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble等
authors.stream()
.mapToInt(author->author.getAge())//返回为Int类型的Stream流
.map(age->age+10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);
优化后不会再频繁拆箱装箱,极大提高效率
并线流
当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完成。如果我们自己去用代码实现的话会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的化,我们只需要修改一个方法的调用就可以使用并行流帮我们实现,从而提高效率。
public static 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, element) -> result + element)
.get();
System.out.println(sum);
}
使用parallel方法把串行流转换为并行流,提高效率
使用peek()中间操作,用于调试查看运行状态。此处打印调用的线程名
在构建流时使用parallelStream
authors.parallelStream()
.map(author -> author.getAge())
.map(age->age+10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);
资料参考:B站UP 三更草堂