【函数式编程】Lambda、Stream、Optional、方法引用、并行流

JDK1.8新特性

JDK1.8中的主要新特性包括:

  • Lambda表达式
  • Stream流
  • Optional
  • 函数式接口
  • 方法引用

这些高级特性由于自适应了并行流的技术,可以在不进行JUC并发编程优化的条件下在海量数据的场景下拥有较高的运行效率。

大幅度简化代码行数,在对于熟悉新特性的开发人员来说,具有更高的可读性。

函数式编程

这些新特性在一定程度上运用的是函数式编程的思想,其区别于面向对象编程的思想。

  • 面向对象:

    关注的更多的是对象,即哪个对象做的哪些事情

  • 函数式编程:

    关注的是函数本身,即这个函数所做的事情。

Lambda表达式

Lambda表达式是一种简单的定义方式,一个经典的例子是:

    public static void main(String[] args) {
        // 传统的线程定义方式
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("第一种方法,匿名内部类");
            }
        });
        t1.start();

        // 对线程的定义进行简化,当一个接口只有一个抽象方法需要被重写时,可以使用Lambda
        Thread t2 = new Thread(() -> {
            System.out.println("第二种方法,Lambda表达式");
        });
        t2.start();
    }

只有函数式接口才可以使用Lambda进行简化(函数式接口就是只有一个实现类的接口)

  • 使用IntBinaryOperator进行两数之和的运算:

        public static void main(String[] args) {
            System.out.println(
                // 匿名内部类的写法
                caculateNum(new IntBinaryOperator() {
                    @Override
                    public int applyAsInt(int left, int right) {
                        return left + right;
                    }
                })
            );
    
            System.out.println(
                // 我们点进去IntBinaryOperator类中,发现只有一个方法需要重写,故可以使用Lambda进行优化
                caculateNum((int left, int right) -> {
                    return left + right;
                })
            );
    
        }
    
        public static int caculateNum(IntBinaryOperator operator) {
            int a = 10;
            int b = 20;
            // 调用自己的方法进行运算,这个运算是需要自己处理的
            return operator.applyAsInt(a, b);
        }
    
  • 使用IntPredict进行奇偶数判断:

        public static void main(String[] args) {
            // 使用匿名内部类进行处理
            printNum(new IntPredicate() {
                @Override
                public boolean test(int value) {
                    return value % 2 == 0;
                }
            });
    
            // 由于IntPredicate只有一个方法需要实现,故我们可以使用Lambda表达式对其进行简化
            printNum((int value) -> {
                return value % 2 == 0;
            });
        }
    
        // IntPredicate类是一个用于对一个数进行判断的函数式接口,其只有一个抽象方法,可以用Lambda进行简化,其会返回一个Boolean型的变量
        private static void printNum(IntPredicate predicate) {
            int[] arr = {1, 2, 3, 4, 5, 6};
            for (int i : arr) {
                if (predicate.test(i)) {
                    System.out.println(i);
                }
            }
        }
    
  • 使用Function类来将变量转换

        public static void main(String[] args) {
            // 使用匿名内部类的写法
            System.out.println(
                typeConvert(
                    new Function<String, Integer>() {
                        @Override
                        public Integer apply(String s) {
                            return Integer.valueOf(s);
                        }
                    }
                )
            );
    
            // 使用Lambda的写法
            System.out.println(
                typeConvert((String s) -> {
                    return Integer.valueOf(s);
                })
            );
        }
    
        /**
         *  R 是一种泛型的特有写法,其作用是为一个方法赋予一个不确定的返回值类型,这个返回值类型由调用者进行具体确定
         * Funtion是一个函数式接口,其作用是对于传入的T类型的变量进行操作,并返回一个R类型的变量
         */
        private static <R> R typeConvert(Function<String, R> function) {
            String str = "12345";
            R result = function.apply(str);
            return result;
        }
    

Stream流

Stream流由创建流、操作流、终结流三个方法组成,其中若没有终结流的方法,操作流的操作也不会执行(ForEach就是一种典型的终结流的操作)

