【JAVA】什么是lambda表达式

Lambda表达式

一、 Lambda是什么

1. Lambda简述

Lambda表达式是JAVA8中提供的一种新的特性,是一个匿名函数方法。可以把Lambda表达式理解为一段可以传递的代码,可以写出更简洁、更灵活的代码。

编码时,我们一般尽可能轻量级的将代码封装为数据,传统的解决方案是通过接口和实现类(匿名内部类)实现,这中方式存在语法冗余,this关键字,变量捕捉,数据控制等问题,而Lambda表达式的出现便是为了写出更简洁、更灵活的代码(粘贴复制)

简单来说,Lambda表达式是为了让代码更加简洁而设计的一种coding方式,用以简化匿名内部类的代码写法。

2. Lambda表达式的使用

Lambda表达式是对匿名内部类的一种简化,但并非所有的匿名内部类都可以用Lambda表达式来实现。

只有函数式接口的匿名内部类才可以使用Lambda表达式来进行简化。

举例:

  1. 方式1:定义一个类MyRunnable实现Runnable接口,重写run()方法

    //采用自定义类的方式
    class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("小浩测试lambda方法1!");
        }
    }
    
    public class LambdaTest {
        public static void main(String[] args) {
            //新建自定义类对象
            MyRunnable test = new MyRunnable();
            //创建Thread类的对象,把MyRunnable的对象作为构造参数传递
            Thread t = new Thread(test);
            //启动线程
            t.start();
        }
    }
    
  2. 方式2:匿名内部类的方式改进

    public class LambdaTest {
        public static void main(String[] args) {
            //采用匿名内部类的方式
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("小浩测试lambda方法2!");
                }
            }).start();
        }
    }
    
  3. 方式3:Lambda表达式的方式改进

public class LambdaDemo {
    public static void main(String[] args) {
        //Lambda表达式改进
        new Thread(() -> {
            System.out.println("小浩测试lambda方法3!");
        }).start();
    }
}

通过三种不同的方式实现自定义线程的启动,可以看出Lambda表达式精简了代码,不需要再去定义实体类或者匿名函数重写run方法。使用Lambda表达式来实现需求时,不用关心创建了什么实体类、重写了什么方法。我们只需要关心它最终要做的事情。但是刚接触的话会觉得晦涩难懂,完全不明白这种写法怎么就能实现上述两种方式的功能。

因此,需要先探讨一下lambda表达式的语法、使用条件以及函数式接口等。

3. 函数式编程概述

在数学中,函数就是有输入量、输出量的一套计算方案,也即是“拿数据做操作”。通过函数把输入的数据转换为输出的结果。
而面向对象思想强调“必须通过对象的形式来做事情”,函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做

Lambda表达式就是函数式思想的体现。

4. 浅谈函数式接口

简单来说就是只包含一个接口方法的特殊接口,通过@FunctionalInterface注解标注

注意:1、函数式接口中默认接口和静态接口可以正常使用
2、由于Java中所有的类都默认继承自Object类,所以继承自Object类中的接口也可以在函数式接口中使用(如何理解)
3、Java中的函数式编程体现就是Lambda表达式。

如何检测一个接口是不是函数式接口?

@FunctionalInterface放在接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败。

注意:自定义函数式接口时,@FunctionalInterface是可选的,就算不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。

函数式接口的两种用法:

  1. 函数式接口做为方法的参数

    package com.test;
    
    public class Test {
        public static void main(String[] args) {
            //方式1:匿名内部类
            startMyThread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"测试线程启动");
                }
            });
    
            //方式2:Lambda表达式
            startThread(()-> System.out.println(Thread.currentThread().getName()+"测试线程启动"));
        }
    
         private static void startMyThread(Runnable r){
            Thread t = new Thread(r);
            t.start();
    
        }
    }
    
  2. 函数式接口作为方法的返回值**(重点理解)**

    package com.test;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    
    public class Demo {
        public static void main(String[] args) {
            ArrayList<String> array=new ArrayList<String>();
            array.add("北京大学物理学院");
            array.add("复旦大学计算机学院");
            array.add("厦门大学法学院");
            array.add("四川大学");
    
            System.out.println("排序前:"+array);
            Collections.sort(array);//自然排序
            System.out.println("自然排序后:"+array);
            Collections.sort(array,getComparator());
            System.out.println("比较器排序后:"+array);
    
        }
    
        private static Comparator<String> getComparator(){
    	        //匿名内部类方式实现
              Comparator<String> comp=new Comparator<String>() {
                  @Override
                  public int compare(String s1, String s2) {
                      return s1.length()-s2.length();
                  }
      
              };
              return comp;
    
            //Lambda表达式
              return (String s1,String s2)->{
                  return s1.length()-s2.length();
              };
             //Lambda表达式简化版
            return (s1,s2)->s1.length()-s2.length();
        }
    }
    

四个常用的函数式接口

  • Supplier接口
  • Consumer接口
  • Predicate接口
  • Function接口

不展开讲诉了,具体参考Java之函数式接口

