【Java】:lambda 表达式

【Java】:lambda 表达式

📃个人主页island1314

🔥个人专栏:java学习

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞

【Java】:lambda 表达式


1. 背景 🚀

🔥 Lambda 表达式 是JDK8新增的特性,Lambda 表达式 可以取代大部分匿名内部类,写出更优雅的Java代码,尤其在集合的遍历和其他集合操作中,可以将函数作为一个方法的参数,也就是函数作为参数传递到方法中,极大地优化代码结构JDK也提供了大量的内置函数式接口供开发者使用,使得 Lambda 表达式 的运用更加方便、高效。

  • Lambda 表达式(Lambda expression)可以看作是一个匿名函数 ,基于数学中的λ演算得名,也可称为闭包(Closure)
  • Lambda 表达式 的使用场景:用以简化接口实现

2. 基本使用 🖊

🥑 1. 语法格式

🎈 Lambda表达式的基本语法:由 参数列表(parameters)、箭头符号(一 >) 和方法体(expression 或者 statements)组成

  • paramaters: 类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。
    - >:可理解为“被用于” 的意思
    方法体: 可以是表达式也可以代码块,是函数式接口里方法的实现。

    • 代码块可返回一个值或者什么都不返回,这里的代码块等同于方法的方法体。

    • 如果是表达式,也可以返回一个值或者什么都不返回。

    • 其中,表达式会被执行,然后返回执行结果;语句块中的语句会被依次执行,就像方法中的语句一样。

Lambda表达式常用的语法格式如下所示:

语法格式 描述
() -> System.out.println("Hello IsLand1314"); 无参数,无返回值
(x) -> System.out.println(x); 有一个参数,无返回值
x -> System.out.println(x); 若只有一个参数,小括号可以省略不写
Comparatorcom = (x,y) -> { System.out.priniln("函数式接口"); return Integer,compare(x,y); }; 有两个以上的参数,有返回值,并且Lambda方法体中有多条语句
Comparatorcom = (x,y) -> Integer,compare(x,y);); 若Lambda方法体中只有一条语句,return和大括号都可以省略不写
(Integer x, Integer y) -> Integer.compare(x, y): Lambda表达式的参数列表的数据类型可以省略不写,因为 Java 虚拟机的编译器可以通过上下文推断出数据类型,即 "类型推断"

🥑 2. 函数式接口

🥑 虽然说,Lambda 表达式 可以在⼀定程度上简化接口的实现。但是,并不是所有的接口都可以使用lambda表达式来简洁实现的。

🧃 Lambda 表达式 毕竟只是⼀个匿名方法。当实现的接口中的方法过多或者多少的时候,lambda表达式都是不适用的。

  • lambda表达式,只能实现函数式接口,函数式接口 定义:一个接口有且只有一个抽象方法

如下:

```java
//有且只有一个实现类必须要实现的抽象方法,所以是函数式接口
interface Test{
    public void test();
}

```
  • ⼀个接口中,要求实现类必须实现的抽象方法,有且只有⼀个!这样的接口,就是函数式接口

补充:

🔖 @FunctionalInterface
  • 是⼀个注解,用在接口之前,判断这个接口是否是⼀个函数式接口。
  • 如果是函数式接口,没有任何问题。如果不是函数式接口,则会报错
  • 功能类似于 @Override

    ```java
    @FunctionalInterface
    interface NoParameterNoReturn {
    //注意:只能有一个方法
    void test();
    }

    ```

但是这种方式也是可以的:

```java
@FunctionalInterface
interface NoParameterNoReturn {
    void test();
    default void test2() {
        System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");
    }
}

```

注意:

  1. 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
  2. 如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的
    1. 所以,从某种意义上来说,只要你保证你的接口中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的
  3. Lambda 表达式 只能简化函数式接口的匿名内部类的写法

🥑 3. 具体使用

🌈 我们在上面提到过,Lambda表达式本质是一个匿名函数,函数的方法是:返回值 方法名 参数列表 方法体

  • Lambda 表达式 中我们只需要关心:参数列表 方法体

首先,我们实现准备好几个接口:

```java
//无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
    void test();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
    void test(int a);
}
//无返回值两个参数
@FunctionalInterface
interface MoreParameterNoReturn {
    void test(int a,int b);
}

//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
    int test();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
    int test(int a);
}
//有返回值多个参数
@FunctionalInterface
interface MoreParameterReturn {
    int test(int a,int b);
}
```

