Java--函数式编程详细教程

Lambda表达式

作用
  • 避免匿名内部类定义过多
  • 可以让你的代码看起来很简洁
  • 去掉了一堆没有意义的代码,只留下核心的逻辑
  • 其实质属于函数式编程的概念
函数式接口

定义:

  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
//例如;
public interface Runnable{
    public abstract void run();//默认都是抽象的,可以不写public abstract
}
  • 对于函数式接口,我们可以通过lambda表达式来创建该接口对象

  • 四大核心内置接口

    Consumer<T> : 消费型接口(无返回值,有去无回)
             void accept(T t);
    Supplier<T> : 供给型接口
         T get();
    
    Function<T,R> : 函数型接口
        R apply(T t);
    
    Predicate<T> : 断言型接口
        boolean test(T t);
            
    //这四大核心接口在之后Stream中常用
    
    //以上四大核心内置接口是我们日常开发中经常要用到的,同时,它们还有一些变种,如:
    //BiConsumer,Consumer的增强版,接受两个参数:
    @FunctionalInterface
    public interface BiConsumer<T, U> {
       void accept(T t, U u);
    }
    //BiFunction类似,Function的增强版,接受两个参数,返回一个参数:
    @FunctionalInterface
    public interface BiFunction<T, U, R> {
    
      R apply(T t, U u);
    
      default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
          Objects.requireNonNull(after);
          return (T t, U u) -> after.apply(apply(t, u));
      }
    }
    
使用
//推导lambda表达式
public class TestLambda {
    //3.静态内部类
    static class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("i like lambda2");
        }
    }
    public static void main(String[] args) {
        ILike like =new Like();
        like.lambda();
        like =new Like2();
        like.lambda();
        //4.局部内部类
        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("i like lambda3");
            }
        }
        like =new Like3();
        like.lambda();
        //5.匿名内部类,没有类的名称,必须借助接口或者父类
        like =new ILike() {
            @Override
            public void lambda() {
                System.out.println("i like lambda4");
            }
        };
        like.lambda();
        //6.用lambda简化 因为只有一个接口所以省去类和接口名字重写那部分,只留下方式的具体实现部分
        like = ()->{
            System.out.println("i like lambda5");
        };
        like.lambda();
    }
}
//1.定义一个函数式接口
interface ILike{
    void lambda();
}
//2.实现类
class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}
简化

简化规则与总结:

  1. lambda表达式只能有一行代码的情况下才能简化成为一行,若果有多行,那么就用代码块。(由{}包裹)

  2. 前提是接口为函数式接口(只有一个抽象方法)

  3. 多个参数也可以去掉参数类型,要去掉就都去掉,必须加括号

    如love =(i,j)->{ System.out.println(“i love you too !!”+i+j); };

public class TestLamdba2 {
    public static void main(String[] args) {
        //1.lambda表示简化
        Ilove love =(int i)->{
            System.out.println("i love you !!"+i);
        };
        //简化1.参数类型
        love =(i)->{
            System.out.println("i love you too !!"+i);
        };
        //简化2.简化括号
        love =i->{
            System.out.println("i love you too 1 !!"+i);
        };
        //简化3.去掉花括号
        love =i-> System.out.println("i love you too a !!"+i);
        /*总结:
        1.lambda表达式只能有一行代码的情况下才能简化成为一行,若果有多行,那么就用代码块。(由{}包裹)
        2.前提是接口为函数式接口(只有一个抽象方法)
        3.多个参数也可以去掉参数类型,要去掉就都去掉,必须加括号
        如love =(i,j)->{ System.out.println("i love you too !!"+i+j); };
        */
        love.love(521);
    }
}
interface Ilove{
    void  love(int a);
}
class Love implements Ilove{

    @Override
    public void love(int a) {
        System.out.println("i love you !!");
    }
}

方法引用

说明
  1. 所谓方法引用,是指如果某个方法签名和接口恰好一致,就可以直接传入方法引用。
  2. 方法签名只看参数类型和返回类型,不看方法名称,也不看类的继承关系。
作用
  1. 方法引用可以理解为Lambda表达式的另外一种表现形式。
  2. 方法引用通过方法的名字来指向一个方法。
  3. 方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  4. 方法引用使用一对冒号 ::
