Lambda表达式也可以称为闭包,它是推动Java8发布的最重要新特性,lambda允许把函数作为一个方法参数传递给方法。
在Java8之前,如果我们新创建一个线程对象,需要使用匿名内部类传递我们要执行的任务,在Java8我们可以使用lambda简化这种操;
public static void main(String[] args) { // 匿名内部类 new Thread(new Runnable() { @Override public void run() { System.out.println("使用匿名内部类1"); } }).start(); // lambda表达式 new Thread(() -> { System.out.println("使用匿名内部类1"); System.out.println("使用匿名内部类2"); }).start(); }
什么是函数式接口呢?它必须满足如下条件:
- 函数式接口只能包含一个方法
- 可以包含多个默认方法default(默认方法相当于已经实现的方法,默认方法不会影响lambda表达式对接口方法的实现)
- Object类下的方法不计算在内,例如:toString()、equals()、hashCode()等方法。
限制接口类只有一个抽象方法:@FunctionalInterface注解
例如 Runnble
TreeMap 的Comparator
我们可以将lambda表达式的实现逻辑封装成一个方法,然后直接在lambda表达式函数中调用封装好的方法,称为方法引用,方法引用包括静态方法引用和动态方法引用
无返回值的
public class TestFunction { public static void main(String[] args) { // 静态方法引用 print(TestFunction::format1); // 普通方法引用 print(new TestFunction()::format2); } public static void format1(String name, int age) { System.out.printf("name: %s, age: %s%n", name, age); } public void format2(String name, int age) { System.out.printf("name: %s, age: %s%n", name, age); } public static void print(PrintFunction function) { function.print("王大", 23); } }
有返回值的
public class TestResultFunction { public static void main(String[] args) { // 静态方法引用 String nameAndAge1 = getNameAndAge("张三", 18, TestResultFunction::format1); // 普通方法引用 String nameAndAge2 = getNameAndAge("张三", 18, new TestResultFunction()::format2); // 使用函数调用 ResultFunction resultFunction = (name, age) -> { return name + ":" + age; }; String nameAndAge3 = resultFunction.getNameAndAge("张三", 18); System.out.println(nameAndAge1); System.out.println(nameAndAge2); System.out.println(nameAndAge3); } public static String format1(String name, int age) { return name + ":" + age; } public String format2(String name, int age) { return name + ":" + age; } public static String getNameAndAge(String name, Integer age, ResultFunction function) { return function.getNameAndAge(name, age); } }
因为我们不可能每次需要用到函数式接口就去定义一个接口,这样就是重复工作,所以java给我们按照需求的类型(消费型,供给型,函数型,断言型)提供了四个规范接口,以及他们的拓展变种接口;
1. 消费型接口
无返回值,只处理数据;例如 Stream.peek; forEach; Optional.ifPresent
Consumer<T> void accept(T t);
2. 供给型接口
没有参数,只返回数据,例如 Optional.orElseGet; Optional.orElseThrow;
Supplier<T> T get();
例如给缓存方法提供为空的值
public class CacheUtil { private static HashMap<String, Object> localCache = new ConcurrentHashMap<>(); public <T> T get(String key, RedisSupplier<T> redisSupplier) { Object value = localCache.get(key); if (Objects.isNull(value)) { T result = redisSupplier.get(); this.set(key, result, redisSupplier.getExpire(), redisSupplier.getTimeUnit()); return result; } return (T) value; } }
3. 函数型接口
提供参数加获取返回值,例如Stream.map; Optional.map; Map.compute; Stream.mapToInt; MybatisPlus.select; MybatisPlus.eq;
Function <T, R> R apply(T t);
4. 断言型接口
返回boolean类型值; 例如Stream.filter; Stream.anyMatch; Stream.allMatch;Optional.filter
Predicate<T> boolean test(T t);
分为两种,中间处理数据的方法,和结果集收集方法;这里只介绍Stream对象的方法
中间处理方法
函数 | 解释 |
---|---|
map | 数据处理,返回新的数据流 |
flatMap | 数据维度降级(合并列表数据) |
filter | 过滤数据 |
peek | 查看数据 |
distinct | 去重 |
sorted | 排序 |
limit | 数据截取,默认从第一个开始 |
skip | 跳过N个数据 |
终端收集方法
函数 | 解释 |
---|---|
forEach | 数据处理,返回新的数据流 |
toArray | 数据维度降级(合并列表数据) |
reduce | 过滤数据 |
collect | 转成集合 |
toArray | 转成数组 |
max/min/count | 最大值/最小值/计数 |
allMatch | 排序 |
anyMatch | 数据截取,默认从第一个开始 |
noneMatch | 跳过N个数据 |
findFirst | 找到第一个匹配的值 |
findAny | 找到所有匹配的值 |
map函数的作用是遍历Collection中的元素,生成一个新的Collection
public class TestStream { @Test public void testMap() { UserInfo userInfo1 = new UserInfo("张三",18,"18273416040"); UserInfo userInfo2 = new UserInfo("李四",20,"18273416040"); UserInfo userInfo3 = new UserInfo("王五",17,"18273416040"); List<UserInfo> userInfos = Arrays.asList(userInfo1, userInfo2, userInfo3); System.out.println("*************抽取对象集合中的某个字段 返回数组***************"); String[] usernameArrays = userInfos.stream().map(UserInfo::getUsername).toArray(String[]::new); System.out.println(Arrays.toString(usernameArrays)); System.out.println("*************抽取对象集合中的某个字段 返回集合***************"); List<String> usernameList = userInfos.stream().map(UserInfo::getUsername).collect(Collectors.toList()); System.out.println(usernameList); System.out.println("*************对象属性修改***************"); List<UserInfo> updateList = userInfos.stream().map(item -> { item.setAge(100); item.setMobile("123"); return item; }).collect(Collectors.toList()); System.out.println(updateList); System.out.println("*************对象集合转map集合***************"); List<Map<String, Object>> mapList = userInfos.stream().map(BeanUtil::beanToMap).collect(Collectors.toList()); System.out.println(mapList); System.out.println("*************map集合转对象集合***************"); List<UserInfo> mapToBeamList = mapList.stream().map(item -> { UserInfo userInfo = new UserInfo(); userInfo.setUsername(item.get("username").toString()); userInfo.setMobile(item.get("mobile").toString()); userInfo.setAge((Integer) item.get("age")); return userInfo; }).collect(Collectors.toList()); System.out.println(mapToBeamList); } }
flatmap用于集合的维度降级,也可以理解成把多个Stream流合成一个流;比如多维数组,集合中的元素中包含集合;
@Test public void testFlatMap() { UserInfo userInfo1 = new UserInfo("张三", 18, "18273416040"); UserInfo userInfo2 = new UserInfo("李四", 20, "18273416040"); List<String> addressList1 = new ArrayList<>(); addressList1.add("北京市海淀区"); addressList1.add("广州市天河区"); userInfo1.setAddress(addressList1); List<String> addressList2 = new ArrayList<>(); addressList2.add("广州市天河区"); addressList2.add("广州市海珠区"); userInfo2.setAddress(addressList2); List<UserInfo> userInfos = Arrays.asList(userInfo1, userInfo2); System.out.println(userInfos); List<List<String>> collect1 = userInfos.stream().map(UserInfo::getAddress).collect(Collectors.toList()); // 也可以distinct Set<String> collect2 = userInfos.stream().map(UserInfo::getAddress).flatMap(Collection::stream). collect(Collectors.toSet()); System.out.println(collect1); System.out.println(collect2); }
public class TestStream { @Test public void testPage() { String[] names = {"宋江", "卢俊义", "吴用", "公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进"}; String[] mobiles = {"10086", "10010"}; List<UserInfo> userInfos = new ArrayList<>(); for (int i = 0; i < 10; i++) { UserInfo userInfo = new UserInfo(names[i], i + 17, mobiles[i % 2]); userInfos.add(userInfo); } userInfos.forEach(System.out::println); System.out.println("\n**************************** filter ******************************\n"); // filter List<UserInfo> filters = userInfos.stream().filter(item -> item.getAge() > 24).collect(Collectors.toList()); System.out.println(filters); System.out.println("\n***************************** peek ***************************\n"); List<UserInfo> peeks1 = userInfos.stream().peek(System.out::println).collect(Collectors.toList()); // List<UserInfo> peeks2 = userInfos.stream().peek(item -> item.setAge(0)).collect(Collectors.toList()); // System.out.println(peeks2); System.out.println("\n***************************** distinct ***************************\n"); List<String> mobileList1 = userInfos.stream().map(UserInfo::getMobile).collect(Collectors.toList()); List<String> mobileList2 = userInfos.stream().map(UserInfo::getMobile).distinct().collect(Collectors.toList()); System.out.println(mobileList1); System.out.println(mobileList2); System.out.println("\n***************************** sorted ***************************\n"); // 自然顺序对流的元素进行排序。元素类必须实现Comparable接口 List<UserInfo> sorted1 = userInfos.stream().sorted().collect(Collectors.toList()); sorted1.forEach(System.out::println); // reverseOrder降序 naturalOrder升序 List<UserInfo> sorted2 = userInfos.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList()); List<UserInfo> sorted3 = userInfos.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList()); System.out.println("sorted2"); sorted2.forEach(System.out::println); System.out.println("sorted3"); sorted3.forEach(System.out::println); List<UserInfo> sorted4 = userInfos.stream().sorted(Comparator.comparing(UserInfo::getAge).reversed()).collect(Collectors.toList()); System.out.println("sorted4"); sorted4.forEach(System.out::println); List<UserInfo> sorted5 = userInfos.stream().sorted(Comparator.comparingInt(UserInfo::getAge).reversed()).collect(Collectors.toList()); List<UserInfo> sorted6 = userInfos.stream().sorted((e1, e2) -> { if (Objects.equals(e2.getAge(), e1.getAge())) { return e1.getUsername().compareTo(e2.getUsername()); } return Integer.compare(e2.getAge(), e1.getAge()); }).collect(Collectors.toList()); System.out.println("\n***************************** limit ***************************\n"); List<UserInfo> limit1 = userInfos.stream().limit(1).collect(Collectors.toList()); List<UserInfo> limit2 = userInfos.stream().sorted(Comparator.comparing(UserInfo::getAge).reversed()) .limit(1).collect(Collectors.toList()); System.out.println(limit1); System.out.println(limit2); } }
limit方法,它是用于限制流中元素的个数,即取前n个元素,返回新的流;
skip()方法用于跳过前面n个元素,然后再返回新的流;
public class TestStream { @Test public void testSkipAndLimit() { String[] names = {"宋江", "卢俊义", "吴用", "公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进"}; String[] mobiles = {"10086", "10010"}; List<UserInfo> userInfos = new ArrayList<>(); for (int i = 0; i < 10; i++) { UserInfo userInfo = new UserInfo(names[i], i + 17, mobiles[i % 2]); userInfos.add(userInfo); } List<UserInfo> skip = userInfos.stream().skip(2).collect(Collectors.toList()); List<UserInfo> limit = userInfos.stream().limit(2).collect(Collectors.toList()); System.out.println("skip"); System.out.println(skip); System.out.println("limit"); System.out.println(limit); System.out.println("\n*****************************skip加limit 实现分页**********************\n"); long pageSize = 3; long totalPage = 4; for (int pageIndex = 1; pageIndex <= totalPage; pageIndex++) { List<UserInfo> infoList = userInfos.stream().skip((pageIndex - 1) * pageSize).limit(pageSize).collect(Collectors.toList()); System.out.println(infoList); System.out.println(); } } }
Stream在组合使用才能发货最大的优势,如果仅仅只是单一的操作,其他方法也许更简单高效;
1 取交集 并集 差集
2 多集合取交集
3 分页拉取数据
并行流(Parallel Stream)利用所有可用CPU内核的优势,并并行处理任务。 如果任务数超过内核数,则其余任务将等待当前正在运行的任务完成。
可以通过 Runtime.getRuntime().availableProcessors()来获取当前计算机的CPU内核数量。
使用场景
Java 使用ForkJoinPool实现并行性,ForkJoinPool派生源流并提交执行;
下面的测试方法,cpu是AMD 5600G 情况下 未使用Parallel需要13秒,使用Parallel之后,2秒
public class ParallelStreamTest { @Test public void test1() { List<Integer> data = new ArrayList<>(); for (int i = 0; i < 100000; i++) { data.add(i); } Instant start = Instant.now(); long sum = data.stream() .map(i -> (int) Math.sqrt(i)) .map(ParallelStreamTest::performComputation) .reduce(0, Integer::sum); Instant end = Instant.now(); System.out.println(sum); System.out.printf("Time taken to complete:%s秒", Duration.between(start, end).getSeconds()); } @Test public void test2() { List<Integer> data = new ArrayList<>(); for (int i = 0; i < 100000; i++) { data.add(i); } Instant start = Instant.now(); long sum = data.stream().parallel() .map(i -> (int) Math.sqrt(i)) .map(ParallelStreamTest::performComputation) .reduce(0, Integer::sum); Instant end = Instant.now(); System.out.println(sum); System.out.printf("Time taken to complete:%s秒", Duration.between(start, end).getSeconds()); } public static int performComputation(int n) { int sum = 0; for (int i = 1; i < 100000; i++) { int a = (n / i); sum += a; } return sum; } }