我们在上面提到过,Lambda可以理解为:Lambda就是匿名内部类的简化,实际上是创建了一个类,实现了接口,重写了接口的方法 。

  • 没有使用 Lambda 表达式 的时候的调用方式

    java
    NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn(){
      @Override
      public void test() {
        System.out.println("hello");
     }
    };
    noParameterNoReturn.test();

  • 使用 Lambda 表达式 的时候的调用方式

    ```java

    public class Test {
    public static void main(String[] args) {
    NoParameterNoReturn n = ()->{
    System.out.println("无参数无返回值");
    };
    n.test();

        OneParameterNoReturn o = (a)-> {
            System.out.println("无返回值一个参数 "+a);
        };
        o.test(666);
        MoreParameterNoReturn m = (int a,int b)->{
            System.out.println("无返回值两个参数 "+a+" "+b);
        };
        m.test(666,999);
    
        System.out.println("================");
    
        NoParameterReturn n1 = ()->{
            return 666;
        };
    
        int ret1 = n1.test();
        System.out.println(ret1);
        System.out.println("================");
        OneParameterReturn o1 = (int a)->{
            return a;
        };
        int ret2 = o1.test(999);
        System.out.println(ret2);
        System.out.println("================");
        MoreParameterReturn m1 = (int a,int b)-> {
            return a+b;
        };
        int ret3 = m1.test(10,90);
        System.out.println(ret3);
    
    }
    

    }

    // 打印结果:
    无参数无返回值
    无返回值一个参数 666
    无返回值两个参数 666 999
    ================
    666
    ================
    999
    ================
    100

    ```

🔥 Lambda 表达式 的语法还可以精简,显得非常有逼格,但是可读性就非常差,之前我们上面说过 Lambda 表达式 的语法格式表格。

  1. 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
  2. 参数的小括号里面只有一个参数,那么小括号可以省略
  3. 如果方法体当中只有一句代码,那么大括号可以省略
  4. 如果方法体中只有一条语句,其是return语句,那么大括号可以省略,且去掉return关键字

    ```java
    public class Test {
    public static void main(String[] args) {
    MoreParameterNoReturn moreParameterNoReturn = (a, b)->{
    System.out.println("无返回值多个参数,省略参数类型:"+a+" "+b);
    };
    moreParameterNoReturn.test(20,30);
    OneParameterNoReturn oneParameterNoReturn = a ->{
    System.out.println("无参数一个返回值,小括号可以省略:"+ a);
    };
    oneParameterNoReturn.test(10);
    NoParameterNoReturn noParameterNoReturn = ()->System.out.println("无参数无返回值,方法体中只有 一行代码");
    noParameterNoReturn.test();
    //方法体中只有一条语句,且是return语句
    NoParameterReturn noParameterReturn = ()-> 40;
    int ret = noParameterReturn.test();
    System.out.println(ret);
    }
    }

    // 结果如下:
    无返回值多个参数,省略参数类型:20 30
    无参数一个返回值,小括号可以省略:10
    无参数无返回值,方法体中只有 一行代码
    40

    ```

可能还不直观,一般来说,大家只要向下面这样使用就行

```cpp
interface T{
    public int test(String name,int age);
}

public class Test {
        public static void main(String[] args) {
        T t = (name, age) -> {
            System.out.println(name + "  "+ age + " 岁了");
            return age + 1;
        };

        int age = t.test("qian", 18);
        System.out.println(age);
    }
}

// 输出如下:
qian  18 岁了
19
```

4. 变量捕获

🔥 Lambda 表达式中存在变量捕获 ,了解了变量捕获之后,我们才能更好的理解 Lambda 表达式 的作用域 。Java当中的匿名类中,会存在变量捕获。

🥝 1. 匿名内部类变量捕获

匿名内部类就是没有名字的内部类 。在前面的博客——>【Java 学习】:内部类详解 中提到了匿名内部类中变量的捕获。

  • 而我们这里只是为了说明变量捕获,所以,匿名内部类只要会使用就好
  • 匿名内部类中:一定是程序在运行的过程当中没有发生改变的量

那么下面我们来,下面就来看看匿名内部类的使用及其变量捕获

```java

//有参 有返回值
class T{
   public void func(){
       System.out.println("func()");
   }
}
public class Test {
        public static void main(String[] args) {
        int a = 100;
        new T(){
            // a = 666; // 如果把 a 在匿名内部类修改,就会报错
            @Override
            public void func() {
                System.out.println("a= " + a);
            }}.func();
    }
}

// 输出
a= 100
```

注意: 上面在匿名内部类注释的 a = 666,代码中会报错