方法的分类
类型语法对应的Lambda表达式
静态方法引用类名::staticMethod(args) -> 类名.staticMethod(args)
实例方法引用inst::instMethod(args) -> inst.instMethod(args)
对象方法引用类名::instMethod(inst,args) -> 类名.instMethod(args)
构建方法引用类名::new(args) -> new 类名(args)
方法引用举例
  1. 静态方法引用

    public class Main {
        public static void main(String[] args) {
            String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
            Arrays.sort(array, Main::cmp);//Comparator<String>接口定义的方法是int compare(String, String),和静态方法int cmp(String, String)相比,除了方法名外,方法参数一致,返回类型相同,可以直接把方法名作为Lambda表达式传入
            System.out.println(String.join(", ", array));
        }
    
        static int cmp(String s1, String s2) {
            return s1.compareTo(s2);
        }
    }
    
  2. 实例方法引用

    @Data
    class User {
        private String name;
        private Integer age;
    
        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
        
        public getName(){
            return name;
        }
    }
    public class MethodReference {
        public static void main(String[] args) {
            User user = new User("巴巴",32);
            Supplier<String> supplier = () -> user.getName();
            System.out.println("Lambda表达式输出结果:" + supplier.get());
    
            Supplier<String> supplier2 = user::getName;
            System.out.println("实例方法引用输出结果:" + supplier2.get());
        }
    }
    
  3. 特定类的任意对象的方法引用

    public class Main {
        public static void main(String[] args) {
            String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
            Arrays.sort(array, String::compareTo);
            System.out.println(String.join(", ", array));
        }
    }
    

    其中compareTo的方法定义为:

    public int compareTo(String o) {
        ...
    }
    

    因为实例方法有一个隐含的this参数,String类的compareTo()方法在实际调用的时候,第一个隐含参数总是传入this,相当于静态方法:

    public static int compareTo(this, String o);
    

    也可以理解为第一个参数是实例方法的参数调用者,而第二个参数是实例方法的参数。x.compareTo(y);x为参数调用者,y为实例方法的参数。所以,String.compareTo()

    方法也可作为方法引用传入。

  4. 构造方法引用

    1 Supplier<List<User>> userSupplier = () -> new ArrayList<>();
    2 List<User> user = userSupplier.get();
    3 
    4 Supplier<List<User>> userSupplier2 = ArrayList<User>::new;    // 构造方法引用写法
    5 List<User> user2 = userSupplier.get();
    

Stream

介绍:
  1. Stream为全新的流式API:Stream API。它位于java.util.stream包中。这个Stream不同于java.ioInputStreamOutputStream,它代表的是任意Java对象的序列。
  2. 这个StreamList也不一样,List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。
  3. Stream API支持函数式编程和链式操作;
  4. Stream可以表示无限序列,并且大多数情况下是惰性求值的。
操作步骤:
  1. 创建Stream----从一个数据源,如集合、数组中获取流。
  2. 中间操作----一个操作的中间链,对数据源的数据进行操作。
  3. 终止(聚合)操作----一个终止操作,执行中间操作链,并产生结果。

**注:**一般只有到聚合操作的时候才会触发计算,中间操作都是惰性计算并不是真正的计算

创建:
  1. 使用Stream.of()创建----元素固定

    public static void main(String[] args) {
        Stream<String> stream = Stream.of("A", "B", "C", "D");
        // forEach()方法相当于内部循环调用,
        // 可传入符合Consumer接口的void accept(T t)的方法引用:
        stream.forEach(System.out::println);
    }
    
  2. 基于数组或Collection----元素固定

    public static void main(String[] args) {
        Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" });
        Stream<String> stream2 = List.of("X", "Y", "Z").stream();
        stream1.forEach(System.out::println);
        stream2.forEach(System.out::println);
    }
    //对于Collection(List、Set、Queue等),直接调用stream()方法就可以获得Stream。
    
  3. 基于Supplier----可以表示无限序列

    public static void main(String[] args) {
        Stream<Integer> natual = Stream.generate(new NatualSupplier());//Stream.generate(Supplier<String> sp);该方法需要传入一个Supplier对象
        // 注意:无限序列必须先变成有限序列再打印:
        natual.limit(20).forEach(System.out::println);
    }
    }
    class NatualSupplier implements Supplier<Integer> {
    int n = 0;
    public Integer get() {
        n++;
        return n;
    }
    
  4. 其他方法

    //1.Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容:
    try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) {
        ...   
    }
    //2.正则表达式的Pattern对象有一个splitAsStream()方法,可以直接把一个长字符串分割成Stream序列而不是数组:
    Pattern p = Pattern.compile("\\s+");
    Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog");
    s.forEach(System.out::println);
    
  5. 基本类型

    //基本类型无法使用泛型,使用Integer会频繁装箱拆箱,Java标准库提供了IntStream、LongStream和DoubleStream这三种使用基本类型的Stream
    
    // 将int[]数组变为IntStream:
    IntStream is = Arrays.stream(new int[] { 1, 2, 3 });
    // 将Stream<String>转换为LongStream:
    LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);
    