5. Lambda表达式的语法

组成Lambda表达式的三要素:形式参数,箭头,代码块

标准格式

Lambda表达式的格式:

  1. 格式: (形式参数) -> {代码块}
  2. **形式参数:**如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
  3. **->:**由英文中画线和大于符号组成,固定写法。代表指向动作
  4. **代码块:**是我们具体要做的事情,也就是以前我们写的方法体内容

省略格式

在Lambda标准格式的基础上,使用省略写法的规则为:

  • 小括号内参数的参数类型可以省略。
  • 小括号有且只有一个参数,则小括号可以直接省略。
  • 如果大括号有且只有一个语句,无论是否有返回值,大括号、return关键字、分号可以省略。

Lambda表达式的使用前提

  1. 有一个接口
  2. 接口中有且仅有一个抽象方法

方法引用

当要传递给Lambda表达式的操作,已经有实现的方法了,就可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用的参数列表一致,方法的返回值也必须一致,即方法的签名一致)。方法引用可以理解为方法引用是Lambda表达式的另外一种表现形式。

方法引用的语法:使用操作符“::”将对象或类和方法名分隔开。

方法引用的使用情况共分为以下三种:

  • 对象::实例方法名
  • 类::静态方法名
  • 类::实例方法名

二、 Lambda表达式实战

1. Lambda表达式语法实战

Lambda表达式在Java语言中引入了一个操作符**“->”**,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两个部分:

  • 左侧:指定了Lambda表达式需要的所有参数
  • 右侧:制定了Lambda体,即Lambda表达式要执行的功能。

lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。

  • 可选的参数圆括号:一个参数无需定义圆括号,但无参数或多个参数需要定义圆括号。

  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。

  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

(1) 语法格式一:无参,无返回值,Lambda体只需一条语句。如下:

  @Test
  public void test01(){
    Runnable runnable = ()-> System.out.println("小浩Lambda运行");
    runnable.run();//结果:Runnable 运行
  }

(2) 语法格式二:Lambda需要一个参数,无返回值。如下:

  @Test
  public void test02(){
    Consumer<String> consumer = (x)-> System.out.println(x);
    consumer.accept("小浩Lambda运行");
  }

(3)语法格式三:Lambda只需要一个参数时,参数的小括号可以省略,如下:

  public void test03(){
    Consumer<String> consumer = x-> System.out.println(x);
    consumer.accept("小浩Lambda运行");
  }

(4)语法格式四:Lambda需要两个参数,并且Lambda体中有多条语句。

  @Test
  public void test04(){
    Comparator<Integer> com=(x, y)->{
      System.out.println("函数式接口");
      return Integer.compare(x,y);
    };
    System.out.println(com.compare(2,4));//结果:-1
  }

(5)语法格式五:有两个以上参数,有返回值,若Lambda体中只有一条语句,return和大括号都可以省略不写

  @Test
  public void test05(){
    Comparator<Integer> com=(x, y)-> Integer.compare(x,y);
    System.out.println(com.compare(4,2));//结果:1
  }

(6)Lambda表达式的参数列表的数据类型可以省略不写,因为JVM可以通过上下文推断出数据类型,即“类型推断

  @Test
  public void test06(){
    Comparator<Integer> com=(Integer x, Integer y)-> Integer.compare(x,y);
    System.out.println(com.compare(4,2));//结果:1
  }


2. 函数式接口详解

**定义:**只包含一个抽象方法的接口,就称为函数式接口。

1)自定义函数式接口

按照函数式接口的定义,自定义一个函数式接口,如下:

@FunctionalInterface
public interface MyFuncInterf<T> {
   public T getValue(String origin);
}

定义一个方法将函数式接口作为方法参数。

  public String toLowerString(MyFuncInterf<String> mf,String origin){
    return mf.getValue(origin);
  }

将Lambda表达式实现的接口作为参数传递。

  public void test(){
      String value=toLowerString((str)->{
        return str.toLowerCase();
      },"ABC");
    System.out.println(value);//结果abc
  }