原因如下:

  • *匿名内部类中可以访问外部方法的局部变量(如 a),但这个变量必须是 final 或 隐式 final,这里的 a 由于没有被修改,因此是隐式 final,所以可以安全访问。*

  • *你注释掉的代码 a = 666; 会导致编译错误,因为局部变量 a 在 Lambda 或匿名内部类中不可修改,必须保持其初始值*

🥝 2. Lambda的变量捕获

Lambda 表达式当中也可以进行变量的捕获

  • Lambda 表达式的变量捕获,同样也是不能捕获放生改变的,如果发生改变就会报错

具体我们看一下代码

```java
public class Test {
    @FunctionalInterface
    interface NoParameterNoReturn {
        void test();
    }
    public static void main(String[] args) {
        int a = 10;
        NoParameterNoReturn noParameterNoReturn = ()->{
        // a = 99; error
        System.out.println("捕获变量:"+a);
        };
        noParameterNoReturn.test();
    }
}

```

注意事项:

  • 这里类似于局部内部类、匿名内部类,依然存在闭包的问题。
  • 如果在 Lambda 表达式中,使用到了局部变量,那么这个局部变量会被隐式的声明为 final。是⼀个常量,不能修改值。

5. 函数引用

💦 Lambda 表达式是为了简化接口的实现的。在Lambda 表达式中,不应该出现比较复杂的逻辑。

  • 如果在 Lambda 表达式中出现了过于复杂的逻辑,会对程序的可读性造成非常大的影响。
  • 如果在 Lambda 表达式中需要处理的逻辑比较复杂,⼀般情况会单独的写⼀个方法。
  • Lambda 表达式中直接引用这个方法即可。

函数引用:引用⼀个已经存在的方法,使其替代lambda表达式完成接口的实现

🦄 1. 静态方法引用

语法

```java
类::静态方法
```

案例如下:

```java
interface T{
    int test(int a,int b);
}

class Cal{
    public static int add(int a,int b ){
        return a + b;
    }
}

public class Test {
    public static void main(String[] args) {
        //实现多个参数,一个返回值的接口
        //对一个静态方法的引用,语法:类::静态方法
        T t = Cal::add;
        System.out.println(t.test(4,5));
    }
}

// 输出结果:9

```

注意事项

  • 在引用的方法后面,不要添加小括号。
  • 引用的这个方法,参数(数量、类型)和返回值,必须要跟接口中定义的⼀致

🦄 2. 非静态方法引用

语法

```java
对象::非静态方法
```

案例如下:

```java
interface T{
    int test(int a,int b);
}

public class Test {
    private static class Cal{
        public int add(int a, int b) {
            return a+b;
         }
    }
    public static void main(String[] args) {
        // //对非静态方法的引用,需要使用对象来完成

        // 两种方法
        // 方法一
        Cal cal = new Cal();
        T t = cal::add;

        // 方法二:
        //T t = new Cal()::add;
        System.out.println(t.test(4,5));
    }
}
```

注意事项

  • 在引用的方法后面,不要添加小括号。
  • 引用的这个方法, 参数(数量、类型) 和 返回值, 必须要跟接口中定义的⼀致

🦄 3. 构造方法引用

使用场景

  • 如果某⼀个函数式接口中定义的方法,仅仅是为了得到⼀个类的对象。此时我们就可以使用构造方法的引用,简化这个方法的实现。

语法

```java
类名::new
```

案例如下:

```java
public class Test {
    private static class Student {
        String name;
        int age;

        //无参构造
        public Student() {
            System.out.println("学生对象的无参构造");
        }

        //有参构造
        public Student(String name, int age) {
            System.out.println("学生对象的有参构造");
            this.name = name;
            this.age = age;
        }
    }

    //定义一个函数式接口,用以获取无参的对象
    @FunctionalInterface
    private interface GetStudent {
        //若此方法仅仅是为了获得一个Student对象,而且通过无参构造去获取一个Student对象作为返回值
        Student s();
    }

    //定义一个函数式接口,用以获取有参的对象
    @FunctionalInterface
    private interface GetStudentWithParameter {
        //若此方法仅仅是为了获得一个Student对象,而且通过有参构造去获取一个Student对象作为返回值
        Student a(String name, int age);
    }

    // 测试
    public static void main(String[] args) {
        //lambda表达式实现接口
        GetStudent lm = Student::new; //引用到学生类中的无参构造方法,获取到一个学生对象
        Student s1 = lm.s();
        System.out.println("学生的名字:" + s1.name + " 学生的年龄:" + s1.age); //学生的名字:null 学生的年龄:0

        System.out.println("--------------------------------");

        GetStudentWithParameter lm2 = Student::new;//引用到Student类中的有参构造,来获取一个学生对象
        Student s2 = lm2.s("IsLand", 18);
        System.out.println("学生的名字:" + s2.name + " 学生的年龄:" + s2.age);//学生的名字:IsLand 学生的年龄:18

    }
}
```

