Lambda的学习笔记

参照学习链接原文章链接

三步理解Lambda

确认 Lambda 表达式类型
  1. 能用 Lambda 表达式来表示的类型,必须是一个函数式接口=> 定义=>只有一个抽象方法的接口.例如,以下为一个标准的函数式接口。

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    

    @FunctionalInterface // 此注释仅在编译期帮你检查你这个接口是否符合函数式接口的条件,比如你没有任何抽象方法,或者有多个抽象方法,编译是无法通过的

    // 没有实现任何抽象方法的接口
    @FunctionalInterface
    public interface MyRunnable {}
    
    // 编译后控制台显示如下信息
    Error:(3, 1) java: 
    意外的 **@FunctionalInterface** 注释
    MyRunnable 不是函数接口
    在接口 MyRunnable 中找不到抽象方法
    
  2. Java 8 之后接口中是允许使用默认方法静态方法的,而这些都不算抽象方法,所以也可以加在函数式接口里。例如以下的实例

    @FunctionalInterface
    public interface Consumer<T> {
        void accept(T t);  // 抽象方法
        default Consumer<T> andThen(Consumer<? super T> after) {...}  // 默认方法
    }
    

    只有一个抽象方法(accept),还有一个默认方法(方法体的代码省略了),这个也不影响它是个函数式接口。再看一个更复杂的,多了静态方法,这同样也是个函数式接口,因为它仍然只有一个抽象方法。同样的可以判断以下是是否符合只有一个抽象方法的函数式接口

    @FunctionalInterface
    public interface Predicate<T> {
        // 抽象方法
        boolean test(T t);
        // 默认方法
        default Predicate<T> and(Predicate<? super T> other) {...}
        default Predicate<T> negate() {...}
        default Predicate<T> or(Predicate<? super T> other) {...}
        // 静态方法
        static <T> Predicate<T> isEqual(Object targetRef) {...} 
        static <T> Predicate<T> not(Predicate<? super T> target) {...}
    }
    

    一句话 认准

    1. 一个抽象方法
    2. 接口
找到要实现的方法
  1. 实现上文中的抽象方法

    @FunctionalInterface
    public interface Runnable {
        public abstract void run(); // 抽象方法
    }
    
    @FunctionalInterface
    public interface Consumer<T> {
        void accept(T t); // 抽象方法
        default Consumer<T> andThen(Consumer<? super T> after) {...}
    }
    
    @FunctionalInterface
    public interface Predicate<T> {
        boolean test(T t); // 抽象方法
        default Predicate<T> and(Predicate<? super T> other) {...}
        default Predicate<T> negate() {...}
        default Predicate<T> or(Predicate<? super T> other) {...}
        static <T> Predicate<T> isEqual(Object targetRef) {...}
        static <T> Predicate<T> not(Predicate<? super T> target) {...}
    }
    
实现这个方法
  1. 匿名类方法(非lambda方法)

    Predicate<String> predicate = new Predicate<String>() {
        @Override
        public boolean test(String s) {
            return s.length() != 0;
        }
    };
    
  2. lambda表达式方法

    Predicate<String> predicate = (String s) -> {return s.length()= 0;};
    

    这个 Lambda 语法由三部分组成:

    参数块:就是前面的 (String s),就是简单地把要实现的抽象方法的参数原封不动写在这

    小箭头:就是 -> 这个符号。

    代码块:就是要实现的方法原封不动写在这

  3. 对2的代码逐步进行简写

    1. 参数块

      1. (String s) 里面的类型信息是多余的,可以由编译器推导,去掉它。简写为:

        Predicate<String> predicate = (s) -> {return s.length()= 0;};
        
      2. 当只有一个参数时,括号也可以去掉,简写为:

        Predicate<String> predicate = s -> {return s.length()= 0;};
        
    2. 代码块

      1. 方法体中只有一行代码,可以把花括号return去掉

        Predicate<String> predicate = s -> s.length() != 0;
        
  4. lambda实现Runnable接口

    1. 函数式接口确认=》 √符合要求

      @FunctionalInterface
      public interface Runnable {
          public abstract void run();
      }
      
    2. 确定参数要实现的方法=》 run() 方法

    3. lambda实现 参数部分代码部分

      Runnable runnable = () -> System.out.println("I'm  running");
      

      因为runnable抽象方法没有返回值,所以()不能省略

    4. 进一步讨论,在平时为进程新建线程的时候。”八股文“语法为

      // 基于匿名类实现函数式接口
      Runnable runnable = new Runnable(){
      	@Override
          public void run() {
              System.out.println("I'm  running");
          }
      }
      // 声明线程类
      Thread thread = new Thread(runnable);
      // 启动线程
      thread.start();
      

      在此我们使用lambda进行替换

      new Thread(()-> System.out.println("I'm  running")).start();
      
    5. 完整实例

      public class Runnable_test {
      //  方法1
          public static void func_1() {
              Runnable runnable = new Runnable(){
                  @Override
                  public void run() {
                      System.out.println("traditional func");
                  }
              };
      
              Thread thread = new Thread(runnable);
      
              thread.start();
          }
      // 方法2
          public static void func_2() {
              Runnable runnable = () -> System.out.println("lambda func1");
      
              Thread thread = new Thread(runnable);
      
              thread.start();
          }
      // 方法3
          public static void func_3() {
      
      //        Thread thread = new Thread(() -> System.out.println("I'm  running"));
      //        thread.start();
      
              new Thread(()-> System.out.println("lambda func2")).start();
          }
      
          public static void main(String[] args) {
              func_1();
              func_2();
              func_3();
          }
      }
      
      
    6. 多个入参的情况,分析处理。

      1. 根据上面提到的方法,判断符合只有一个抽象方法的接口,则可以进行改写

        @FunctionalInterface
        public interface BiConsumer<T, U> {
            void accept(T t, U u);
        }
        
        Biconsumer<Random,Integer> randomNumberPrinter = 
            (random,number)->{
            for(int i=0;i<number;i++){
                System.out.println("next random = " + random.nextInt());
            }
        }
        
        
        randomNumberPrinter.accept(new Random(314L), 5));
        
      2. 在多参数的基础上,增加返回值

        @FunctionalInterface
        public interface BiFunction<T, U, R> {
            R apply(T t, U u);
            // default methods removed
        }
        
        
        BiFunction<String, String, Integer> findWordInSentence = 
            (word, sentence) -> sentence.indexOf(word);
        	
        

        返回值为 sentence.indexOf(word)

