Lambda表达式深入浅出
1 概念简介
1.1Lambda表达式是什么
Lambda表达式是JAVA8中提供的一种新的特性,是一个匿名函数方法,可以对某些匿名内部类的写法进行简化。我们可以把Lambda表达式理解为一段可以传递的代码,可以写出更简洁、更灵活的代码。
1.2 Lambda表达式在什么条件下使用
Lambda表达式可以认为是对匿名内部类的一种简化,但不是所有的匿名内部类都可以简化为Lambda表达式。
重点掌握:只有函数式接口的匿名内部类才可以使用Lambda表达式来进行简化。
函数式接口不同于普通接口,较为特殊化。接口当中只有一个抽象方法是需要我们去实现的,Lambda表达式正好是针对这个唯一的抽象方法使用。
1.3 Lambda表达式带来的编程好处
能够快速提高集合的处理效率
代码的可读性提高
简化嵌套编程
易于 并发编程
丰富的api更快速的获取结果
帮助我们更好的去阅读包含lambda表达式的源码
2 开始Lambda表达式学习
2.1 Lambda 表达式时Java 8 的一种语法糖
基本格式
(参数列表...) -> {代码逻辑}
针对一些可推导的参数返回值等等有些是可以直接省略的
2.2 示例讲解
示例1
public class LambdaDemo {
public static void main(String[] args) {
// 声明集合
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
//lambda 实现
//简写方式
//list.forEach(System.out::println);
list.forEach((s) -> {
System.out.println(s);
});
//增强for 循环
for (String s : list) {
System.out.println(s);
}
}
}
在循环遍历时使用Lambda 表达式可以让代码更简洁,同时执行效率也被提高
示例2
public class LambdaDemo2 {
public static void main(String[] args) {
// 接口方法的实现和调用
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};
r.run();
// 函数式编程是新
Runnable r2 = () -> {
System.out.println("hello2");
};
r2.run();
}
}
可以看到Lambda表达式不关注类型以及返回值,它只关注方法的参数和方法体里面的代码,这样就帮我们很好的去除了繁琐的接口实现代码的编写,让代码更简洁更容易阅读
示例3
public class LambdaDemo3 {
public static void main(String[] args) {
// 例如我们很熟悉的集合排序
List<Integer> numList = new ArrayList<Integer>(Arrays.asList(7, 2, 5, 4, 9, 0, 1, 3, 11));
//匿名内部类写法
Collections.sort(numList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
System.out.println(numList);
// 使用Lambda 表达式写法
Collections.sort(numList, (o1, o2) -> { return o1.compareTo(o2); });
System.out.println(numList);
}
示例4
public class LambdaDemo4 {
public static void main(String[] args) {
long number = getNumber(new LongFunction<Long>() {
@Override
public Long apply(long value) {
return value;
}
});
System.out.println(number);
long number1 = getNumber((value) -> {
return value;
});
System.out.println("number :"+number1);
}
public static<R> R getNumber(LongFunction<R> longFunction){
return longFunction.apply(22L);
}
}
示例5
public class LambdaDemo5 {
public static void main(String[] args) {
// 匿名内部类的写法
Long aLong = convertType("567", new Function<String, Long>() {
@Override
public Long apply(String s) {
Long aLong = Long.valueOf(s);
return aLong;
}
});
System.out.println(aLong);
// lambda 表达式写法
Long aLong1 = convertType("89000", (s) -> {
return Long.valueOf(s);
});
System.out.println(aLong1);
}
public static <R> R convertType(String value,Function<String, R> function) {
R r = function.apply(value);
return r;
}
}
示例总结:
函数式接口方法参数没有可以直接省略 用括号 () -> {}
函数式接口方法参数类型可以省略
函数式接口方法体只有一行代码时代码和return 时后面的省略号可不写
2.3 流式编程Stream流
2.3.1 概念简述
stream流操作是Java 8提供一个重要新特性,它允许开发人员以声明性方式处理集合,其核心类库主要改进了对集合类的 API和新增Stream操作。Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中,能 让代码更加简洁,极大地简化了集合的处理操作,提高了开发的效率和生产力。
特性介绍:
stream流操作是Java 8提供一个重要新特性,它允许开发人员以声明性方式处理集合,其核心类库主要改进了对集合类的 API和新增Stream操作。Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中,能 让代码更加简洁,极大地简化了集合的处理操作,提高了开发的效率和生产力。
同时stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。在Stream中的操作每一次都会产生新的流,内部不会像普通集合操作一样立刻获取值,而是 惰性取值 ,只有等到用户真正需要结果的时候才会执行。 并且对于现在调用的方法,本身都是一种高层次构件,与线程模型无关。因此在并行使用中,开发者们无需再去操 心线程和锁了。Stream内部都已经做好了 。
2.3.2 准备工作
创建一个Maven 工程,引入Lombok
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jack.study</groupId>
<artifactId>Lambda-Stream</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
</project>
创建操作实体类
数据工具类
public class DataUtil {
public static List<Cook> getCooks() {
Cook cook1 = new Cook(1L,"王大厨" , 3, "传承川菜", null);
Cook cook2 = new Cook(2L,"赵大厨" , 7, "传承湘菜鄂菜", null);
Cook cook3 = new Cook(3L,"钱大厨" , 5, "传承湘菜", null);
Cook cook4 = new Cook(4L,"孙大厨" , 2, "传承粤菜", null);
Cook cook5 = new Cook(4L,"孙大厨" , 2, "传承粤菜", null);
List<Food> foods1 = new ArrayList<>();
List<Food> foods2 = new ArrayList<>();
List<Food> foods3 = new ArrayList<>();
foods1.add(new Food(1,"宫保鸡丁" , "川菜", 97, "川菜大师"));
foods1.add(new Food(2,"辣子鸡" , "川菜", 92, "川菜大师"));
foods1.add(new Food(3,"土豆烧鸡" , "川菜", 99, "川菜大师"));
foods2.add(new Food(4,"水煮鱼" , "川菜", 87, "湘菜大师"));
foods2.add(new Food(4,"水煮鱼" , "川菜", 91, "川菜湘菜大师"));
foods2.add(new Food(5,"红烧鱼块" , "川菜", 82, "湘菜大师"));
foods3.add(new Food(6,"毛血旺" , "川菜", 97, "川菜大师"));
foods3.add(new Food(7,"孜然肉片" , "湘菜", 99, "湘菜大师"));
foods3.add(new Food(8,"西冷牛排" , "鄂菜", 70, "西餐大师"));
foods3.add(new Food(8,"西冷牛排" , "西餐", 81, "西餐大师"));
cook1.setFoods(foods1);
cook2.setFoods(foods2);
cook3.setFoods(foods3);
cook4.setFoods(foods3);
cook5.setFoods(foods3);
List<Cook> cooks = new ArrayList<>(Arrays.asList(cook1, cook2, cook3, cook4,cook5));
return cooks;
}
}
厨师类
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Cook {
// 工号
private Long id;
//姓名
private String name;
//工龄
private Integer workingYear;
// 厨师描述
private String describe;
// 菜品集合
private List<Food> foods;
}
菜品类
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Food {
// 菜id
private Integer id;
//菜名
private String name;
// 菜系
private String type;
// 评分
private Integer score;
// 菜描述
private String description;
}
测试类
需求:获取工龄大于3 的所有不重复的厨师
public class StreamDemo {
public static void main(String[] args) {
List<Cook> cooks = getCooks();
// 获取工龄大于3 的所有厨师
// 第一步去重
//第二部过滤
cooks.stream().distinct().filter(new Predicate<Cook>() {
@Override
public boolean test(Cook cook) {
return cook.getWorkingYear() >= 3;
}
}).forEach(new Consumer<Cook>() {
@Override
public void accept(Cook cook) {
System.out.println(cook.getName());
}
});
// Set<Cook> cookSet = cooks.stream().distinct().filter(c -> c.getWorkingYear() >= 3).collect(Collectors.toSet());
// System.out.println(cookSet);
//stream简写方式
cooks.stream().distinct().filter((cook) -> {return cook.getWorkingYear() >= 3;})
.forEach((c) -> {
System.out.println(c.getName());
});
}
private static List<Cook> getCooks() {
Cook cook1 = new Cook(1L,"王大厨" , 3, "传承川菜", null);
Cook cook2 = new Cook(2L,"赵大厨" , 7, "传承湘菜鄂菜", null);
Cook cook3 = new Cook(3L,"钱大厨" , 5, "传承湘菜", null);
Cook cook4 = new Cook(4L,"孙大厨" , 2, "传承粤菜", null);
Cook cook5 = new Cook(4L,"孙大厨" , 2, "传承粤菜", null);
List<Food> foods1 = new ArrayList<>();
List<Food> foods2 = new ArrayList<>();
List<Food> foods3 = new ArrayList<>();
foods1.add(new Food(1,"宫保鸡丁" , "川菜", 97, "川菜大师"));
foods1.add(new Food(2,"辣子鸡" , "川菜", 92, "川菜大师"));
foods1.add(new Food(3,"土豆烧鸡" , "川菜", 99, "川菜大师"));
foods2.add(new Food(4,"水煮鱼" , "川菜", 87, "湘菜大师"));
foods2.add(new Food(4,"水煮鱼" , "川菜", 91, "川菜湘菜大师"));
foods2.add(new Food(5,"红烧鱼块" , "川菜", 82, "湘菜大师"));
foods3.add(new Food(6,"毛血旺" , "川菜", 97, "川菜大师"));
foods3.add(new Food(7,"孜然肉片" , "湘菜", 99, "湘菜大师"));
foods3.add(new Food(8,"西冷牛排" , "鄂菜", 70, "西餐大师"));
foods3.add(new Food(8,"西冷牛排" , "西餐", 81, "西餐大师"));
cook1.setFoods(foods1);
cook2.setFoods(foods2);
cook3.setFoods(foods3);
cook4.setFoods(foods3);
cook5.setFoods(foods3);
List<Cook> cooks = new ArrayList<>(Arrays.asList(cook1, cook2, cook3, cook4,cook5));
return cooks;
}
}
2.3.2 Stream 调用流程
2.3.2.1 创建流
常见创建方式:
集合创建: 集合.stream()
List<Cook> cooks = getCooks();
// 获取工龄大于3 的所有厨师
cooks.stream();
数组创建: Arrays.stream()也可以使用Stream.of()
// 排序操作
Integer[] array = {10, 9, 8 ,2,4,5};
Stream<Integer> stream = Arrays.stream(array);
List<Integer> collect = stream.sorted((o1, o2) -> o1 - o2).collect(Collectors.toList());
双列集合操作: 需要先转换成单列集合然后创建stream
// 获取大于60的 key
Map<String , Integer> map = new HashMap<>();
map.put("a", 88);
map.put("b", 23);
map.put("c", 99);
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
entrySet.stream().filter(e -> e.getValue() > 60).forEach(e -> System.out.println(e.getKey()));
2.3.2.2 Stream 流中间操作
filter: 可以对流中的元素进行筛选,必须符合过滤规则才会保留在流中
// 获取会做湘菜的厨师
List<Cook> cooks = DataUtil.getCooks();
// 获取会做湘菜的厨师
cooks.stream().filter(cook -> cook.getDescribe().contains("湘")).forEach(cook -> {
System.out.println(cook.getName());
});
map
// 获取所有厨师的姓名
List<Cook> cooks = DataUtil.getCooks();
cooks.stream().map(cook -> cook.getName()).forEach(System.out::println);
// 获取工龄加上10
cooks.stream().map(cook -> cook.getWorkingYear())
.map(year -> year+ 10).forEach(System.out::println);
distinct: 去除流中相同的元素
List<Cook> cooks = DataUtil.getCooks();
// 过滤掉工龄相同的厨师
cooks.stream().distinct().forEach(cook -> {
System.out.println(cook.getName());
});
sorted:按照指定排序条件对流中的元素排序
// 按照工龄大小进行排序,不允许有重复厨师
List<Cook> cooks = DataUtil.getCooks();
// 无参需要自定义实现Comparable<Cook> 接口
cooks.stream().distinct().sorted().forEach(cook -> System.out.println(cook.getWorkingYear()));
//有参直接使用 Comparator 的方法比较
cooks.stream().distinct().sorted((o1, o2) -> o2.getWorkingYear()-o1.getWorkingYear()).forEach(cook -> System.out.println(cook.getWorkingYear()));
limit:指定长度,对流中超出长度元素会被过滤掉
// 获取工龄最大的前两位厨师姓名
List<Cook> cooks = DataUtil.getCooks();
cooks.stream().distinct().sorted((o1, o2) -> o2.getWorkingYear()-o1.getWorkingYear()).limit(2).forEach(cook -> System.out.println(cook.getName()));
skip:跳过流中指定数量的元素,返回剩下的元素
// 跳过指定个数元素返回剩下的元素,倒叙排序跳过前两个厨师
List<Cook> cooks = DataUtil.getCooks();
cooks.stream().distinct().sorted((o1, o2) -> o2.getWorkingYear()-o1.getWorkingYear()).skip(1).forEach(cook -> System.out.println(cook.getName()));
flatMap:前面的map我们只能将一个对象转换为另一个对象然后就行stream操作,而flatMap可以将一个对象转为多个对象作为流中元素进行操作
// 去除所有厨师里面包含 重复的菜
List<Cook> cooks = DataUtil.getCooks();
cooks.stream().flatMap( cook -> {
List<Food> foods = cook.getFoods();
return foods.stream();
}).distinct().forEach(food -> System.out.println(food.getName()));
多个flatMap使用
// 获取所有不重复厨师菜品中菜系,用逗号分隔完成后 去除重复菜系 输入过滤后的菜系名称
List<Cook> cooks = DataUtil.getCooks();
cooks.stream().flatMap(cook -> cook.getFoods().stream())
.distinct()
.flatMap(food -> Arrays.asList(food.getType().split(",")).stream())
.distinct()
.forEach(System.out::println);
2.3.2.3 Stream流终结操作
forEach:对流中所有元素进行遍历
//输出所有厨师名字
List<Cook> cooks = DataUtil.getCooks();
cooks.forEach(cook -> System.out.println(cook.getName()));
count:统计流中元素的个数
// 获取所有厨师的不重复的菜品总数
List<Cook> cooks = DataUtil.getCooks();
long count = cooks.stream().flatMap(cook -> cook.getFoods().stream()).distinct()
.count();
System.out.println(count);
max和min:获取流中元素指定类型的最大值和最小值
// 获取菜品评分最高的分数
List<Cook> cooks = DataUtil.getCooks();
Optional<Integer> max = cooks.stream()
.flatMap(cook -> cook.getFoods().stream())
.map(Food::getScore)
.max((o1, o2) -> o1 - o2);
System.out.println(max.get());
//获取菜品评分最低的分数
Optional<Integer> min = cooks.stream()
.flatMap(cook -> cook.getFoods().stream())
.map(Food::getScore)
.min((o1, o2) -> o1 - o2);
System.out.println(min.get());
collect: 将当前操作的流转换为一个新的集合
-转换为一个List集合
-转换为一个Set集合
-转换为一个Map集合
List<Cook> cooks = DataUtil.getCooks();
// 获取所有厨师姓名集合
List<String> list = cooks.stream().map(Cook::getName).collect(Collectors.toList());
System.out.println(list);
// 获取所有不重复菜品集合
Set<Food> foodSet = cooks.stream().flatMap(cook -> cook.getFoods().stream()).collect(Collectors.toSet());
System.out.println(foodSet);
// 转为map集合 key为厨师名称 value 为厨师菜品
// 这里需要注意map里面的key重复会报错,我们就需要用到第三个参数根据实际返回其中一个即可
Map<String, List<Food>> collect = cooks.stream().collect(Collectors.toMap(
Cook::getName,
Cook::getFoods,
(v1, v2) -> v2
));
System.out.println(collect);
2.3.2.4 Stream 查找和匹配
anyMatch: 判断流中是否有任意一个元素匹配,匹配返回true 不匹配返回false
List<Cook> cooks = DataUtil.getCooks();
//判断厨师是否工作年龄是否大于3
boolean b = cooks.stream().anyMatch(new Predicate<Cook>() {
@Override
public boolean test(Cook cook) {
return cook.getWorkingYear() > 30;
}
});
System.out.println(b);
noneMatch:判断流中元素都不满足条件, 不满足返回true 满足返回fasle
List<Cook> cooks = DataUtil.getCooks();
// 判断流中元素都不满足条件 工龄都大于30
boolean b1 = cooks.stream().noneMatch(cook -> cook.getWorkingYear() > 30);
System.out.println(b1);
allMatch: 判断流中所有元素都满足条件, 满足返回true 不满足返回fasle
List<Cook> cooks = DataUtil.getCooks();
// 判断所有厨师的工龄是否都大于0
boolean b2 = cooks.stream().allMatch(cook -> cook.getWorkingYear() > 0);
System.out.println(b2);
findAny: 获取流中的任意个元素
// 获取厨师工龄大于2 的第一个元素
List<Cook> cooks = DataUtil.getCooks();
Optional<Cook> cookOptional = cooks.stream().filter(c -> c.getWorkingYear() > 2).findAny();
cookOptional.ifPresent(cook -> System.out.println(cook.getName()));
findFirst: 获取元素中满足条件的第一个元素
// 获取工龄最小的厨师 也可以排序后通过limit 实现
List<Cook> cooks = DataUtil.getCooks();
Optional<Cook> first = cooks.stream().sorted((a, b) -> a.getWorkingYear() - b.getWorkingYear())
.findFirst();
first.ifPresent(c -> System.out.println(c.getWorkingYear()));
reduce:对流中的元素按照自定义的计算方法计算出一个结果使用提供的标识值和关联累积函数对此流的元素执行缩减,并返回缩减值
方法介绍
传入一个初始化的值,它会将我们的初始化值进行计算然后返回计算的结果
使用提供的标识值和关联累积函数对此流的元素执行缩减,并返回缩减值。这相当于:
T result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
但不限于按顺序执行。
该 identity 值必须是累加器函数的标识。这意味着对于所有人来说 t, accumulator.apply(identity, t) 都是平等的 t。该 accumulator 函数必须是 关联 函数。
这是一个 终端操作。
identity: 我们传入的参数
apply(result, element) : 具体的计算操作,但是具体怎么计算我们是不确定的
注意:在对元素进行操作的时候,我们先需要获取元素对应的类型,转换之后我们在使用reduce方法对元素进行操作
示例1:
// 获取所有厨师工龄之和
List<Cook> cooks = DataUtil.getCooks();
Integer reduce = cooks.stream().map(Cook::getWorkingYear)
.reduce(0, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
});
System.out.println(reduce);
示例2:
// reduce获取工龄最大
List<Cook> cooks = DataUtil.getCooks();
Integer reduce1 = cooks.stream().map(Cook::getWorkingYear).reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);
System.out.println(reduce1);
示例3:
// reduce获取工龄最小
List<Cook> cooks = DataUtil.getCooks();
Integer reduce2 = cooks.stream().map(Cook::getWorkingYear).reduce(Integer.MAX_VALUE, (result, element) -> result < element ? result : element);
System.out.println(reduce2);
reduce一个参数方法内部代码,实际上是将流里面的第一个元素赋值给result,后面其他元素直接走 accumulator.apply(result, element); 方法 完成计算
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();
注意事项:
流的使用是一次性使用完之后就不能再使用了
执行终结操作时一定会执行中间操作,同理中间操作执行时也一定要有对应的终结操作
操作原始流中数据时不会去影响原有集合 数组或者map的原始数据,这也是流操作所带来的好处,更新我们期望得到的结果
2.4 空指针优化帮手Optiona
2.4.1简介
Optional类是Java8为了解决null值判断问题,借鉴google guava类库的Optional类而引入的一个同名Optional类,使用Optional类可以避免显式的null值判断(null的防御性检查),避免null导致的NPE(NullPointerException),让代码更简洁。
好处:
通过使用Optional 提供的api我们能很好的处理对象为空或者对象里面的属性为空,过多的判断会让代码显得臃肿
,因此使用Optional去避免空指针异常,让代码写起来更简洁。
2.4.2 创建Optional
Cook cook = getCook();
Optional<Cook> optional = Optional.ofNullable(cook);
optional.ifPresent(System.out::println);
public static Cook getCook() {
Cook cook = new Cook();
return null;
}
ofNullable:当前确定一个对象可能会为null,你可以调用该方法避免空指针异常
Cook cook = getCook();
Optional<Cook> optional = Optional.ofNullable(cook);
optional.ifPresent(System.out::println);
public static Cook getCook() {
Cook cook = new Cook();
return null;
}
of:当前确定一个对象不为不为null可以使用of方法返回一个非空的Optional对象
Optional.of(cook).ifPresent(System.out::println);
public static Cook getCook() {
Cook cook = new Cook();
return cook;
}
2.4.3 Optional数据安全检查
get:如果 this Optional中存在值,则返回该值,否则抛出 NoSuchElementException,不推荐使用方法获取值,推荐使用orElse
Optional<Cook> optional = getCook();
Cook cook = optional.get();
System.out.println(cook);
public static Optional<Cook> getCook() {
Cook cook = new Cook();
return Optional.ofNullable(null);
}
orElse:如果存在,则返回值,否则调用 other 并返回该调用的结果
Optional<Cook> optional = getCook();
Cook cook1 = optional.orElseGet(new Supplier<Cook>() {
@Override
public Cook get() {
// 为空则返回一个默认值
return new Cook();
}
});
System.out.println(cook1);
public static Optional<Cook> getCook() {
Cook cook = new Cook();
return Optional.ofNullable(cook);
}
orElseThrow: 返回包含的值(如果存在),否则就抛出自定义的异常。
Optional<Cook> optional = getCook();
Cook cook2 = optional.orElseThrow(new Supplier<Throwable>() {
@Override
public Throwable get() {
return new RuntimeException("结果为空");
}
});
System.out.println(cook2);
public static Optional<Cook> getCook() {
Cook cook = new Cook();
return Optional.ofNullable(null);
}
2.4.4 Optional数据过滤
filter:当我们使用filter对数据进行过滤时,如果有数据但是不符合判断规则,它将返回一个新的无数据Optional
public static void main(String[] args) {
Optional<Cook> optional = getCook();
Optional<Cook> optional1 = optional.filter(new Predicate<Cook>() {
@Override
public boolean test(Cook cook) {
return cook.getWorkingYear() > 3;
}
});
System.out.println(optional1.get());
}
public static Optional<Cook> getCook() {
Cook cook = new Cook(22L ,"刘厨", 2, "大厨师", null);
return Optional.ofNullable(cook);
}
2.4.5 Optional判断
**ifPresent:**如果存在值,则使用该值调用指定的使用者,否则不执行任何操作
public static void main(String[] args) {
Optional<Cook> optional = getCook();
optional
.filter(cook -> cook.getWorkingYear() > 1)
.ifPresent(System.out::println);
}
public static Optional<Cook> getCook() {
Cook cook = new Cook(22L ,"刘厨", 2, "大厨师", null);
return Optional.ofNullable(cook);
}
isPresent: 如果存在值,则返回 true ,否则 false
public static void main(String[] args) {
Optional<Cook> optional = getCook();
if (optional.isPresent()) {
System.out.println(optional.get());
}
}
public static Optional<Cook> getCook() {
Cook cook = new Cook(22L ,"刘厨", 2, "大厨师", null);
return Optional.ofNullable(cook);
}
2.4.6 数据转换
map:可以将数据进行转换,转换为Optional 类型的包装数据,保证了使用安全
public static void main(String[] args) {
Optional<Cook> optional = getCook();
Optional<List<Food>> foods = optional.map(cook -> cook.getFoods());
foods.ifPresent(foods1 -> {
System.out.println(foods1.get(0));
});
}
public static Optional<Cook> getCook() {
Cook cook = new Cook(22L ,"刘厨", 2, "大厨师", null);
List<Food> foods1 = new ArrayList<>();
foods1.add(new Food(1,"宫保鸡丁" , "川菜", 97, "川菜大师"));
cook.setFoods(foods1);
return Optional.ofNullable(cook);
}
2.5 函数式接口
2.5.1 概述
当接口中有且只有一个抽象方法的接口 我们称这个接口为函数式的接口,在JDK中函数式接口都会被标注一个@FunctionalInterface注解修饰
示例:
@FunctionalInterface //JDK自带 会有这个注解,没有这个注解但是接口中仅有一个抽象方法也是函数式接口
public interface FunIntefaceDemo {
void funTest();
}
2.5.2 常见函数式接口
在JDK 8 的那种jar包rt.jar 中 java.util.function中为我们提供了很多函数式接口
Consumer : 这是一个消费接口,T为方法的参数类型,没有返回值
Consumer<Cook> consumer = o -> System.out.println("输出:"+o);
consumer.accept(new Cook(100L, "ceshi", null, null, null));
**Function<T,R>**: 这是一个对抽象方法传入的参数进行计算或转换返回对应结果, 为方法的输入参数类型 –为方法的返回值类型
特殊:对于BiFunction 的函数式接口,可以传入两个参数进行计算或者转换返回对应指定类型结果
Function<Cook, Integer> function = new Function<Cook, Integer>() {
@Override
public Integer apply(Cook o) {
return o.getWorkingYear() + 100;
}
};
System.out.println(function.apply(new Cook(100L, "ceshi", 19, null, null)));
**Predicate **: 判断接口,T为方法的参数类型,返回值为Boolean类型
Predicate<Integer> predicate = new Predicate<Integer>() {
@Override
public boolean test(Integer o) {
return o > 40;
}
};
System.out.println(predicate.test(60));
**Supplier **: 生产接口,只有一个get方法,返回一个指定类型数据T, T为返回值类型
Supplier<int[]> supplier = () -> {
int[] ints = new int[]{1, 2, 3};
return ints;
};
System.out.println(Arrays.toString(supplier.get()));
2.5.2 函数式接口常用默认方法
Predicate 方法
test:根据给定参数进行条件判断,满足返回true,不满足返回false。
// 判断传入的数值是否大于40
Predicate<Integer> predicate = new Predicate<Integer>() {
@Override
public boolean test(Integer o) {
return o > 40;
}
};
System.out.println(predicate.test(60));
and:可以在filter后面紧跟着and方法,多条件判断
List<Cook> cooks = DataUtil.getCooks();
cooks.stream()
.filter(((Predicate<Cook>) cook -> cook.getWorkingYear() > 1).
and(cook -> cook.getName().contains("王"))
).forEach(System.out::println);
negate:返回一个谓词,该谓词表示此谓词的逻辑否定,底层是return !test(T t)
// 获取工龄不大于3 的厨师
List<Cook> cooks = DataUtil.getCooks();
cooks.stream()
.filter(((Predicate<Cook>) cook -> cook.getWorkingYear() > 3).
negate()// 取 反操作
).forEach(System.out::println);
or
// 获取工龄大于1 或者 姓王的厨师
List<Cook> cooks = DataUtil.getCooks();
cooks.stream()
.filter(((Predicate<Cook>) cook -> cook.getWorkingYear() > 1).
or(cook -> cook.getName().contains("王"))
).forEach(System.out::println);
isEqual:返回一个谓词,该谓词实际调用的是 Objects.equals(Object, Object)两个参数是否相等,配合test方法使用判断两个对象是否相等
Predicate<Object> hello = Predicate.isEqual("hello");
System.out.println(hello.test("hello"));
2.6 方法引用
概念:
方法引用是lambda表达式的一种简化写法,它也是一种语法糖。
什么时候使用方法引用:
如果lambda表达式的方法体中只调用了一个方法,并且调用的方法和函数式接口中定义的抽象方法的参数列表和返回值都一致,就可以使用方法引用进行简化。
2.6.1 基本格式
类名或对象名:: 方法名
2.6.2 语法详解
引用类的静态方法:我们在重写方法的时候,方法体只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们要把重写的抽象方法所有的参数按照顺序传入这个静态方法中,这个时候我们就可以引用类的静态方法。
格式
类名::方法名
//示例 将工龄转换为字符串
List<Cook> cooks = DataUtil.getCooks();
Stream<String> stringStream = cooks.stream().map(cook -> cook.getWorkingYear())
.map(String::valueOf);
引用对象的实例方法:我们在重写方法的时候,方法体只有一行代码,并且这行代码是调用了某个对象成员方法,并且我们要把重写的抽象方法所有的参数按照顺序传入这个成员方法中,这个时候我们引用对象的实例方法。
格式
对象名::实例方法
//实例 拼接所有厨师的姓名
List<Cook> cooks = DataUtil.getCooks();
StringBuilder stringBuilder = new StringBuilder();
cooks.stream().map(cook -> cook.getName())
.forEach(stringBuilder::append);
System.out.println(stringBuilder.toString());
引用类的实例方法:如果我们在重写方法时 ,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们要把重写的抽象方法中剩余的所有的参数按顺序传入到这个成员方法中,这个时候我们就可以引用类的实例方法。
格式
类名::实例方法
interface UseString {
String user(String str, int start, int length);
}
public static String subAuthorName(String str, UseString useString) {
int start = 0 ;
int length = 1;
return useString.user(str, start, length);
}
public static void main(String[] args) {
// String s = subAuthorName("学习测试dsadaasd", new UseString() {
// @Override
// public String user(String str, int start, int length) {
// return str.substring(start, length);
// }
// });
// 简写方式
String s = subAuthorName("学习测试dsadaasd", String::substring);
System.out.println(s);
}
构造器引用:如果方法体中的代码只有一行并且这行代码时调用了某个类的构造器方法,并且我们把要重写的抽象方法中所有的参数按照顺序都传入到这个构造方法中,这个时候我们就可以使用引用构造器。
类名::new
//实例给所有厨师的名字加上 study 字符串
List<Cook> cooks = DataUtil.getCooks();
cooks.stream().map(cook -> cook.getName())
.map(new Function<String, StringBuilder>() {
@Override
public StringBuilder apply(String s) {
return new StringBuilder(s);
}
}).map(stringBuilder -> stringBuilder.append("study").toString())
.forEach(System.out::println);
2.7 并行流
并行流是stream通过多线程对大数据量的元素进行处理,可以使用并行流提高操作效率,并行流会分配个多个线程分别去操作元素,stram 为我们提供的很多api去支持并发编程下使用stream
示例:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer integer = stream.parallel()
.peek(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer + Thread.currentThread().getName());
}
}).filter(num -> num > 3)
.reduce(Integer::sum).get();
System.out.println(integer);
也可以通过集合的parallelStream 方法生成流然后操作元素
List<Cook> cooks = DataUtil.getCooks();
cooks.parallelStream()
.map(Cook::getWorkingYear)
.map(age -> age+10)
.filter(age -> age > 10)
.map(age -> age -2)
.forEach(System.out::println);