目录
一 . Lambda 表达式
Lambda 表达式可以对某些匿名内部类的写法进行简化. 它是函数式编程的一个重要体现, 让我们不用太关注什么是对象, 而是更关心对数据的处理.
基本格式:
(参数列表)->{代码}
实例:
例子1: 当我们在创建一个线程的时
没有使用 Lambda 表达式:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我使用了匿名内部类的方式");
}
}).start();
结果: 我使用了匿名内部类的方式
使用 Lambda 了表达式:
new Thread(() -> {
System.out.println("使用 Lambda 了表达式");
}).start();
结果: 使用 Lambda 了表达式
例子2:
没有使用 Lambda 表达式:
@Test
void test2() {
int i = calculateNum(new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return left + right;
}
});
System.out.println(i);
}
public static int calculateNum(IntBinaryOperator operator) {
int a = 10;
int b = 20;
return operator.applyAsInt(a, b);
}
结果: 30
使用 Lambda 了表达式:
@Test
void test2() {
int i = calculateNum((int left, int right) -> {
return left + right;
});
System.out.println(i);
}
public static int calculateNum(IntBinaryOperator operator) {
int a = 10;
int b = 20;
return operator.applyAsInt(a, b);
}
结果: 30
tips: 上面代码出现的 IntBinaryOperator 是一个接口, 并且有一个 applyAsInt 方法
例子3:
没有使用 Lambda 表达式:
@Test
void test3() {
printNum(new IntPredicate() {
@Override
public boolean test(int 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);
}
}
}
结果:
2
4
6
8
10
使用 Lambda 了表达式:
@Test
void test3() {
printNum((int 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);
}
}
}
结果:
2
4
6
8
10
tips: 上面代码出现的 IntPredicate 也是一个接口, 并且有一个 test 方法
例子4:
没有使用 Lambda 表达式:
@Test
void test4() {
Integer integer = typeConver(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.valueOf(s);
}
});
System.out.println(integer);
}
public static <R> R typeConver(Function<String, R> function) {
String str = "12345";
R result = function.apply(str);
return result;
}
结果: 12345
使用 Lambda 了表达式:
@Test
void test4() {
Integer integer = typeConver((String s) -> {
return Integer.valueOf(s);
});
System.out.println(integer);
}
public static <R> R typeConver(Function<String, R> function) {
String str = "12345";
R result = function.apply(str);
return result;
}
结果: 12345
我们还可以让它返回一个 String 类型的值:
@Test
void test4() {
String str = typeConver((String s) -> {
return s + "上山打老虎";
});
System.out.println(str);
}
public static <R> R typeConver(Function<String, R> function) {
String str = "12345";
R result = function.apply(str);
return result;
}
结果: 12345上山打老虎
tips: 上面代码出现的 Function<T, R> 也是一个接口, 并且有一个 apply 方法
例子5:
没有使用 Lambda 表达式:
@Test
void test5() {
foreachArr(new IntConsumer() {
@Override
public void accept(int value) {
System.out.print(value + " ");
}
});
}
public static void foreachArr(IntConsumer consumer) {
int[] arr = {1, 2, 3 ,4, 5, 6, 7, 8, 9, 10};
for (int i : arr) {
consumer.accept(arr[i]);
}
}
结果: 1 2 3 4 5 6 7 8 9 10
使用了 Lambda 表达式:
@Test
void test5() {
foreachArr((int value) -> {
System.out.print(value + " ");
});
}
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);
}
}
tips: 上面代码出现的 IntConsumer 也是一个接口, 并且有一个 accept 方法
Lambda 的简写:
Lambda 表达式 只关注 参数列表 和 方法体, 其他的一律不过问.
省略规则:
1. 参数类型可以省略
2. 方法体如果只有一局代码时, 大括号"{}" 和 return 还有 分号 “;” 可以省略
3. 方法只有一个参数时,参数列表的小括号可以省略
如果上述规则觉得有些复杂,可以不用记,可以先使用函数式编程把代码敲出来,让后将鼠标移动到 对象名 上,让后按住 art + enter 键,选 "Replace with lambda"。
然后就变成简化后的 Lambda 表达式了
二. Stream 流
Java8 的 Stream 使用的是函数式编程模式, 它可以被用来对集合或数组进行链状流式的操作. 可以更方便的让我们对数组或集合进行操作.
案例数据准备:
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode // 用于后期的去重使用
public class Book {
// id
private Long id;
// 分类
private String category;
// 书名
private String name;
// 评分
private Double score;
// 简介
private String intro;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode // 用于后期的去重使用
public class Author {
// id
private Long id;
// 姓名
private String name;
// 年龄
private Integer age;
// 简介
private String info;
// 作品
private List<Book> books;
}
创建一个测试类
// 初始化一些数据
private static List<Author> getAuthors() {
Author author1 = new Author(1L, "周杰伦", 18, "my introduction 1", null);
Author author2 = new Author(2L, "周星驰", 19, "my introduction 2", null);
Author author3 = new Author(3L, "周润发", 20, "my introduction 3", null);
Author author4 = new Author(4L, "周迅", 17, "my introduction 4", null);
Author author5 = new Author(5L, "周一", 16, "my introduction 4", null);
List<Book> books1 = new ArrayList<>();
List<Book> books2 = new ArrayList<>();
List<Book> books3 = new ArrayList<>();
// 上面是作者和书
books1.add(new Book(1L, "类别,分类啊", "书名1", 45D, "这是简介哦"));
books1.add(new Book(2L, "高效,天啊", "书名2", 84D, "这是简介哦"));
books1.add(new Book(3L, "喜剧,高效", "书名3", 83D, "这是简介哦"));
books2.add(new Book(5L, "天啊", "书名4", 65D, "这是简介哦"));
books2.add(new Book(6L, "高效", "书名5", 89D, "这是简介哦"));
books3.add(new Book(7L, "久啊,天啊", "书名6", 45D, "这是简介哦"));
books3.add(new Book(8L, "高效", "书名7", 44D, "这是简介哦"));
books3.add(new Book(9L, "喜剧,高效", "书名8", 81D, "这是简介哦"));
author1.setBooks(books1);
author2.setBooks(books2);
author3.setBooks(books3);
author4.setBooks(books3);
author5.setBooks(books2);
return new ArrayList<>(Arrays.asList(author1, author2, author3, author4, author5));
}
private Author getAuthor() {
Author author = new Author(1L, "周杰伦", 18, "my introduction 1", null);
List<Book> books1 = new ArrayList<>();
books1.add(new Book(1L, "类别,分类啊", "书名1", 45D, "这是简介哦"));
books1.add(new Book(2L, "高效,天啊", "书名2", 84D, "这是简介哦"));
books1.add(new Book(3L, "喜剧,高效", "书名3", 83D, "这是简介哦"));
author.setBooks(books1);
return author;
}
入门实例:
例子1:
现在需要打印所有年龄小于 18 的作家的名字, 并且要注意去重
(使用函数式编程)
@Test
void test1() {
// 我们可以调用 getAuthors 方法获取到作家集合.
List<Author> authors = getAuthors();
// 现在需要打印所有年龄小于 18 的作家的名字, 并且要注意去重
authors.stream() // 把对象转换成流
.distinct() // 去重
.filter(new Predicate<Author>() { // 筛选小于 18 岁的作者
@Override
public boolean test(Author author) {
return author.getAge() < 18;
}
})
.forEach(new Consumer<Author>() { // 打印
@Override
public void accept(Author author) {
System.out.println(author);
}
});
}
结果:
Author(id=4, name=周迅, age=17, info=my introduction 4, books=[Book(id=7, name=久啊, category=书名6, score=45.0, intro=这是简介哦), Book(id=8, name=高效, category=书名7, score=44.0, intro=这是简介哦), Book(id=9, name=喜剧, category=书名8, score=81.0, intro=这是简介哦)])
Author(id=5, name=周一, age=16, info=my introduction 4, books=[Book(id=5, name=天啊, category=书名4, score=65.0, intro=这是简介哦), Book(id=6, name=高效, category=书名5, score=89.0, intro=这是简介哦)])
(使用 Lambda 表达式, 结果同上)
@Test
void test1() {
// 我们可以调用 getAuthors 方法获取到作家集合.
List<Author> authors = getAuthors();
// 现在需要打印所有年龄小于 18 的作家的名字, 并且要注意去重
// 筛选小于 18 岁的作者
// 打印
authors.stream() // 把集合转换成流
.distinct() // 去重
.filter(author -> author.getAge() < 18)
.forEach(author -> System.out.println(author));
}
调试技巧:
我们再进行 Stream 调试的时候, 可以点击下图指示的按钮
就会出现下图的画面:
然会就可以看到一些调试信息了 ~~
常用操作:
创建流
1. 单例集合:
集合对象.stream()
List<Author> authors = getAuthors();
Stream<Author> system = authors.stream();
2. 数组
Arrags.stream(数组), 或者使用 Stream.of 来创建
Integer[] arr = {1, 2, 3, 4, 5};
Stream<Integer> system1 = Arrays.stream(arr);
Stream<Integer> system2 = Stream.of(arr);
3. 双列集合
转换成单例集合后再创建
Map<String, Integer> map = new HashMap<>();
map.put("鸣人", 1);
map.put("佐助", 2);
map.put("小樱", 3);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
中间操作
1. filter
filter 可以对流中的元素进行条件过滤, 符合过滤条件的才能继续留在流中.
例如:
打印所有姓名长度大于 1 的作家的名字
authors.stream()
.filter(author -> author.getName().length() > 2)
.forEach(author -> System.out.println(author));
2. map
map 可以把元素进行计算或者转换.
// 打印所有作家的姓名, 并在姓名后加 "***" (将 Author类 转换成 String类)
authors.stream()
.map(author -> author.getName())
.map(name -> name + "***")
.forEach(author -> System.out.println(author));
结果:
周杰伦***
周星驰***
周润发***
周迅***
周一***
3. distinct
distinct 可以去除流中重复元素
// 打印所有作家元素, 并且要求不能有重复元素
authors.stream()
.distinct()
.forEach(author -> System.out.println(author));
注意: distinct 方法是依赖 Objects 的 equals 方法来判断是否是相同的对象的, 所以需要注意重写 equals 方法. 这个重写我们使用了 Lombok 的注解来实现了.
4. sorted
sorted 可以对流中的对象进行排序
注意 : 如果使用空参的 sorted 方法, 在使用这个方法前, 对象要实现 Comparable 接口, 并重写 compareTo 方法:
// 对流中的作家元素按照年龄进行升序排序.
authors.stream()
.sorted()
.forEach(author -> System.out.println(author));
或者使用一个含有 Comparator 比较器的 sorted 方法:
// 对流中的作家元素按照年龄进行升序排序.
authors.stream()
.sorted((o1, o2) -> o1.getAge() - o2.getAge())
.forEach(author -> System.out.println(author));
5. limit
limit 可以设置流的最大长度, 超出的部分将被抛弃.
// 对流中的作家元素按照年龄进行升序排序. 然后打印年龄最小的两个作家
authors.stream()
.sorted((o1, o2) -> o1.getAge() - o2.getAge())
.limit(2L)
.forEach(author -> System.out.println(author));
6. skip
skip 跳过流中的前 n 个元素, 返回剩下的元素.
// 对流中的作家元素按照年龄进行升序排序. 然后打印除了年龄最小的两个作家之外的其他作家
authors.stream()
.sorted((o1, o2) -> o1.getAge() - o2.getAge())
.skip(2L)
.forEach(author -> System.out.println(author));
7. flatMap
flatMap 可以把一个对象转换成多个对象作为流中的元素.
例子1:
// 打印所有书籍的名字
// 匿名内部类版
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());
}
});
// Lambda 表达式版
authors.stream()
.flatMap((Function<Author, Stream<Book>>) author -> author.getBooks().stream())
.distinct()
.forEach(book -> System.out.println(book.getName()));
根据调试结果可以看出一个 Author 对象转换成多个 Book 对象作为流中的元素.
例子2:
// 打印所有书籍的分类, 要求对分类进行去重, 不能出现这种格式: 高效,天啊
authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.flatMap(book -> Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(category -> System.out.println(category));
终结操作
1. forEach
对流中的元素进行遍历操作, 我们通过传入的参数去指定对遍历到的元素进行什么具体操作.
// 打印所有作家的姓名
authors.stream()
.forEach(author -> System.out.println(author.getName()));
2. count
可以用来获取当前流中的元素个数
// 打印出这些作家所出书籍的数目, 注意删除重复的元素
long count = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.count();
System.out.println(count);
3. max & min
可以获取流中的最值
// 分别获取这些作家的所出书籍的最高分和最低分并打印
Optional<Double> max = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getScore())
.max((o1, o2) -> (int) (o1 - o2));
System.out.println(max.get());
Optional<Double> min = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getScore())
.min((o1, o2) -> (int) (o1 - o2));
System.out.println(min.get());
4. collect
collect 把当前流转换成一个集合
例子1:
// 获取一个存放所有作者名字的 List 集合
List<String> authorsName = authors.stream()
.map(author -> author.getName())
.collect(Collectors.toList());
System.out.println(authorsName);
例子2:
// 获取一个所有书名的 Set 集合
Set<String> booksName = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getName())
.collect(Collectors.toSet());
System.out.println(booksName);
例子3:
// 获取一个 Map 集合, map 的 key 作为 作者名, value 为 List<Book>
// 匿名内部类版
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();
}
}));
// Lambda 表达式版
Map<String, List<Book>> map = authors.stream()
.distinct()
.collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
注意: key 值不能重复, 不然会报错!!!
查找与匹配
1. anyMatch
anyMatch 可以用来判断是否有任意元素符合匹配条件, 结果为 boolean 类型
// 判断是否有年龄在 19 岁以上的作家
boolean b = authors.stream()
.anyMatch(author -> author.getAge() > 19);
System.out.println(b);
2. allMatch
可以用来判断是否所有元素符合匹配条件, 结果为 boolean 类型
// 判断是否所有年龄在 19 岁以上的作家
boolean b = authors.stream()
.allMatch(author -> author.getAge() > 19);
System.out.println(b);
3. noneMatch
noneMatch 可以用来判断是否所有元素都不符合匹配条件, 结果为 boolean 类型, 都不符合返回 true, 都不符合返回 false
boolean b = authors.stream()
.noneMatch(author -> author.getAge() > 100);
System.out.println(b);
4. findAny
findAny 获取流中的任意一个元素.
// 获取任意一个年龄大于 18 的作家, 如果存在就输出它的姓名
Optional<Author> optionalAuthor = authors.stream()
.filter(author -> author.getAge() > 18)
.findAny();
optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
5. firstAny
firstAny 获取流中的第一个元素.
// 获取一个年龄最小的作家, 如果存在就输出它的姓名
Optional<Author> optionalAuthor = authors.stream()
.sorted((o1, o2) -> o1.getAge() - o2.getAge())
.findFirst();
optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
reduce 归并
对流中的数据, 按照你指定的计算方式计算出一个结果.(缩紧操作)
reduce 的作用十八 stream 中的元素给组合起来, 我们可以传入一个初始值, 他会按照我们计算方式依次拿流中的元素和在初始化值进行计算, 计算结果再和后面的元素计算.
reduce 的两个参数的重载形式内部的计算方式如下:
T result = identity; // 相当于初始值
for (T element : this stream) {
result = accumulator.apply(result, element);
}
return result;
// 你可以把这段代码想象成
int[] arr = {1, 2, 3, 4, 5};
int sum = 0;
for (int i : arr) {
sum = sum + i;
}
return sum;
实例:
例子1:
// 使用 reduce 求所有作家年龄的和
// 使用匿名内部类方式
Integer ageCount = authors.stream()// 先把 Author 对象转换为 Integer 对象
.map(author -> author.getAge())
// 此处的 0 相当于初始值 identity
.reduce(0, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result+ element;
}
});
System.out.println(ageCount);
// 使用Lambda表达式
Integer ageCount = authors.stream()// 先把 Author 对象转换为 Integer 对象
.map(author -> author.getAge())
.reduce(0, (result, element) -> result+ element);
System.out.println(ageCount);
例子2:
// 使用 reduce 求作者年龄的最大值
// 使用匿名内部类方式
Integer maxAge = authors.stream()
.map(author -> author.getAge())
.reduce(Integer.MIN_VALUE, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result < element ? element : result;
}
});
System.out.println(maxAge);
// 使用Lambda表达式
Integer maxAge = authors.stream()
.map(author -> author.getAge())
.reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);
System.out.println(maxAge);
reduce 的一个参数的重载形式内部的计算方式如下:
boolean foundAny = false;
T result = null;
for (T element : this stream) {
if (!foundAny) { // 第一次遍历走到这里
foundAny = true; // 将 foundAny 置为true, 相当于这里面的代码只执行一次
result = element; // 将第一个元素给到 result
}
else // 其余的遍历走下面这行代码
result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();
例子3:
// 使用 reduce 求作者年龄的最小值
// 使用匿名内部类方式
Optional<Integer> minAge = authors.stream()
.map(author -> author.getAge())
.reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result > element ? element : result;
}
});
minAge.ifPresent(age -> System.out.println(age));
// 使用Lambda表达式
Optional<Integer> minAge = authors.stream()
.map(author -> author.getAge())
.reduce((result, element) -> result > element ? element : result);
minAge.ifPresent(age -> System.out.println(age));
注意事项:
1. 惰性求值 (如果没有终结操作, 中间操作是不会得到执行的)
2. 流是一次性的 (一旦一个流对象经过一个终结操作后, 这个流不能再被使用)
3. 不会影响原数据 (我们在流中可以多数据地做很多处理, 但是正常情况下(不调用原数据的set方法等)是不会影响原来集合中的元素的)
三. Optional
我们在编写代码的时候经常会出现空指针异常, 所以在很多情况下我们需要做各种非空的判断.
比如:
Author author = getAuthor();
if (author != null) {
System.out.println(author.getName());
}
尤其是对象中的属性还有一个对象的情况下, 这种判断会更多.
而过多的判断语句会使我们的代码显得臃肿不堪.
所以在 JDK 8 中引入了 Optional, 养成使用 Optional 的习惯后, 可以写出更优雅的代码来避免空指针异常. 并且在很多的函数式编程相关的 API 中也都用到了 Optional.
1. 创建对象
Optional 就好像是包装类,可以把我们的具体数据封装 Optional 对象内部。 然后我们去使用Optional 中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。
我们一般使用 Optional 的静态方法 ofNullable 来把数据封装成一个 Optional 对象。 无论传入的参数是否为null都不会出现问题。
Author authors = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(authors);
authorOptional.ifPresent(author -> System.out.println(author.getName()));
你可能会觉得还要加一行代码来封装数据比较麻烦。但是如果改造下 getAuthor 方法,让其的返回值就是封装好的 Optional 的话,我们在使用时就会方便很多。
而且在实际开发中我们的数据很多是从数据库获取的。Mybatis 从 3.5 版本可以也已经支持 Optional 了。 我们可以直接把 dao 方法的返回值类型定义成 Optional 类型,MyBastis 会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。
如果你确定一个对象不是空的则可以使用Optional的静态方法of来把数据封装成Optional对象。
Author authors = new Author();
Optional<Author> authorOptional = Optional.of(authors);
但是一定要注意,如果使用of的时候传入的参数必须不为 null。
如果一个方法的返回值类型是 Optional 类型。 而如果我们经判断发现某次计算得到的返回值为 null,这个时候就需要把 null 封装成 Optional 对象返回。这时则可以使用 Optional 的静态方法 empty 来进行封装。
Optional.empty()
2. 安全消费值
我们获取到一个optional对象后肯定需要对其中的数据进行使用。这时候我们可以使用其 ifPresent 方法对来消费其中的值。这个方法会判断其内封装的数据是否为空, 不为空时才会执行具体的消费代码。这样使用起来就更加安全了。
例如, 以下写法就优雅的避免了空指针异常。
Optional<Author> authorOptional = Optional.ofNullable(authors);
authorOptional.ifPresent(author -> System.out.println(author.getName()));
3. 获取值
如果我们想获取值自己进行处理可以使用get方法获取,但是不推荐。因为当Optional内部的数据为空的时候会出现异常。
Author authors = new Author();
Optional<Author> authorOptional = Optional.of(authors);
System.out.println(authorOptional.get().getName());
4. 安全获取值
如果我们期望安全的获取值。我们不推荐使用get方法,而是使用Optional提供的以下方法。
- orElseGet
获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建对象作为默认值返回。
Author authors = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(authors);
System.out.println(authorOptional.orElseGet(new Supplier<Author>() {
@Override
public Author get() {
return new Author(); // 为空时的默认返回值
}
}).getName());
获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。
- orElseThrow
Author authors = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(authors);
System.out.println(authorOptional.orElseThrow(new Supplier<Throwable>() {
@Override
public Throwable get() {
return new RuntimeException("数据为空"); // 为空时抛出异常
}
}).getName());
5. 过滤
我们可以使用 filter 方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的 Optional 对象。
Author authors = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(authors);
Optional<Author> newAuthor = authorOptional.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 18;
}
});
6. 判断
我们可以使用 isPresent 方法进行是否存在数据的判断。如果为空返回值为false,如果不为空,返回值为true。但是这种方式并不能体现 Optional 的好处,更推荐使用ifPresent方法。
Author authors = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(authors);
if (authorOptional.isPresent()) {
System.out.println(authorOptional.get().getName());
}
7. 数据转换
Optional 还提供了 map 可以让我们的对数据进行转换,并且转换得到的数据也还是被 Optional 包装好的,保证了我们的使用安全。
例如我们想获取作家的书籍集合:
Author authors = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(authors);
authorOptional.map(new Function<Author, List<Book>>() {
@Override
public List<Book> apply(Author author) {
return author.getBooks();
}
}).ifPresent(books -> System.out.println(books));
四. 函数式接口
只有一个抽象方法的接口我们称之为函数接口。
JDK的函数式接口都加上了 @FunctionalInterface 注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
常见函数式接口
- Consumer消费接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。
- Function计算转换接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回。
- Predicate判断接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果。
- Supplier生产型接口
根据其中抽象方法的参数列表和返回值类型知道, 我们可以在方法中创建对象, 把创建好的对象返回
常见默认方法
- and
我们在使用 Predicate 接口时候可能需要进行判断条件的拼接。而 and 方法相当于是使用 && 来拼接两个判断条件
例如:
打印作家中年龄大于17并且姓名的长度大于1的作家。
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 18;
}
}.and(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getName().length() > 2;
}
})).forEach(author -> System.out.println(author));
// 简写
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter((Predicate<Author>) author -> author.getAge() > 18 && author.getName().length() > 2).forEach(author -> System.out.println(author));
- or
我们在使用 Predicate 接口时候可能需要进行判断条件的拼接。而 or 方法相当于是使用| |来拼接两个判断条件。
例如:
打印作家中年龄大于17或者姓名的长度小于2的作家。
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 18;
}
}.or(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getName().length() > 2;
}
})).forEach(author -> System.out.println(author));
// 简写
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter((Predicate<Author>) author -> author.getAge() > 18 || author.getName().length() > 2).forEach(author -> System.out.println(author));
- negate
Predicate 接口中的方法。negate 方法相当于是在判断添加前面加了个 ! 表示取反
例如:
打印作家中年龄不大于17的作家。
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 17;
}
}.negate()).forEach(author -> System.out.println(author));
五. 方法引用
我们在使用lambda时,如果方法体中只有一个方法的调用的话(包括构造方法) ,我们可以用方法引用进一步简化代码。
1. 推荐用法
我们在使用lambda时不需要考虑什么时候用方法引用, 用哪种方法引用, 方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码, 并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。
当我们方法引用使用的多了慢慢的也可以直接写出方法引用。
转换成
2. 基本格式
类名或者对象名::方法名
3. 语法详解(了解)
引用静态方法
其实就是引用类的静态方法
格式:
类名::方法名
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。
例如:
如下代码就可以用方法引用进行简化
// 使用方法引用前
Author authors = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(authors);
authorOptional.map(author -> author.getAge())
.map(new Function<Integer, String>() {
@Override
public String apply(Integer age) {
return String.valueOf(age);
}
});
// 使用方法引用后
Author authors = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(authors);
authorOptional.map(author -> author.getAge())
.map(String::valueOf);
引用对象的实例方法
格式:
对象名::方法名
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法, 并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法
例如:
// 使用方法引用前
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName()).forEach(new Consumer<String>() {
@Override
public void accept(String name) {
sb.append(name);
}
});
优化后:
// 使用方法引用前
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName()).forEach(sb::append);
引用类的实例方法
格式:
类名::方法名
使用前提
如果我们在重写方法的时候,方法体中只有一行代码, 并且这行代码是调用了第一个参数的成员方法, 并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。
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);
}
@Test
void testMethodReference() {
// 优化前
subAuthorName("周杰伦", new UseString() {
@Override
public String use(String str, int start, int length) {
return str.substring(start, length); // 第一个对象把后面两个按照顺序传入了,所以可以直接简化
// 优化后
subAuthorName("周杰伦", String::substring);
}
});
}
构造器引用
如果方法体中的一行代码是构造器的话可以使用构造器引用
格式:
类名::new
使用前提
如果我们在重写方法的时候,方法体中只有一行代码, 并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。
// 优化前
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getName())
.map(new Function<String, StringBuilder>() {
@Override
public StringBuilder apply(String name) {
return new StringBuilder(name);
}
})
.map(stringBuilder -> stringBuilder.append("好酷哦").toString())
.forEach(str -> System.out.println(str));
// 优化后
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getName())
.map(StringBuilder::new)
.map(stringBuilder -> stringBuilder.append("好酷哦").toString())
.forEach(str -> System.out.println(str));
高级用法
基本数据类型优化
我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。 但是你一定要知道装箱和拆箱肯定是要消耗时间的。虽然这个时间消耗很下。但是在大量的数据不断的重复装箱拆箱的时候,你就不能无视这个时间损耗了。
所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了 很多专门针对基本数据类型的方法。
例如: mapToInt,mapToLong,mapToDouble,flatMapTolnt,flatMapToDouble等。
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);
并行流
当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。
parallel 可以把串行流改成并行流.
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5 ,6, 7, 8, 9, 10);
Integer sum = stream.parallel()
.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();
结果:
数字7 的线程为 main
数字6 的线程为 main
数字2 的线程为 main
数字1 的线程为 ForkJoinPool.commonPool-worker-11
数字8 的线程为 main
数字4 的线程为 main
数字10 的线程为 ForkJoinPool.commonPool-worker-4
数字5 的线程为 ForkJoinPool.commonPool-worker-11
数字3 的线程为 ForkJoinPool.commonPool-worker-9
数字9 的线程为 ForkJoinPool.commonPool-worker-2
也可以通过 parallelStream 直接获取并行流对象
List<Author> authors = getAuthors();
authors.parallelStream()
.mapToInt(author -> author.getAge())
.map(age -> age + 10)
.filter(age -> age > 18)
.map(age -> age + 2)
.forEach(System.out::println);
以上内容参考自 up 主 --- 三更草堂的视频