Stream流的创建方式

  • 单列集合(List、Set)

            // 获取一个集合
            List<Author> authors = getAuthors();
    
            // 通过Collection类的.stream()方法来创建一个Stream对象
            Stream<Author> stream = authors.stream();
    
  • 数组

            // 数组获取stream流的方式,使用Arrays工具类中的方法
            Integer[] arr = {1, 3, 5, 7, 8, 4, 12};
            Stream<Integer> stream1 = Arrays.stream(arr);
            // 或使用Stream接口的.of方法
            Stream<Integer> stream2 = Stream.of(arr);
    
  • map

    map对象获取Stream流的方式为:将map转换为一个个的entry,再将entry们视为一个个的对象放入一个set中,使用set进行stream流转换

            // 将map转换为set结合
            Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
          Stream<Map.Entry<String, Integer>> stream3 = entrySet.stream();
    

Filter过滤器

Filter用来过滤流中的元素,将需要的元素留在流中,将不需要的元素剔除,其是一个函数式接口,其中只有一个Predicate方法需要重写

        List<Author> list = getAuthors();
        list.stream()       // 获取流
                .filter(new Predicate<Author>() {       // 过滤器,只有被其重写的方法返回结果为true的元素才可以被留在流中
                    @Override
                    public boolean test(Author author) {
                        return author.getName().length() > 1;   // 获取到名字长度大于一的作家
                    }
                })
                .forEach(new Consumer<Author>() {       // 终结操作的一种,只有有终结操作,流才会执行
                    @Override
                    public void accept(Author author) {
                        System.out.println(author);
                    }
                });

Lambda表达式优化:

        list.stream()
                .filter(author -> author.getName().length() > 1)
                .forEach(author -> System.out.println(author));

Map转换器

Map方法是一个转换器,可以将我们传入的参数进行一系列的计算或类型转换,将其返回值作为流的内容继续操作,用于筛选自己需要的数据以及进行简单的计算操作

        // map也是一个函数式接口,其泛型第一个是传入的参数类型,第二个是要转换成的参数类型
        list.stream()
                .map(new Function<Author, String>() {
                    @Override
                    public String apply(Author author) {
                        return "作者是:" + author.getName();
                    }
                })
                .forEach(name -> System.out.println(name));

        // Lambda表达式简化:
        list.stream()
                .map(author -> "作者为:" + author.getName())
                .forEach(name -> System.out.println(name));

Distinct去重器(直接用)

Sorted排序器

sorted方法用于将流进行排序,一般在最后一步进行,其有两个方法,空参方法要求流对象必须实现Comparable接口(注意不是Comparator接口),会调用它的默认排序器,有参方法是一个函数式接口,其可以进行自定义排序。

        List<Author> authors = getAuthors();
        authors.stream()
                .distinct()
                .sorted(new Comparator<Author>() {      // 有参的写法,Author类可以不实现Comparator接口
                    @Override
                    public int compare(Author o1, Author o2) {
                        /**
                         * 根据权重理解排序操作,若结果为正,则前者权重大,若为负,后者权重大
                         * 其按照权重由小到大排序,故在 o1 - o2 中,权重越大,越往后排
                         * 在 o2 - o1 中,权重越大,越往前排,所以
                         * 正写升序,逆写降序
                         */
                        return o1.getAge() - o2.getAge();
                    }
                })
                .forEach(author -> System.out.println(author.getAge()));
        
        // Lambda简化
        authors.stream()
                .distinct()
                .sorted((o1, o2) -> o2.getAge() - o1.getAge())
                .forEach(author -> System.out.println(author.getAge()));

空参写法:必须Author类实现Comparable接口并重写CompareTo方法

        // 空参,调用Author的默认写法
        authors.stream()
                .distinct()
                .sorted()
                .forEach(author -> System.out.println(author.getName()));

Author类:

public class Author implements Comparable<Author> {
    @Override
    public int compareTo(Author o) {
        return o.getAge() - this.getAge();
    }

limit控制器

limit(Int)用于限制流中内容输出的数量,流中输出前几个元素