Stream编程

学生list实例

将student list 转换成一个 map,key 是 student 对象的 id,value 就是 student 对象本身。

List<Student> studentList = gen();
Map<String, Student> map = studentList.stream()
        .collect(Collectors.toMap(Student::getId, a -> a, (a, b) -> a));

把 Lambda 表达式的部分提取出来

Collectors.toMap(Student::getId,a->a,(a,b)->a)

我们还没见过 :: 这种形式,先打回原样

Collectors.toMap(a -> a.getId(), a -> a, (a, b) -> a)

Collectors.toMap的方法源码

public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(
                                Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction) 
		{
   		 return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
		}

入参有三个,分别是Function,Function,BinaryOperator,其中 BinaryOperator 只是继承了 BiFunction 并扩展了几个方法,我们没有用到,所以不妨就把它当做BiFunction。针对Function R apply(T t) BiFunction R apply(T t, U u)以下进行拆分

  1. 第一个参数**a -> a.getId()**就是 R apply(T t) 的实现,入参是 Student 类型的对象 a,返回 a.getId()
  2. 第二个参数a -> a也是 R apply(T t) 的实现,入参是 Student 类型的 a,返回 a 本身
  3. 第三个参数**(a, b) -> a**是 R apply(T t, U u) 的实现,入参是Student 类型的 a 和 b,返回是第一个入参 a,Stream 里把它用作当两个对象 a 和 b 的 key 相同时,value 就取第一个元素 a

其中第二个参数 a -> a 在 Stream 里表示从 list 转为 map 时的 value 值,就用原来的对象自己,你肯定还见过这样的写法。

Collectors.toMap(a -> a.getId(), Function.identity(), (a, b) -> a)

为什么可以这样写,给你看 Function 类的全貌你就明白了。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t); 
    ...
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

看到了吧,identity 这个方法,就是帮我们把表达式给实现了,就不用我们自己写了,其实就是包了个方法。这回知道一个函数式接口**,为什么有好多还要包含一堆默认方法和静态方法了吧?就是干这个事用的。**

predict实例

我们再来试一个,Predicate 里面有这样一个默认方法。

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
}

它能干嘛用呢?我来告诉你,如果没有这个方法,有一段代码你可能会这样写。

Predicate<String> p = 
    s -> (s != null) && 
    !s.isEmpty() && 
    s.length() < 5;

如果利用上这个方法,就可以变成如下这种优雅形式。

Predicate<String> nonNull = s -> s != null;
Predicate<String> nonEmpty = s -> s.isEmpty();
Predicate<String> shorterThan5 = s -> s.length() < 5;

Predicate<String> p = nonNull.and(nonEmpty).and(shorterThan5);

方法引用

针对 Student::getId 这种写法。当方法体中只有一个方法调用时,就可以作这样的简化。

比如这个 a -> a.getId() 就只是对 Student 对象上 getId() 这个方法的调用,那么就可以写成 Student::getId 这种形式。

再看几个例子

Function<String, Integer> toLength = s -> s.length();
Function<String, Integer> toLength = String::length;

Function<User, String> getName = user -> user.getName();
Function<String, Integer> toLength = User::getName;

Consumer<String> printer = s -> System.out.println(s);
Consumer<String> printer = System.out::println;

如果是构造方法的话,也可以简化。

Supplier<List<String>> newListOfStrings = () -> new ArrayList<>();
Supplier<List<String>> newListOfStrings = ArrayList::new;

总结

学会理解和写 Lamda 表达式,别忘了最开始的三步。

1. 确认 Lamda 表达式的类型

2. 找到要实现的方法

3. 实现这个方法

Lamda 表达式的类型就是函数式接口,要实现的方法就是函数式接口里那个唯一的抽象方法,实现这个方法的方式就是参数块 + 小箭头 + 方法体,其中参数块和方法体都可以一定程度上简化它的写法。

今天的文章主要就是讲怎么写出 Lamda 表达式,至于原理,之后再说。这里提个引子,你觉得 Lamda 表达式是匿名类的简化么?按照官方的说法,Lamda 表达式在某些情况下就是匿名类的一种更简单的写法,但是从字节码层面看,完全不同

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值