这里 Student::new 是对 Student 类中无参构造方法的引用,使用它来实现 GetStudent 接口的 test() 方法。因此,lm.test() 会调用 Student 类的无参构造器,返回一个 Student 对象。在这种情况下,Student 对象的 name 和 age 字段会保持默认值:null 和 0。

注意事项 :可以通过接口中的方法的参数, 区分引用不同的构造方法。

6. Lambda 在集合中的使用

🔥 为了能够让Lambda和Java的集合类集更好的一起使用,集合当中,也新增了部分接口,以便与Lambda表达式对接。

  • 多提一句:要用Lambda遍历集合就一定要看懂源码

【Java】:lambda 表达式

注意:Collection的forEach()方法是从接口 java.lang.Iterable 拿过来的

🐇 1. Collection 接口

forEach() 方法演示

🥥 forEach()方法遍历集合。如果要打印元素,它需要的实现 Consumer接口,同时要实现重写 accept() 方法,它会把数组里的每一个元素都交给 accept()方法。

该方法在接口 Iterable 当中,原型如下:

```java
default void forEach(Consumer< ? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

```
  • *该方法表示:对容器中的每个元素执行action指定的动作*

    ```java
    public class Test {
    public static void main(String[] args) {
    ArrayList list = new ArrayList<>();
    list.add("Hello");list.add("IsLand");
    list.add("Hello");list.add("lambda");

        // 两种遍历遍历
        list.forEach(new Consumer() {
            @Override
                public void accept(String str) {
                //简单遍历集合中的元素。
                System.out.print(str + " ");
            }
        });
        System.out.println();
        System.out.println("----------------------------");
        //表示调用一个,不带有参数的方法,其执行花括号内的语句,为原来的函数体内容。
        list.forEach(s -> {System.out.print(s + " ");});
    }
    

    }

    // 输出:
    Hello IsLand Hello lambda


    Hello IsLand Hello lambda
    ```

🐇 2. List 接口

sort()方法的演示

sort方法源码:该方法根据c指定的比较规则对容器元素进行排序

```java
public void sort(Comparator< ? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}
```

使用示例:

```java
public class Test {
    public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        list.add("Hello");list.add("IsLand");
        list.add("Hello");list.add("lambda");

        // 两种排序方式
        list.sort(new Comparator() {
            @Override
                public int compare(String s1, String s2) {
                //注意这里比较长度
                return s1.length() - s2.length();
            }
        });
        System.out.println(list);

        System.out.println("----------------------------");

        // 修改为 Lambda 表达式
        //调用带有2个参数的方法,且返回长度的差值
        list.sort((s1, s2)->s1.length() - s2.length());
        System.out.println(list);
    }
}

// 输出如下:
[Hello, Hello, IsLand, lambda]
----------------------------
[Hello, Hello, IsLand, lambda]

```

🐇 3. Arrays 接口

```java
public class Test {
    public static void main(String[] args) {
        String[] a = {"program", "creek", "is", "a", "java", "site"};
        Arrays.sort(a, (m,n)->Integer.compare(m.length(), n.length())) ;
        System.out.println("Lambda 语句体只有一条语句,参数类型可推断: " + Arrays.toString(a));

        Arrays.sort(a, (String m, String n)->{
            if( m.length() > n.length()) return -1;
            else return 0;
        });
        System.out.println("Lambda 语句体有多条语句,参数类型可推断: " + Arrays.toString(a));
    }
}

// 输出结果如下:
Lambda 语句体只有一条语句,参数类型可推断: [a, is, java, site, creek, program]
Lambda 语句体有多条语句,参数类型可推断: [program, creek, java, site, is, a]
```

🐇 4. Map 接口

HashMap 的 forEach()
该方法原型如下:

```java
default void forEach(BiConsumer< ? super K, ? super V> action) {
    Objects.requireNonNull(action);
    for (Map.Entry entry : entrySet()) {
        K k;
        V v;
        try {
            k = entry.getKey();
            v = entry.getValue();
        }
        catch (IllegalStateException ise) {
            // this usually means the entry is no longer in the map.
            throw new ConcurrentModificationException(ise);
        }
        action.accept(k, v);
    }
}

```
  • *作用是对Map中的每个映射执行action指定的操作*

代码示例