中间操作:
  1. 使用map–重要

    • 所谓map操作,就是把一种操作运算,映射到一个序列的每一个元素上。它把一个Stream转换为另一个Stream

      Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
      Stream<Integer> s2 = s.map(n -> n * n);//相当于每个元素变为原来的平方
      
    • map()方法接收的对象是Function接口对象,它定义了一个apply()方法,负责把一个T类型转换成R类型:

      <R> Stream<R> map(Function<? super T, ? extends R> mapper);
      //Function为前面提到的四大函数式核心接口--函数型接口
      @FunctionalInterface
      public interface Function<T, R> {
          // 将T类型转换为R:
          R apply(T t);
      }
      
    • 例子:字符串操作

      public static void main(String[] args) {
          List.of("  Apple ", " pear ", " ORANGE", " BaNaNa ")
                  .stream()
                  .map(String::trim) // 去空格
                  .map(String::toLowerCase) // 变小写
                  .forEach(System.out::println); // 打印
      }
      
  2. 使用filter–重要

    • 所谓filter()操作,就是对一个Stream的所有元素一一进行测试,过滤掉不满足的形成新的Stream

      IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                      .filter(n -> n % 2 != 0) //过滤掉原Stream中的偶数
                      .forEach(System.out::println);
      
    • filter()方法接收的对象是Predicate接口对象,它定义了一个test()方法,负责判断元素是否符合条件:

      Stream<T> filter(Predicate<? super T> predicate)
      //Predicate为前面提到的四大函数式核心接口--断言型接口
      @FunctionalInterface
      public interface Predicate<T> {
          // 判断元素t是否符合条件:
          boolean test(T t);
      }
      
    • 例子:获取空字符串的数量

      List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
      // 获取空字符串的数量
      long count = strings.stream().filter(string -> string.isEmpty()).count();
      
  3. 其他中间操作

    • limit举例

      IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                      .limit(5) //只要前面5元素
                      .forEach(System.out::println);
      //打印 1 2 3 4 5 
      
    • skip举例

      IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                      .skip(4) //前面4个元素不要
                      .forEach(System.out::println);
      //打印 5 6 7 8 9 
      
    • distinct去重举例

      IntStream.of(1, 2, 3, 4, 5, 6, 6, 7, 7, 7)
                      .distinct() //去掉重复的元素
                      .forEach(System.out::println);
      //打印 1 2 3 4 5 6 7
      
    • sort排序举例

      //此方法需要Stream的每个元素必须实现Comparable接口;
      List<String> list = List.of("orange", "apple", "banana")
                  .stream()
                  .sorted()
                  .collect(Collectors.toList());
              System.out.println(list);
      //打印[apple,banana,orange]
      
    • concat合并举例

      Stream<String> s1 = List.of("A", "B", "C").stream();
      Stream<String> s2 = List.of("D", "E").stream();
      // 合并:
      Stream<String> s = Stream.concat(s1, s2);
      System.out.println(s.collect(Collectors.toList())); 
      //打印[A, B, C, D, E]
      
    • flatMap举例

      //所谓flatMap(),是指把Stream的每个元素(这里是List)映射为Stream,然后合并成一个新的Stream:
      Stream<List<Integer>> s = Stream.of(
              Arrays.asList(1, 2, 3),
              Arrays.asList(4, 5, 6),
              Arrays.asList(7, 8, 9));
      Stream<Integer> i = s.flatMap(list -> list.stream());//通过flatMap把元素是集合的Stream转换为Stream<Integer>
      
