zoukankan      html  css  js  c++  java
  • java函数式编程

    2020/12/08

    Lambda表达式

    函数式编程

    • 函数当成基本运算单元
    • 函数可以当参数
    • 函数可以接收参数
    • 函数可以返回参数

    Lambda表达式

    • 简化语法
    • JDK >= 1.8
    • 只使用于函数式接口

    函数式接口

    • 接口只有一个抽象方法

    • 不包括object那些public方法

      image-20201208152153370

      interface并没有继承Object类,它只是隐式的声明了Object类里的所有的public方法。并且在接口的实现中可以不用override这些方法,因为调用这些隐式方法的时候默认会直接调用到Object类里的方法。Object类里的protected方法(finalize)不会被隐式声明。

      引用自:java接口是否也继承于Object类?

    所以下面2个接口都是函数式接口:

    image-20201208150539017

    将匿名类简化成Lambda表达式

    String[] words = "Improving code with lambda expressions in java".split(" ");
    Arrays.sort(words, new Comparator<String>() {
        @Override
        public int compare(String str1, String str2) {
            // 全部小写后再比较
            return str1.toLowerCase().compareTo(str2.toLowerCase());
        }
    });
    

    简化:

    Arrays.sort(words, (s1, s2) -> {
       return s1.toLowerCase().compareTo(s2.toLowerCase());
    });
    

    再简化:

    Arrays.sort(words, (s1, s2) -> s1.toLowerCase().compareTo(s2.toLowerCase()));
    System.out.println(Arrays.toString(words));
    

    s1,s2的类型编译器会自动判断

    方法引用

    非构造方法的引用

    定义一个Student类用于测试

    public class Student {
        private String name;
        private int score;
    
        public Student() {
        }
    
        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getScore() {
            return score;
        }
    
        public void setScore(int score) {
            this.score = score;
        }
    
        public static int compareStudentByScore(Student student1, Student student2) {
            return student1.getScore() - student2.getScore();
        }
    
        public static int compareStudentByName(Student student1, Student student2) {
            return student1.getName().compareToIgnoreCase(student2.getName());
        }
    

    使用之前提到的Lambda表达式进行排序

    Student student1 = new Student("zhangsan", 60);
    Student student2 = new Student("lisi", 70);
    Student student3 = new Student("wangwu", 80);
    Student student4 = new Student("zhaoliu", 90);
    List<Student> students = Arrays.asList(student1, student2, student3, student4);
    students.sort((o1, o2) -> o1.getScore() - o2.getScore());
    students.forEach(student -> System.out.println(student.getScore()));
    

    使用类名::静态方法进行排序

    先理解方法签名的概念

    如果两个方法参数类型和返回值类型相同,那么这两个方法签名相同

    image-20201208163737783

    例如上图的ignoreCasecompare方法签名相同

    所以之前传入的Lambda表达式,或者说是函数式接口,可以使用与该接口中那个抽象方法签名相同的方法替换,程序会自动将传入的方法转换成对应的函数式接口

    public static int compareStudentByScore(Student student1, Student student2) {
        return student1.getScore() - student2.getScore();
    }
    
    students.sort(Student::compareStudentByScore);
    

    使用实例::实例方法进行排序

    由于签名相同的判断条件不包括static,因此也可以使用类名::实例方法来替换原来的函数式接口(前提也是签名相同)

    创建一个Student的排序器

    public class StudentComparator {
        public int compareStudentByScore(Student student1, Student student2) {
            return student2.getScore() - student1.getScore();
        }
    }
    

    将方法传入

    StudentComparator comparator = new StudentComparator();
    students.sort(comparator::compareStudentByScore);	
    

    使用类名::实例方法进行排序

    先看一下之前的静态方法

    public static int compareStudentByScore(Student student1, Student student2) {
        return student1.getScore() - student2.getScore();
    }
    

    虽然这个方法在语法上没有任何问题,可以作为一个工具正常使用,但是有没有觉得其在设计上是不合适的或者是错误的。这样的方法定义放在任何一个类中都可以正常使用,而不只是从属于Student这个类,那如果要定义一个只能从属于Student类的比较方法下面这个实例方法更合适一些

    public int compareByScore(Student student) {
        return this.getScore() - student.getScore();
    }
    
    students.sort(Student::compareByScore);
    

    那么该方法的签名和Comparator接口中的compare方法不同,为何可以替换?

    因为类中每个方法都会隐式传入this参数,所以实际调用的是

    public int compareByScore(Student this, Student student) {
        return this.getScore() - student.getScore();
    }
    

    这样签名就相同了

    构造方法的引用

    使用下方接口进行测试

    @FunctionalInterface
    public interface Supplier<T> {
    
        /**
         * Gets a result.
         *
         * @return a result
         */
        T get();
    }
    

    使用Lambda表达式

    Supplier<Student> supplier = () -> new Student();
    Student student = supplier.get();
    

    是用构造方法的引用(前提要有无参构造

    Supplier<Student> supplier = Student::new;
    Student student = supplier.get();
    

    参考:Java8-8-方法引用详解

    stream

    • 可以“存储”有限个或无限个元素
    • 一个stream可以转换成另一个stream
    • 参数都是Lambda表达式
    • 不会存储元素
    • 惰性计算:在需要获取结果时才进行计算
    image-20201208202325803

    创建stream

    // 1. 通过Collections系列集合提供的stream() 或 parallelStream()
    List<String> list = new ArrayList<>();
    Stream<String> stream01 = list.stream();
    
    // 2. 通过Arrays中的静态方法stream()获取数组流
    String[] strList = new String[10];
    Stream<String> stream02 = Arrays.stream(strList);
    
    // 3. 通过Stream类中的静态方法
    Stream<String> stream03 = Stream.of(strList);
    
    // 4. 迭代
    Stream<Integer> stream04 = Stream.iterate(0, (x) -> (x + 1));
    stream04.limit(10).forEach(System.out::println);
    
    // 5. 生成
    Stream<Double> stream05 = Stream.generate(() -> Math.random());
    stream05.limit(10).forEach(System.out::println);
    

    参考:Java | Stream

    map

    • 将一个stream映射成另一个stream

    • map的参数是Function接口

      <R> Stream<R> map(Function<? super T, ? extends R> mapper);
      

      Function接口

      @FunctionalInterface
      public interface Function<T, R> {
      	// 将T类型转换为R类型
          R apply(T t);
      }
      

    示例一

    String[] words = "Stream API supports functional-style operations".split(" ");
    Stream<String> stream = Arrays.stream(words);
    stream.map(String::toUpperCase).forEach(System.out::println);
    

    示例二(将String转为Student)

    String[] students = {"小明, 1", "张三, 2", "李四, 3", "王五, 4", "小刘, 5", "小王, 6"};
    Stream<String> stream = Arrays.stream(students);
    Stream<Student> studentStream = stream.map(s -> {
        int index = s.indexOf(',');
        String name = s.substring(0, index);
        int id = Integer.parseInt(s.substring(index + 2));
        return new Student(id, name);
    });
    studentStream.forEach(System.out::println);
    

    参考:Java | Stream

    filter

    • 用来过滤stream

    • 传入参数为Predicate接口

      Stream<T> filter(Predicate<? super T> predicate); 
      

      Predicate接口

      @FunctionalInterface
      public interface Predicate<T> {
      	// 传入参数T,判断是否满足条件
          boolean test(T t);
      }
      

    示例一

    class NaturalSupplier implements Supplier<Long> {
        private long x;
        @Override
        public Long get() {
            x = x + 1;
            return x;
        }
    }
    
    Stream<Long> natural = Stream.generate(new NaturalSupplier());
    // 奇数
    Stream<Long> odd = natural.filter(n -> n % 2 == 1);
    odd.limit(15).forEach(System.out::println);
    

    示例二

    String[] words = {"Java", " Python ", null, "\n\n", " Ruby "};
    // Java中的trim()函数去除了字符串前后两端的所有包括空格、换行、回车
    Arrays.stream(words).
        filter(s -> s != null && !s.trim().isEmpty()).
        map(String::trim).
        forEach(System.out::println);
    

    参考:Java | Stream

    reduce

    • 用来聚合stream

    • 传入参数为BinaryOperator接口

      Optional<T> reduce(BinaryOperator<T> accumulator);
      

      BinaryOperator接口

      @FunctionalInterface
      public interface BinaryOperator<T> extends BiFunction<T,T,T> {
          // 两个输入,一个输出,并且类型相同
          T apply(T t, T u);
      }
      

    示例一

    Optional<Integer> reduce = Stream.of(1, 2, 6, 8, 9).reduce((acc, n) -> acc + n);
    System.out.println(reduce.get());
    
    image-20201208223023351

    示例二

    Integer reduce = Stream.of(1, 2, 6, 8, 9).reduce(1000, (acc, n) -> acc * n);
    System.out.println(reduce);
    
    image-20201208223255162

    示例三

    String[] students = {"小明, 1", "张三, 2", "李四, 3", "王五, 4", "小刘, 5", "小王, 6"};
    Stream<String> stream = Arrays.stream(students);
    Optional<String> reduce = stream.reduce((acc, s) -> acc + "~" + s);
    System.out.println(reduce.get());
    

    其它方法

    排序

    Stream<T> sorted()	//按元素默认大小排序(必须实现comparable接口)
    Stream<T> sorted(Comparator<? super T> cp)	//按指定comparator比较的结果排序
    

    去重

    stream<T> distinct()	// 返回去除重复元素的新Stream
    // [1, 2, 5, 2, 6, 5, 7] => [ 1, 2, 5, 6, 7]
    

    截取

    Stream<T> limit(long)	// 截取当前Stream的最多前N个元素
    stream<T> skip(long)	// 跳过当前stream的前N个元素
    

    合并

    合并stream

    stream<Integer> s = Stream.concat (
        stream.of(1, 2, 3),
        stream.of (4, 5, 6)
    };
    // 1, 2, 3, 4, 5, 6
    

    合并集合成一个stream

    Stream<List<Integer>> s = Stream.of(
        Arrays. asList (1, 2, 3),
        Arrays.asList(4, 5, 6),
        Arrays.asList (7, 8, 9));
    // 转换为stream<Integer>
    stream<Integer> i = s.flatMap(list -> list.stream()) ;
    

    flatMap把元素映射成stream,然后合并成一个stream

    image-20201209084532130

    stream并行处理

    stream的操作是单线程的,可以手动设置成可多线程处理

    stream<String> s = ...
    string[] result = s.parallel() // 变成一个可以并行处理的stream
    				   .sorted() // 可以进行并行排序
    				   .toArray(String[]::new);
    

    聚合方法

    Optional<T> reduce(BinaryOperator<T> bo)
    long count() // 元素个数
    
    T max(Comparator<? super T> cp) // 找最大元素
    T min(Comparator<? super T> cp) // 找最小元素
    
    // 针对IntStream / LongStream / DoubleStream
    sum() // 求和
    average() // 求平均数
    

    sum和average只适用于3个基本数据类型

    任意与存在

    boolean allMatch(Predicate<? super T>) // 所有元素均满足测试条件?
    boolean anyMatch(Predicate<? super T>) // 至少有一个元素满足测试条件?
    

    循环处理每个元素

    可以传入side-effects(副作用)

    void forEach(Consumer<? super T> action)
    // 示例
    Stream<String> s = ...
    s.forEach(str -> {
    	System.out.println("Hello, " + str);
    });
    

    把stream转换为其它类型

    object[] toArray() // 转换为object数组
    A[] toArray(IntFunction<A[]>) // 转换为A[]数组
    <R, A> R collect(Collector<? super T,A,R> collector) // 转换为List/Set等集合类型
    

    示例

    Stream<String> s = ...
    String[] arr = s.toArray(string[]::new); // 转换为string数组
    List<String> list = s.collect(Collectors.toList()); // 转换为List
    
  • 相关阅读:
    RPC服务和HTTP服务对比
    常用工具地址
    maven教程
    【springboot】知识点总结
    [JZOJ4272] [NOIP2015模拟10.28B组] 序章-弗兰德的秘密 解题报告(树形DP)
    [NOIP2015模拟10.22] 最大子矩阵 解题报告(单调栈)
    [NOIP2015模拟10.27] 挑竹签 解题报告(拓扑排序)
    [NOIP2015模拟10.27] [JZOJ4270] 魔道研究 解题报告(动态开点+权值线段树上二分)
    [NOIP2015模拟10.22] 最小代价 解题报告 (最小生成树)
    BZOJ4479 [JSOI2013] 吃货jyy 解题报告(三进制状态压缩+欧拉回路)
  • 原文地址:https://www.cnblogs.com/linyh99/p/14125632.html
Copyright © 2011-2022 走看看