2)内置函数式接口

  1. Consumer:消费型接口 void accept(T t)

      public void showMusic(Integer music, Consumer<Integer> consumer){
        consumer.accept(money);
      }
     @Test
      public void test(){
        showMusic(10,t-> System.out.println("我今天要听"+t+"首歌"));//结果:我今天要听10首歌
      }
    
  2. Supplier:供给型接口 T get() (重点理解)

     /**
       * 产生指定的整数集合放到集合中
       * 重点理解!!
       * Iterable接口的forEach方法的定义:方法中使用到了Consumer消费型接口,
       *     default void forEach(Consumer<? super T> action) {
       *         Objects.requireNonNull(action);
       *         for (T t : this) {
       *             action.accept(t);
       *         }
       *     }
       * 版权声明:本文为CSDN博主「Code0cean」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
       * 原文链接:https://blog.csdn.net/huangjhai/article/details/107110182
       */
    
    @Test
        public void test(){
        List list = addNumInList(10, () -> (int) (Math.random() * 10));
        list.forEach(t-> System.out.println(t));
      }
      public List addNumInList(int size, Supplier<Integer> supplier){
        List<Integer> list=new ArrayList();
        for (int i = 0; i < size; i++) {
          list.add(supplier.get());
        }
        return list;
      }
    
  3. Function<T,R>:函数型接口 R apply(T t)

    public String handleStr(String s,Function<String,String> f){
        return f.apply(s);
      }
    @Test
    public void test(){
        System.out.println(handleStr("abc",(String s)->s.toUpperCase()));
      }
      //结果:ABC
    
  4. Predicate:断言型接口 boolean test(T t)

     /**
       *  自定义条件过滤字符串集合
     */
      @Test
      public void test(){
        List<String> strings = Arrays.asList("郑文浩", "四川大学", "计算机学院", "厦门");
        List<String> stringList = filterStr(strings, (s) -> s.length() > 3);
        for (String s : stringList) {
          System.out.println(s);
        }
      }
      public List<String> filterStr(List<String> list, Predicate<String> predicate){
        ArrayList result = new ArrayList();
        for (int i = 0; i < list.size(); i++) {
          if (predicate.test(list.get(i))){
            result.add(list.get(i));
          }
        }
        return result;
      }
    
  5. 其他接口

3)方法引用

  1. 对象::实例方法名

        //对象::实例方法名
        @Test
        public void test1(){
        PrintStream out = System.out;
        Consumer<String> consumer=out::println; // 这是怎么使用的consumer?
        consumer.accept("im fine");
      }
    
  2. 类::静态方法名

    /**
     * Integer类中的静态方法compare的定义:
     *     public static int compare(int x, int y) {
     *         return (x < y) ? -1 : ((x == y) ? 0 : 1);
     *     }
     */  
    @Test
      public void test2(){
        Comparator<Integer> comparable=(x,y)->Integer.compare(x,y);
        //使用方法引用实现相同效果
        Comparator<Integer> integerComparable=Integer::compare;
        System.out.println(integerComparable.compare(4,2));//结果:1
        System.out.println(comparable.compare(4,2));//结果:1
      }
    
  3. 类::实例方法名

      @Test
      public void test3(){
        BiPredicate<String,String> bp1=(x,y)->x.equals(y);
        //使用方法引用实现相同效果
        BiPredicate<String,String> bp2=String::equals;
        System.out.println(bp1.test("1","2"));//结果:false
        System.out.println(bp2.test("1","2"));//结果:false
      }
    

4)构造器

5)数组引用

  @Test
  public void test(){
    Function<Integer,String[]> function=String[]::new;
    String[] apply = function.apply(10);
    System.out.println(apply.length);//结果:10
  }

Lambda表达式可以看作是匿名内部类实例化的对象

在Lambda表达式中规定只能引用标记了final的外层局部变量。我们不能在lambda 内部修改定义在域外的局部变量,否则会编译错误。

//错误示例

public class TestFinalVariable {

    interface VarTestInterface{
        Integer change(String str);
    }
    
    public static void main(String[] args) {
       //局部变量不使用final修饰
        Integer tempInt = 1;
        VarTestInterface var = (str -> Integer.valueOf(str+tempInt));
        //再次修改,不符合隐式final定义
        tempInt =2;
        Integer str =var.change("111") ;
        System.out.println(str);
    }

}

特殊情况下,局部变量也可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

Lambda表达式访问局部变量作限制的原因 (重点理解)

Lambda表达式不能访问非final修饰的局部变量的原因是,局部变量是保存在栈帧中的。而在Java的线程模型中,栈帧中的局部变量是线程私有的,如果允许Lambda表达式访问到栈帧中的变量地址(可改变的局部变量),则会可能导致线程私有的数据被并发访问,造成线程不安全问题。

基于上述,对于引用类型的局部变量,因为Java是值传递,又因为引用类型的指向内容是保存在堆中,是线程共享的,因此Lambda表达式中可以修改引用类型的局部变量的内容,而不能修改该变量的引用。

对于基本数据类型的变量,在 Lambda表达式中只是获取到该变量的副本,且局部变量是线程私有的,因此无法知道其他线程对该变量的修改,如果该变量不做final修饰,会造成数据不同步的问题。

但是实例变量,静态变量不作限制,因为实例变量,静态变量是保存在堆中(Java8之后),而堆是线程共享的。在Lambda表达式内部是可以知道实例变量,静态变量的变化。

实现原理
①、匿名内部类:编译之后,产生一个单独的.class字节码文件
②、Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成

3. Lambda的延迟执行

有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以 作为解决方案,提升性能。

参考函数式接口看完就懂了

4. Stream应用(具体详见stream专题)

参考详解lambda表达式

参考

  1. Lambda表达式超详细总结 (重点)
  2. 详解Lambda表达式 (重点)
  3. 让你秒懂的Lambda表达式超级详细讲解
  4. Java中的lambda表达式如何理解——精简
  5. 详解lambda表达式2
  6. 函数式接口看完就懂了
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值