Java8 Stream流使用

2022/7/5 1:20:11

本文主要是介绍Java8 Stream流使用,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Java8 Stream 流式编程

一.Lambda表达式

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();
}

什么是函数式接口呢?它必须满足如下条件:

  1. 函数式接口只能包含一个方法
  2. 可以包含多个默认方法default(默认方法相当于已经实现的方法,默认方法不会影响lambda表达式对接口方法的实现)
  3. Object类下的方法不计算在内,例如:toString()、equals()、hashCode()等方法。

限制接口类只有一个抽象方法:@FunctionalInterface注解

例如 Runnble

image

TreeMap 的Comparator

image

二. 方法引用

我们可以将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流提供的常用函数

分为两种,中间处理数据的方法,和结果集收集方法;这里只介绍Stream对象的方法

中间处理方法

函数 解释
map 数据处理,返回新的数据流
flatMap 数据维度降级(合并列表数据)
filter 过滤数据
peek 查看数据
distinct 去重
sorted 排序
limit 数据截取,默认从第一个开始
skip 跳过N个数据

终端收集方法

函数 解释
forEach 数据处理,返回新的数据流
toArray 数据维度降级(合并列表数据)
reduce 过滤数据
collect 转成集合
toArray 转成数组
max/min/count 最大值/最小值/计数
allMatch 排序
anyMatch 数据截取,默认从第一个开始
noneMatch 跳过N个数据
findFirst 找到第一个匹配的值
findAny 找到所有匹配的值
  • forEach
  • toArray
  • reduce
  • collect
  • max/min/count
  • allMatch/anyMatch/noneMatch
  • allMatch/anyMatch/noneMatch

中间处理方法使用

map的使用

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使用

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);
}

filter peek distinct sorted 使用

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);

    }
}

skip limit

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

并行流(Parallel Stream)利用所有可用CPU内核的优势,并并行处理任务。 如果任务数超过内核数,则其余任务将等待当前正在运行的任务完成。

可以通过 Runtime.getRuntime().availableProcessors()来获取当前计算机的CPU内核数量。

使用场景

Java 使用ForkJoinPool实现并行性,ForkJoinPool派生源流并提交执行;

  • 源数据流应该是可拆分的。例如:ArrayList的数据
  • 在处理问题的时候确实遇到性能问题,否则请不要为了并行而并行。
  • 需要确保线程之间的所有共享资源都是正确同步,否则可能会产生数据不一致问题。

下面的测试方法,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;
    }
}


这篇关于Java8 Stream流使用的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程