        authors.stream()
                .distinct()
                .sorted()
                .limit(2)   // 只输出前两个元素
                .forEach(author -> System.out.println(author.getName()));

Skip控制器

skip(int)也用于限制流中内容输出的数量,用于跳过前几个数据

        authors.stream()
                .distinct()
                .sorted()
                .skip(1)    //跳过前一个元素
                .forEach(author -> System.out.println(author.getName()));

flatMap转换器

flatMap()用于将流中的对象转换为新的多个对象(map只能转换为一个)

        List authors = getAuthors();
        authors.stream()
                // .flatMap方法的泛型为传入的元素以及传出元素的流
                .flatMap(new Function>() {
                    @Override
                    public Stream apply(Author author) {
                        return author.getBooks().stream();
                    }
                })
                .distinct()
                .forEach(item -> System.out.println(item));

        // Lambda的写法
        authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .forEach(item -> System.out.println(item));

例如上述例子中,使用flatMap将author转换为了author中的Books

终结操作

ForEach

forEach()是最基础的终结操作,其会对每一个数据进行处理。

count

count操作以返回流中元素的个数

        List<Author> authors = getAuthors();
        long a = authors.stream()
                .count();
        System.out.println(a);

min & max

min、max用于取得流的最大值与最小值,其要实现Comparator排序规则,同时其返回值为Optional类型,我们要取出其中的内容就需要使用到Optional中的get方法。

        List<Author> authors = getAuthors();
        Optional<Integer> max = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .distinct()
                .map(book -> book.getScore())
                .max((score1, score2) -> score1 - score2);
        System.out.println(max.get());

collect

collect终结操作是一种比较重要的终结操作,它可以把我们的流转换成我们需要的东西,例如List、Set、Map。

将流转换成List:

使用Collectors.toList()

        List<Author> authors = getAuthors();
        List<String> collect = authors.stream()
                .map(author -> author.getName())
                .distinct()
                .collect(Collectors.toList());
        System.out.println(collect);

将流转换成Set:

使用Collectors.toSe()

        List<Author> authors1 = getAuthors();
        authors1.stream()
                .map(author -> author.getName())
                .distinct()
                .collect(Collectors.toSet());

将流转换成Map:

使用Collectors.toMap(),其中传入两个函数式接口,第一个是键,第二个是值

        List<Author> authors2 = getAuthors();
        Map<String, List<Book>> collect1 = authors2.stream()
                .distinct()
                .collect(Collectors.toMap((author -> author.getName()), (author -> author.getBooks())));
        System.out.println(collect1);

anyMatch\allMatch\noneMatch

anyMatch用来判断流是否满足一个条件,返回一个boolean型变量:

        List<Author> authors = getAuthors();

        // 若流中有任意一个元素匹配这种情况,则返回true,否则返回false
        boolean flag = authors.stream()
                .anyMatch(author -> author.getAge() > 88);
        System.out.println(flag);

allMatch则要求流中的所有元素都符合情况

noneMatch要求所有的元素都不符合情况

findAny

findAny会终结流操作并获取流中的任意一个元素(以Optional类型获取)

        // 输出任意一个年龄大于2的作家的名字
        Optional<Author> rs = authors.stream()
                .filter(author -> author.getAge() > 2)
                .findAny();

        rs.ifPresent(item -> System.out.println(item.getName()));

findFirst

findFirst获取流中的第一个元素(以Optional类型获取)

        List<Author> authors = getAuthors();

        // 获取年龄最小的元素的姓名
        Optional<Author> rs = authors.stream()
                .sorted((o1, o2) -> o1.getAge() - o2.getAge())
                .findFirst();
        rs.ifPresent(item -> System.out.println(item.getName()));

reduce +++++

reduce操作用于将流中的所有元素进行遍历计算,并得到一个结果:

例如:将所有的元素相加(注意,因为我们经常会使用对象作为流中的元素,故我们需要先使用map将对象元素转换为特定类型再进行操作(mapreduce结构))

        Integer rs = 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(rs);

另外,reduce操作也可以只使用一个参数的形式,这种形式会默认将第一个流元素作为第一个result进行操作,即没有默认初值的情况

