java8学习:lambda表达式(2)

简介: 内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。书籍购买地址:[[java8实战]](http://product.dangdang.

内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。
书籍购买地址:java8实战

  • 紧接上一篇内容,上一篇内容讲了lambda的使用,自定义函数式接口和它自带的函数式接口,这一篇将讲述lambda的类型检查,方法引用和构造器引用等内容
  • 上一篇内容提到过,lambda表达式可以为函数式接口生成一个实例,类似匿名内部类的功能,但是lambda本身并不包含他在实现哪个函数式接口的信息
  • 下面来看一下lambda的类型检查

    • 上一篇文章中说到的,lambda的传入参数可写可不写,如下
    Predicate<Integer> predicate = (i) -> i == 3;  
    • 那么她是怎么知道i值是Integer类型的呢? 为了说明这个情况,我们可以参考下面代码
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class Apple {
        private String color;
        private Integer weight;
    }
    public class Java8 {
        @Test
        public void test() throws Exception {
            List<Apple> apples = Arrays.asList();
            filterApple(apples,apple -> apple.getWeight() > 100);
        }
        private void filterApple(List<Apple> apples, Predicate<Apple> predicate){
            for (Apple apple : apples) {
                if (predicate.test(apple)){
                    System.out.println(apple);
                }
            }
        }
    }
    • test方法中的lambda并没有标注apple变量是Apple类型的,那么他为了知道apple变量的类型他会这样做

      • 首先查看filterApple方法签名,从中我们可以看到目标类型是Predicate,这样T泛型就绑定到了Apple
      • 然后查看Predicate内的抽象方法,他接受一个Apple。返回一个boolean
      • 这样函数描述符和lambda的签名是一样的都是传入Apple,apple.getWeight() > 100会返回boolean,所以类型检查是无误的
  • 我们来看下面这个值得注意的地方

    Predicate<Integer> predicate = integer -> list.add(integer);
    • 上面的表达式很正常并没有需要值得注意的地方,list.add方法本来就是返回Predicate抽象方法定义的返回值boolean
    Consumer<Integer> consumer = integer -> list.add(integer);
    • 上面的就会有点疑问了,但是它确实是正确的,Consumer的定义为T->{},所以这就会有一个void兼容规则:如果lambda的主体是一个语句表达式,他就和一个返回void的函数描述符兼容,如上面其实是返回Boolean,而且Consumer明确的说明返回void,他们肯定是不兼容会报错的,但是这个规则就会让他们和平相处
  • 类型推断

    • 上面以及讲到了lambda是怎么推断出参数的类型的,那么我们其实就不用费劲的明确给出其参数的类型,比如
    Comparator<Integer> comparator = (Integer a1,Integer a2) -> a1.compareTo(a2);
    • 完全可以写成
    Comparator<Integer> comparator = (a1,a2) -> a1.compareTo(a2);   //以后还可以简化代码
  • 使用局部变量的限制

    public class Java8 {
        private static int a = 0;
        private int b = 1;
        @Test
        public void test() throws Exception {
            int c = 2;
            IntConsumer consumera = (num) -> System.out.println(num + a);
            IntConsumer consumerb = (num) -> System.out.println(num + b);
            IntConsumer consumerc = (num) -> System.out.println(num + c);
            a = 10;
            b = 11;
            //c = 12;  //只要是重新赋值那么上面引用c变量的地方就会出错
        }
    }
    • 如上我们总结出了,类中的类变量和实例变量,lambda引用他们对他们并不造成影响,但是引用局部变量c的话,c就会被隐式的赋予final修饰
    • 对局部限制的原因

      • 首先是不鼓励使用这种能够改变外部变量的典型命令式编程
      • 并且实例变量和局部变量的实现是不一样的,实例变量是保存在堆中的,但是局部变量是保存在栈中,随着方法弹栈就消失了,考虑以下场景:主线程中定义了c,但是他又开启了一个线程B,在线程B中的lambda访问A中的c,这时候可能会在A把c回收以后B再去访问,因此java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量,如果局部变量仅仅赋值一次那就没什么区别了,因此就有了这个限制
    • 总结来就是被lambda引用的局部变量只能是final
  • 方法引用

    • 只是语法糖而已
    • 分三种方法引用

      • 静态方法的方法引用:Integer.parseInt

        • 凡是可以用类名点出来的,那就属于这种
        Function<String,Integer> function = Integer::parseInt;
        Consumer<String> consumer = Arrays::asList;
        • 为什么可以这么用,拿Integer举例
        • 首先查看一下Function的抽象方法的定义

          R apply(T t);
        • 如上可以看出,需要传入一个传出一个 T -> R
        • 再看parseInt的定义

        1. static int parseInt(String s) throws NumberFormatException {
          return parseInt(s,10);
          }

        • 如上是一个静态方法,那么方法引用就可以引用它,它是传入一个String,返回一个int,那么把上面的Function的泛型固定为String和Int不就好了,所以Function所需要的抽象方法的实现可以由paraseInt方法的实现满足,并且泛型一致,当调用Function中的抽象方法的时候,传入的参数就是传入到parseInt方法中,并返回int,所以可以通过编译
      • 实例的方法引用:"123".length

        List<Integer> list = new ArrayList<>();
        list.sort(Integer::compareTo);
        • 我们看为什么可以如上的用法list.sort(Integer::compareTo);,首先查看sort方法
        default void sort(Comparator<? super E> c) {....}
        • 是需要一个Comparator的函数式接口的抽象方法的实现
        • 再来看Comparator的抽象方法int compare(T o1, T o2);,看到这也就知道了,sort里面需要一个(T,T)->int,那么我们继续看Integer的compareTo方法
        public int compareTo(Integer anotherInteger) {
            return compare(this.value, anotherInteger.value);
        }
        • 上面的compareTo也清楚的表明了是两个参数作为传入并且返回一个int,并且是this.value调用,说明是一个实例对象,所以此方法正好满足Comparator函数式接口中抽象方法的方法签名的定义,所以编译无误
      • 现有对象的实例方法引用:引用局部变量类型中的方法

        public class Java8 {
            @Test
            public void test() throws Exception {
                Java8 java8 = new Java8();
                Function<String,Integer> function1 = str -> java8.parseInt(str);
                Function<String,Integer> function2 = java8::parseInt;
            }
            public Integer parseInt(String str){
                return Integer.parseInt(str);
            }
        }
        • 如上就是引用一个局部变量中的方法
      • 分清第二个方法引用方法和第三个方法引用方法

        public class Java8 {
            @Test
            public void test() throws Exception {
                Java8 java8 = new Java8();
                Function<String,Integer> function2 = str -> str.length();
                Function<String,Integer> function22 = String::length;
                Function<String,Integer> function3 = str -> java8.length(str);
                Function<String,Integer> function33 = java8::length;
            }
            public Integer length(String str){
                return str.length();
            }
        }
        • 如上方法名我做了区别,可以观察出,实例方法引用就是靠传入的参数调用方法,而现有对象实例方法利用是不用引用传入的参数作为调用的对象的
  • 构造方法的引用

    • 这个对于方法引用就简单太多了,举例说明一下
    • 对于无参的构造器
    Supplier<Apple> supplier = Apple::new;
    • 对于一个参数的
    Function<String,Apple> function = Apple::new;
    • 对于两个参数的
    BiFunction<String,Integer,Apple> function = Apple::new;
    • 总结来就是找能与想创建的对象的构造函数参数个数匹配的函数式接口,并且函数式接口的泛型必须跟构造方法参数类型一致
    • 上面虽然没有实例化对象,但是已经引用了此对象,那么就会发生一些有意思的事情
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class Fruit {
        private Integer weight;
    }
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class Apple extends Fruit {
        private Integer weight;
    }
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class Banana extends Fruit{
        private Integer weight;
    }
    public class Java8 {
        @Test
        public void test() throws Exception {
            Map<String,Function<Integer,Fruit>> map = new HashMap<>();
            map.put("apple",Apple::new);
            map.put("banana",Banana::new);
    
            Fruit apple = get(map, "apple", 123);
            System.out.println(apple);
            Fruit banana = get(map, "banana", 23);
            System.out.println(banana);
        }
        public Fruit get(Map<String,Function<Integer,Fruit>> map,String fruitName,Integer fruitWeight){
            return map.get(fruitName).apply(fruitWeight);
        }
    }
    //输出:Apple(weight=123)
    //输出:Banana(weight=23)
    • 可以根据不同的名称得到不同的重量的各种蔬果,只因为它存在构造器的引用
  • 下面就是lambda表达式的有用的方法的说明

    • 考虑一种情况那就是组合比较,比如说两个对象,比较值a都相同的情况下在比较b值是否相等,然后排序,如上的lambda中我们只是使用Comparator中的方法比较了一次,那么如果我们是连续比较呢?

      @Test
      public void test() throws Exception {
          Apple apple1 = new Apple(2015,22);
          Apple apple2 = new Apple(2014,82);
          Apple apple3 = new Apple(2014,12);
          List<Apple> apples = Arrays.asList(apple1,apple2,apple3);
          apples.sort(Comparator.comparing(Apple::getYear));
          //[Apple(year=2014, weight=82), Apple(year=2014, weight=12), Apple(year=2015, weight=22)]
          System.out.println(apples);
      }
      • 如上使用lambda只能排序一次,那么我们怎么二次排序呢?使用thenComparing就可以啦
      @Test
      public void test() throws Exception {
          Apple apple1 = new Apple(2015,22);
          Apple apple2 = new Apple(2014,82);
          Apple apple3 = new Apple(2014,12);
          List<Apple> apples = Arrays.asList(apple1,apple2,apple3);
          apples.sort(Comparator.comparing(Apple::getYear).thenComparing(Apple::getWeight));
          //[Apple(year=2014, weight=12), Apple(year=2014, weight=82), Apple(year=2015, weight=22)]
          System.out.println(apples);
      }
      • 翻转的排序规则的话使用reversed就可以实现了
    • 在我们使用Predicate判断接口的时候,怎么实现&&,||,^呢?对应的方法也就是and,or,negate

      • 还是利用上面的apples集合,下面我们来做筛选
      • 筛选出2015年的apple
      Predicate<Apple> year2014 = apple -> apple.getYear() == 2014;
      for (Apple apple : apples) {
          if (year2014.test(apple)) {
              //Apple(year=2014, weight=82)
              //Apple(year=2014, weight=12)
              System.out.println(apple);
          }
      }
      Predicate<Apple> year2015 = year2014.negate();
      for (Apple apple : apples) {
          if (year2015.test(apple)) {
              //Apple(year=2015, weight=22)
              System.out.println(apple);
          }
      }
      • negate也就是取反操作,当然取2015年的apple,直接==2015也行,这只是在做演示
    • 取出苹果weight=12或者22的apple
    Predicate<Apple> weight12 = apple -> apple.getWeight() == 12;
    Predicate<Apple> weight22 = weight12.or(apple -> apple.getWeight() == 22);
    for (Apple apple : apples) {
        //Apple(year=2015, weight=22)
        //Apple(year=2014, weight=12)
        if (weight22.test(apple)){
            System.out.println(apple);
        }
    }
    • 只是Predicate的组合而已
    • 取出weight=12,year=2014的aple
Predicate<Apple> weight12 = apple -> apple.getWeight() == 12;
Predicate<Apple> year2014 = weight12.and(apple -> apple.getYear() == 2014);
for (Apple apple : apples) {
    //Apple(year=2014, weight=12)
    if (year2014.test(apple)){
        System.out.println(apple);
    }
}
//Predicate<Apple> and = weight12.and(year2014);这样也是可以的,而且这样标示比较的清楚,只要变量名起的好
  • 书上说:这样可以组合更复杂的表达式,但是一行行去读代码还不如原来的操作符。因为自己也是学习,所以理解不深刻,对于这如果有其他的爽歪歪的用法欢迎评论~
  • 函数的复合

    • 在Function中存在andThen方法,我们来试试看
      public void test() throws Exception {
      Function<Integer,Integer> a = x -> x + 1;
      Function<Integer,Integer> b = x -> x * 2;
      Function<Integer,Integer> c = a.andThen(b);
      System.out.println(c.apply(3));
      //1 4
      //2 6
      //3 8
    • 明显的看出,传入的3是这样计算的(3+1)*2
    • 还提供了另一个方法:compose
Function<Integer,Integer> a = x -> x + 1;
Function<Integer,Integer> b = x -> x * 2;
Function<Integer,Integer> c = a.compose(b);
System.out.println(c.apply(2));
//1 3
//2 5
//3 7
  • 计算过程就是这样的:(2*2)+1
好了本篇就介绍到这了。如果发现不对的,请及时更正哈~
目录
相关文章
|
1月前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
2月前
|
Java API 开发者
Java中的Lambda表达式:简洁代码的利器####
本文探讨了Java中Lambda表达式的概念、用途及其在简化代码和提高开发效率方面的显著作用。通过具体实例,展示了Lambda表达式如何在Java 8及更高版本中替代传统的匿名内部类,使代码更加简洁易读。文章还简要介绍了Lambda表达式的语法和常见用法,帮助开发者更好地理解和应用这一强大的工具。 ####
|
2月前
|
并行计算 Java 编译器
深入理解Java中的Lambda表达式
在Java 8中引入的Lambda表达式,不仅简化了代码编写,还提升了代码可读性。本文将带你探索Lambda表达式背后的逻辑与原理,通过实例展示如何高效利用这一特性优化你的程序。
|
2月前
|
搜索推荐 Java API
探索Java中的Lambda表达式
本文将深入探讨Java 8引入的Lambda表达式,这一特性极大地简化了代码编写,提高了程序的可读性。通过实例分析,我们将了解Lambda表达式的基本概念、使用场景以及如何优雅地重构传统代码。文章不仅适合初学者,也能帮助有经验的开发者加深对Lambda表达式的理解。
|
1月前
|
安全 Java API
Java中的Lambda表达式:简化代码的现代魔法
在Java 8的发布中,Lambda表达式的引入无疑是一场编程范式的革命。它不仅让代码变得更加简洁,还使得函数式编程在Java中成为可能。本文将深入探讨Lambda表达式如何改变我们编写和维护Java代码的方式,以及它是如何提升我们编码效率的。
|
2月前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
29 0
|
2月前
|
Java 开发者
探索Java中的Lambda表达式
【10月更文挑战第43天】本文将深入浅出地介绍Java中的Lambda表达式,通过实际代码示例,带领读者理解其背后的原理及应用场景。Lambda表达式不仅简化了代码,还提高了开发效率,是Java开发者必备的技能之一。
|
2月前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
4月前
|
Java 程序员 API
Java 8新特性之Lambda表达式与Stream API的探索
【9月更文挑战第24天】本文将深入浅出地介绍Java 8中的重要新特性——Lambda表达式和Stream API,通过实例解析其语法、用法及背后的设计哲学。我们将一探究竟,看看这些新特性如何让Java代码变得更加简洁、易读且富有表现力,同时提升程序的性能和开发效率。
|
5月前
|
Java API
Java 8新特性:Lambda表达式与Stream API的深度解析
【7月更文挑战第61天】本文将深入探讨Java 8中的两个重要特性:Lambda表达式和Stream API。我们将首先介绍Lambda表达式的基本概念和语法,然后详细解析Stream API的使用和优势。最后,我们将通过实例代码演示如何结合使用Lambda表达式和Stream API,以提高Java编程的效率和可读性。