```java
public class Test {
    public static void main(String[] args) {
        HashMap map = new HashMap<>();
        map.put(1, "Hello");map.put(2, "IsLand");
        map.put(3, "Hello");map.put(4, "lambda");

        map.forEach(new BiConsumer(){
            @Override
            public void accept(Integer k, String v){
                System.out.println(k + "=" + v);
            }
        });
        System.out.println("--------------------------------------");
        // 改为 Lambda 表达式
        map.forEach((k, v) -> System.out.println(k + "=" + v));
    }
}

// 运行结果如下:
1=Hello
2=IsLand
3=Hello
4=lambda
--------------------------------------
1=Hello
2=IsLand
3=Hello
4=lambda

```

7. 小结

Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读

优点:

  • 简化代码 :使得代码更加简洁,尤其是在处理集合、并行处理和事件监听时。
  • 增强可读性 :通过将代码行为和逻辑传递给方法,减少了冗长的匿名类实现。
  • 支持函数式编程 :Lambda 使得 Java 更加符合函数式编程范式,增强了代码的表达能力。
  • 能够与 Stream API 结合 :Lambda 表达式与 Java 8 引入的 Stream API 配合使用,可以更方便地进行集合的操作(如过滤、映射、聚合等)。

缺点:

  • 调试困难 :Lambda 表达式是匿名的,难以在调试时逐步跟踪。尤其是复杂的 Lambda 表达式可能使得代码的可调试性下降。
  • 过度使用 Lambda :如果 Lambda 表达式过于复杂或滥用,可能会导致代码的可读性下降,尤其是当 Lambda 的行为变得不直观时。
  • 性能问题 :虽然 Lambda 表达式的引入让代码更加简洁,但在某些情况下,Lambda 的性能可能不如传统的匿名类或普通方法调用,因为每次调用 Lambda 都涉及到对象的创建(比如生成函数式接口的代理)。

Lambda表达式有什么使用前提: 必须是接口的匿名内部类,接口中只能有一个抽象方法

  • 隐式 final :如果局部变量没有明确声明为 final,但在 Lambda 表达式中没有改变它的值,则编译器会隐式将其视为 final

文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/4809.html

(0)
LomuLomu
上一篇 2024 年 12 月 30 日
下一篇 2024 年 12 月 30 日

相关推荐

  • 深入理解 Java 接口的回调机制

    前言 回调是一种非常重要的编程技术,它广泛应用于事件驱动的编程、异步任务和框架设计中。在 Java 中,回调机制通常通过 接口 来实现。本篇博客将详细解析 Java 接口的回调原理、实现方式,以及实际开发中的应用场景。 泪崩了,期末JAVA编程考了回调,小编不会。 一、什么是回调? 回调(Callback) 是指通过将一个方法作为参数传递给另一个方法,在某些…

    2025 年 1 月 15 日
    16000
  • JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码)

    目录 JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码) 一、为什么要使用Array.sort() Array.sort() 是 JavaScript 提供的一个内置数组排序方法。它不仅仅是一个简单的升序或降序排列工具,更是一种灵活的排序逻辑实现方式。通…

    未分类 2024 年 12 月 28 日
    16600
  • Java刷题常见的集合类,各种函数的使用以及常见的类型转化等等

    目录 前言 集合类 ArrayList 1. 创建和初始化 ArrayList 2.添加元素 add 3.获取元素 get 4.删除元素 remove 5.检查元素 6.遍历 ArrayList LinkedList Stack 1. 创建Stack对象 2. 压入元素 (push) 3. 弹出元素 (pop) 4. 查看栈顶元素 (peek) 5. 检查栈…

    2025 年 1 月 5 日
    13100
  • Java Druid 面试题

    Druid连接池在项目中有哪些优势? 性能优越:Druid采用了高效的连接管理机制,可以快速地创建和回收数据库连接,减少了连接的创建和销毁带来的性能开销。 监控与统计:Druid提供了详细的监控信息,包括连接池的状态、SQL执行的统计信息等,这有助于性能调优和问题诊断。 SQL日志记录:Druid内置了SQL执行日志记录功能,可以记录所有SQL语句的执行情况…

    未分类 2025 年 1 月 10 日
    16800
  • Java Druid 面试题

    Druid连接池在项目中有哪些优势? 性能优越:Druid采用了高效的连接管理机制,可以快速地创建和回收数据库连接,减少了连接的创建和销毁带来的性能开销。 监控与统计:Druid提供了详细的监控信息,包括连接池的状态、SQL执行的统计信息等,这有助于性能调优和问题诊断。 SQL日志记录:Druid内置了SQL执行日志记录功能,可以记录所有SQL语句的执行情况…

    未分类 2025 年 1 月 10 日
    18000

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信