        List<Author> authors = getAuthors();

        Optional<Integer> reduce = authors.stream()
                .distinct()
                .map(author -> author.getAge())
                .reduce((result, element) -> Math.max(result, element));
        reduce.ifPresent(item -> System.out.println(item));

注意:Stream流的三个特点

  • Stream流的惰性求值特点:如果没有终结操作,Stream流不会执行
  • Stream流是一次性的,创建出来之后使用一次就会销毁
  • Stream流不会影响原数据,可以进行很多拓展操作

Optional

Optional类是为了避免空指针异常而产生的一种便于操作的类,我们可以将对象封装成Optional<>类来避免空指针异常:

    public static void main(String[] args) {
        // 调用ifPresent方法来使用Optional类的对象
        test1().ifPresent(author -> System.out.println(author));
    }

    private static Optional<Author> test1() {
        Author author = new Author(1L, "红色小鸟", 23, "这是一只红色的小鸟", null);

        // 使用Optional的静态方法将对象转换成Optional类对应的对象
        return Optional.ofNullable(author);
    }

Optional内容的获取

可以使用.get()方法Optional类中的内容,但这种方式是不建议的,因为这样依旧会将空指针异常以另一种方式表现出来。

可以使用.orElseGet()方法,这个方法传入一个默认的对象,若Optional中内容为空,则会返回这个默认对象,若不为空,则返回对应的内容。

    public static void main(String[] args) {
        Author author = test1().orElseGet(() -> new Author(5L, "我是默认", 0, "我是默认", null));
        System.out.println(author);
    }

    private static Optional<Author> test1() {
        Author author = new Author(1L, "红色小鸟", 23, "这是一只红色的小鸟", null);
        
        return Optional.ofNullable(null);
    }

可以使用.orElseThrow()方法,该方法在Optional没有内容时会抛出自定义的异常,在有内容是回返回内容