终止操作:
  1. 使用reduce–重要

    • Stream.reduce()Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果。

      int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(0, (acc, n) -> acc + n);//如果不初始化,返回值为Optional<Integer>,因为Stream元素可能是0个,需要使用isPresent()进一步判断
      System.out.println(sum); // 45
      //--------上述代码解释-----------
      Stream<Integer> stream = ...
      int acc = 0;//初始化结果为指定值(这里是0)
      for (n : stream) {
          acc = (acc, n) -> acc + n;
      }
      sum=acc
          
      
    • reduce()方法传入的对象是BinaryOperator接口,它定义了一个apply()方法,负责把上次累加的结果和本次的元素 进行运算,并返回累加的结果:

      T reduce(T identity, BinaryOpeartor<T> accumulator)
          
      @FunctionalInterface
      public interface BinaryOperator<T> {
          // Bi操作:两个输入,一个输出
          T apply(T t, T u);
      }
      
    • 例子:将配置文件的每一行配置通过map()reduce()操作聚合成一个Map<String, String>

      public class Main {
          public static void main(String[] args) {
              // 按行读取配置文件:
              List<String> props = List.of("profile=native", "debug=true", "logging=warn", "interval=500");
              Map<String, String> map = props.stream()
                      // 把k=v转换为Map[k]=v:
                      .map(kv -> {
                          String[] ss = kv.split("\\=", 2);
                          return Map.of(ss[0], ss[1]);
                      })
                      // 把所有Map聚合到一个Map:
                      .reduce(new HashMap<String, String>(), (m, kv) -> {
                          m.putAll(kv);
                          return m;
                      });
              // 打印结果:
              map.forEach((k, v) -> {
                  System.out.println(k + " = " + v);
              });
          }
      }
      
  2. 输出集合

    • 输出为List:把Stream变为List不是一个转换操作,而是一个聚合操作,它会强制Stream输出每个元素。

      Stream<String> stream = Stream.of("Apple", "", null, "Pear", "  ", "Orange");
      List<String> list = stream.filter(s -> s != null && !s.isBlank()).collect(Collectors.toList());//传入Collectors.toSet()可转为Set
      System.out.println(list);
      //调用collect()并传入Collectors.toList()对象,它实际上是一个Collector实例,通过类似reduce()的操作,把每个元素添加到一个收集器中
      
    • 输出为数组:调用toArray()方法,并传入数组的“构造方法”

      List<String> list = List.of("Apple", "Banana", "Orange");
      String[] array = list.stream().toArray(String[]::new);
      
    • 输出为Map

      public static void main(String[] args) {
          Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft");
          Map<String, String> map = stream
                  .collect(Collectors.toMap(
                          // 把元素s映射为key:
                          s -> s.substring(0, s.indexOf(':')),
                          // 把元素s映射为value:
                          s -> s.substring(s.indexOf(':') + 1)));
          System.out.println(map);
      }
      //对于每个元素,添加到Map时需要key和value,因此,我们要指定两个映射函数,分别把元素映射为key和value
      
    • 分组输出

      public static void main(String[] args) {
          List<String> list = List.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");
          Map<String, List<String>> groups = list.stream()
                  .collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList()));
          System.out.println(groups);
      }
      //分组输出使用Collectors.groupingBy(),它需要提供两个函数:一个是分组的key,这里使用s -> s.substring(0, 1),表示只要首字母相同的String分到一组,第二个是分组的value,这里直接使用Collectors.toList(),表示输出为List
      
  3. 其他终止操作

    • allMatch举例

      //判断是否都为正数
      final boolean positive =Stream.of(1,2,3,-1,-4,5,-4).allMatch(n->n>=0);
      System.out.println(positive);
      //打印false
      
    • anyMatch举例

      //判断是否有正数
      final boolean positive =Stream.of(1,2,3,-1,-4,5,-4).anyMatch(n->n>=0);
      System.out.println(positive);
      //打印true
      
    • max min 举例

      final Optional<Integer> maxNum=Stream.of(1,2,3,-1,-4,5,-4).max(Integer::compareTO);
      System.out.println(“最大值: ”+maxNum.get());
      //打印 最大值: 6
      final Optional<Integer> minNum=Stream.of(1,2,3,-1,-4,5,-3).min(Integer::compareTO);
      System.out.println(“最小值: ”+minNum.get());
      //打印 最小值: -4
      
    • average、sum、max、min 配合collect()使用

      Double averageN = Stream.of(5, 5, 5, 5, 5).collect(Collectors.averagingInt(n -> n));
      System.out.println(averageN);
      //打印 5
      Optional<Integer> maxN = Stream.of(9, 6, 5, 3, 2).collect(Collectors.maxBy(Integer::compareTo));
      System.out.println(maxN.get());
      //打印 9
      Optional<Integer> minN = Stream.of(9, 6, 5, 3, 2).collect(Collectors.minBy(Integer::compareTo));
      System.out.println(minN.get());
      //打印 2
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值