    public static void main(String[] args) {
        Author author = null;
        try {
            author = test1().orElseThrow(new Supplier<Throwable>() {
                @Override
                public Throwable get() {
                    return new RuntimeException("数据为NULL");
                }
            });
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        // Lambda表达式的写法
        try{
            author = test1().orElseThrow(() -> new RuntimeException("Lamdba,数据为NULL"));
        }catch (Throwable e){
            throw new RuntimeException(e);
        }

        System.out.println(author);


    }

    private static Optional<Author> test1() {
        Author author = new Author(1L, "红色小鸟", 23, "这是一只红色的小鸟", null);

        return Optional.ofNullable(null);
    }

Optional中的过滤操作

Optional也可以对内容进行过滤,使用filter()方法

    public static void main(String[] args) {
        // 过滤出年龄大于25的人
        Optional<Author> author = test1().filter((authorFilter) -> authorFilter.getAge() > 25);
        author.ifPresent(authorRs -> System.out.println(authorRs));

    }

    private static Optional<Author> test1() {
        Author author = new Author(1L, "红色小鸟", 23, "这是一只红色的小鸟", null);

        return Optional.ofNullable(null);
    }
  • .isPresent():

    判断Optional中是否有内容,如果有会返回true,没有则会返回false

  • .map():

    将Optional中的内容转换为另一个内容:

            List<Author> authors = getAuthors();
            Optional<Author> author = Optional.ofNullable(authors.get(0));
    
            // 将作家转换成书籍的集合
            Optional<List<Book>> books = author.map(author1 -> author1.getBooks());
            books.ifPresent(bookss -> System.out.println(bookss));
    

函数式接口

函数式接口时一种典型的可以使用Lambda表达式进行优化的接口,这种接口为了方便我们使用,被定义出了一些典型的接口例如:

  • Consumer:

    对内容进行操作,返回值为void

  • Predicate:

    进行判断,返回true或false,用于过滤

  • Function:

    进行操作并返回一个自定义值

注意:我们可以通过搜索Consumer类所在的包来找到我们的函数式接口的定义位置来看她到底有什么用处

多个条件或多种操作

  • and

    and用于拼接多个操作,用于让若个操作都执行

            List<Author> authors = getAuthors();
    
            authors.stream()
                    .filter(new Predicate<Author>() {
                        @Override
                        public boolean test(Author author) {
                            return author.getAge() > 14;
                        }
                    }.and(new Predicate<Author>() {
                        @Override
                        public boolean test(Author author) {
                            return author.getName().length() > 1;
                        }
                    }))
                    .forEach(item -> System.out.println(item.getAge() + ":::" + item.getName()));
    
  • or

    or可以让多个判断只要满足一个

  • negate

    negate可以让判断去反

方法引用

方法引用是一种在Lambda基础上更高级的简化代码的技术,我们可以在方法引用的规则上更简洁的编写代码

对于只有一行代码的重写方法,可以考虑方法引用

类的静态方法

规则:重写的方法只有一行代码、这个方法调用了某个类的静态方法、所有的参数都按顺序传入到这个方法中,例:

        List<Author> authors = getAuthors();
        // Lambda表达式的写法
        authors.stream()
                .map(author -> author.getAge())
                .map(age -> String.valueOf(age))
                .forEach(item -> System.out.println(item));

        // 方法引用的写法
        authors.stream()
                .map(author -> author.getAge())
                .map(String :: valueOf)		// 前面写类名,后面写调用的方法,省略形参
                .forEach(item -> System.out.println(item));

对象的实例方法

规则:重写的方法只有一行代码,这个方法调用了某个对象的成员方法,所有参数都按顺序被传入

        List<Author> authors = getAuthors();

        StringBuilder sb = new StringBuilder();
        // Lambda表达式的写法
        authors.stream()
                .map(author -> author.getName())
                .forEach(name -> sb.append(name));

        // 方法引用的写法
        authors.stream()
                .map(author -> author.getName())
                .forEach(sb :: append);

类的实例方法

规则:只有一行代码、该代码调用了第一个形参的成员方法(author.getName()),且剩余的形参都按顺序传入

        List<Author> authors = getAuthors();

        // Lambda
        authors.stream()
                .map(author -> author.getName())
                .forEach(author -> System.out.println(author));

        // 方法引用
        authors.stream()
                .map(Author :: getName)
                .forEach(System.out :: println);

构造器引用

规则:只有一行代码、该代码调用了某个构造器、且所有形参按顺序传入

        List<Author> authors = getAuthors();

        // Lambda表达式
        authors.stream()
                .map(author -> author.getName())
                .map(name -> new StringBuilder())
                .map(sb -> sb.toString())
                .forEach(item -> System.out.println(item));
        
        // 方法引用
        authors.stream()
                .map(Author :: getName)
                .map(StringBuilder :: new)
                .map(StringBuilder :: toString)
                .forEach(System.out :: println);

基本数据类型优化

在之前的使用中,我们的数据类型自动拆装箱会有一定时间的浪费,例如:

        List<Author> authors = getAuthors();

        /**
         * 未优化版本,因为计算每次都需要转换成int类型,而原本是Integer类型,且每次还要转回去,所以浪费了很多时间
         */
        authors.stream()
                .map(author -> author.getAge())
                .map(age -> age + 10)
                .filter(age -> age > 24)
                .map(age -> age - 2)
                .forEach(System.out :: println);

        /**
         * 优化版本
         * 使用mapToInt让后面的操作都避免了自动拆箱和自动装箱
         * 大幅度提高了效率
         */
        authors.stream()
                .mapToInt(Author :: getAge)
                .map(age -> age + 10)
                .filter(age -> age > 24)
                .map(age -> age - 2)
                .forEach(System.out :: println);

并行流

我们通常所使用的stream为串行流、即所有的操作都在同一线程中执行

而在数据量极大时、串行流的效率就会很低,这个时候就要用到并行流来提高效率

        List<Author> authors = getAuthors();

        authors.stream().parallel()			// 或.parallelStream()
                .peek(author -> System.out.println(author.getId() + "   " + Thread.currentThread().getName()))
                .map(Author :: getName)
                .forEach(System.out :: println);

你可能感兴趣的:(Other,java,jvm,开发语言)