Java Lambda表达式详解

本文深入探讨函数式编程的概念,特点及优缺点,详细解析Lambda表达式在Java中的应用,包括语法、底层实现及常见场景,如替代匿名类、处理数据流、并行处理等。

一.什么是函数式编程

函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。和过程化编程相比,函数式编程里函数的计算可随时调用。

1.函数式编程的特点 

函数是"第一等公民":指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。举例来说,下面代码中的print变量就是一个函数,可以作为另一个函数的参数。var print = function(i){ console.log(i);};    [1,2,3].forEach(print);
只用"表达式",不用"语句":"表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
没有"副作用":,指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
不修改状态:函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是它的一个重要特点。
引用透明性:函数程序通常还加强引用透明性,即如果提供同样的输入,那么函数总是返回同样的结果。
副作用:副作用是修改系统状态的语言结构。

2.函数式编程的优缺点

优点:

代码简洁,开发快速:函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。
接近自然语言,易于理解:函数式编程的自由度很高,可以写出很接近自然语言的代码。将表达式(1 + 2) * 3 - 4,写成函数式语言:subtract(multiply(add(1,2), 3), 4)
更方便的代码管理:函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。
易于"并发编程":函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)。
代码的热升级:函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。所以,可以在运行状态下直接升级代码,不需要重启,也不需要停机。

缺点:

函数式编程常被认为严重耗费在CPU和存储器资源。早期的函数式编程语言实现时并无考虑过效率问题;有些非函数式编程语言为求提升速度,不提供自动边界检查或自动垃圾回收等功能;惰性求值亦为语言增加了额外的管理工作。
因为其灵活的语法控制不好程序的结构,带来了语言学习难度高,代码维护性差等缺点。

 

二.什么是Lambda

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

1.Lambda表达式的优缺点

优点:

少量的代码就能替代以前的一大堆循环判断过滤等,代码简洁。


缺点:

用Lambda充当匿名内部类、方法引用等场合效率低。
Lambda的特点还在于开发成本高,并且异常难以排查。它的异常堆栈比匿名内部类还要难懂。如果你把stream的操作都写在同一行,则问题更甚。
代码维护性差。


2.Lambda表达式的语法

Java8中Lambda 表达式由三个部分组成:

第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;

第二部分为一个箭头符号:->;

第三部分为方法体,可以是表达式和代码块。

  • 方法体为表达式,该表达式的值作为返回值返回。

(parameters) -> expression
 //求和
(int a,int b) -> return a + b;
  • 方法体为代码块,必须用 {} 来包裹起来,且需要一个return返回值,但函数式接口里面方法返回值是 void,无需返回值。

(parameters) -> { statements; }
 
 //求平方
(int a) -> {return a * a;}
 
//打印,无返回值
(int a) -> {System.out.println("a = " + a);}
  • Lambda表达式的重要特征

可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值

  • Lambda表达式的底层实现

java8内部Lambda表达式的实现方式在本质是以匿名内部类的形式的实现的。

package Lambda; 
import java.util.function.IntBinaryOperator;
 
/**
 * test
 * @author lc
 */
public class Test {
    public static void main(String[] s) {
        /**
         * 定义了一个叫binaryOperator的Lambda表达式,返回值是一个IntBinaryOperator实例。
         */
        IntBinaryOperator binaryOperator = (int a, int b) -> {
            return a + b;
        };
        /**
         * 利用binaryOperator计算
         */
        int result = binaryOperator.applyAsInt(1,2);
        System.out.println(result);
    }
}

查看IntBinaryOperator这个接口,发现这是一个被@FunctionalInterface注解的接口,@FunctionalInterface标注了这是一个函数式接口,所以(int a, int b) -> {return a + b;}返回的一个IntBinaryOperator的匿名实现类。

@FunctionalInterface
public interface IntBinaryOperator {
 
    /**
     * Applies this operator to the given operands.
     *
     * @param left the first operand
     * @param right the second operand
     * @return the operator result
     */
    int applyAsInt(int left, int right);
}
  • Lambda表达式的函数式接口

1.函数式接口(Functional Interface)是Java 8对一类特殊类型的接口的称呼。这类接口只定义了唯一的抽象方法的接口(除了隐含的Object对象的公共方法,因此最开始也就做SAM类型的接口(Single Abstract Method)。
2.定义函数式接口的原因是在Java Lambda的实现中,开发组不想再为Lambda表达式单独定义一种特殊的Structural函数类型,称之为箭头类型(arrow type,依然想采用Java既有的类型(class, interface, method等)。
3.增加一个结构化的函数类型会增加函数类型的复杂性,破坏既有的Java类型,并对成千上万的Java类库造成严重的影响。权衡利弊,因此最终还是利用SAM 接口作为 Lambda表达式的目标类型。
4.对于函数式接口来说@FunctionalInterface并不是必须的,只要接口中只定义了唯一的抽象方法的接口那它就是一个实质上的函数式接口,就可以用来实现Lambda表达式。

Java8中已经定义了很多常用的函数式接口,它们都放在java.util.function包下面,一般有以下常用的四大核心接口:

 

三.Lambda具体应用场景

1.使用() -> {} 替代匿名类

package Lambda;
 
/**
 * @author lc
 */
public class ThreadDemo {
    public static void main(String[] s) {
        /**
         * 不使用Lambda
         */
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("no use lambda");
            }
        });
        /**
         * 使用Lambda,代码要简洁一些
         */
        Thread t2 = new Thread(() -> System.out.println("use lambda"));
 
        t1.run();
        t2.run();
    }
}

2.以流水线的方式处理数据

/**
 * Stream
 * @author lc
 */
public class StreamDemo {
    public static void main(String[] s) {
        List<Integer> integers = Arrays.asList(4, 5, 6,1, 2, 3,7, 8,8,9,10);
        //过滤出偶数列表 [4,6,8,8,10]
        List<Integer> evens = integers.stream().filter(i -> i % 2 == 0).collect(Collectors.toList()); 
        //转成平方列表
        List<Integer> squareList = integers.stream().map(i -> i * i).collect(Collectors.toList());
    }
}

3.数据并行处理

数据并行处理,只需要在原来的基础上加一个parallel()就可以开启,这里parallel()开启的底层并行框架是fork/join,默认的并行数是Ncpu个。

//转成平方列表,并行处理
List<Integer> squareList1 = integers.stream().parallel().map(i -> i * i).collect(Collectors.toList());

4. 用内部迭代取代外部迭代

package Lambda;
 
import java.util.Arrays;
import java.util.List;
 
/**
 * forEach
 * @author lc
 */
public class FechEachDemo {
 
    public static void main(String[] s) {
        //外部迭代
        List<String> features1 = Arrays.asList("Lambdas", "Default Method", "StreamAPI");
        for (String feature : features1) {
            System.out.println(feature);
        }
        //内部迭代
        List<String> features2 = Arrays.asList("Lambdas", "Default Method", "StreamAPI");
        features2.stream().forEach(n -> System.out.println(n));
    }
}

转至:https://blog.youkuaiyun.com/lcgoing/article/details/87886599@CRUD小王子的小笔记

<think>嗯,用户让我介绍一下JavaLambda表达式,我需要详细解释清楚。首先,我应该回顾一下Lambda表达式的基本概念,然后逐步展开。用户可能是刚开始学习Java的新手,或者已经有一定基础但想深入了解Lambda的人。我需要确保内容结构清晰,容易理解。 首先,Lambda表达式是在Java 8引入的,所以需要提到版本要求。然后,说明它的作用,主要是简化匿名内部类,尤其是函数式接口的实现。接下来,可能需要解释什么是函数式接口,因为这是Lambda的关键。 然后,语法部分很重要。Lambda的基本结构是参数、箭头和方法体。要举几个例子,比如无参数、单个参数、多个参数的情况,以及什么时候可以省略括号和类型声明。这部分可能需要用代码示例来说明,这样用户更容易理解。 接下来,应用场景。比如在集合的遍历中使用forEach,结合Stream API进行过滤、映射等操作。还有线程中的Runnable例子,比较传统匿名内部类和Lambda写法的区别,突出Lambda的简洁性。 然后,注意事项部分。变量捕获,特别是effectively final的概念,还有Lambda的底层实现,比如 invokedynamic指令,这样用户知道Lambda在JVM中的处理方式。另外,方法引用和构造器引用也可以提一下,作为Lambda的简化形式。 可能用户会想知道为什么Lambda只能用在函数式接口,或者Lambda和匿名类的区别是什么,这些需要在注意事项里提到。此外,Lambda的作用域和变量访问规则也需要解释清楚,避免常见的错误。 最后,总结一下Lambda的优点,比如代码简洁、函数式编程支持、提升可读性等。可能还可以提到Lambda在实际开发中的常见用途,比如处理集合、事件处理等。 在组织内容时,要避免使用过于专业的术语,或者如果用了,要适当解释。比如“函数式接口”需要说明是只有一个抽象方法的接口。代码示例要简洁明了,对比传统写法和Lambda写法,让用户直观看到区别。 还要注意用户的潜在需求,他们可能不仅想知道Lambda的语法,还想了解如何在实际项目中应用,所以应用场景部分需要具体一些,给出实际的例子,比如使用Stream处理集合数据,或者用Lambda替代匿名监听器实现。 另外,可能需要提醒用户Lambda的局限性,比如不能独立存在,必须依附于函数式接口,以及调试时可能遇到的困难,因为Lambda没有显式的名称。 最后检查一下内容是否覆盖了Lambda的主要知识点,有没有遗漏重要部分,比如变量捕获、方法引用等。确保逻辑连贯,由浅入深,让用户能够逐步理解。</think>Java Lambda 表达式Java 8 引入的重要特性,主要用于简化函数式接口(Functional Interface)的实现,提升代码的简洁性和可读性。以下是详细解析: --- ### **1. Lambda 表达式基础** - **核心作用**:替代匿名内部类,简化代码。 - **语法结构**: ```java (参数列表) -> { 方法体 } ``` - 参数类型可省略(编译器自动推断)。 - 单参数时括号可省略(如 `x -> x + 1`)。 - 单行方法体可省略大括号和 `return`。 **示例对比**: ```java // 传统匿名内部类 Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Hello"); } }; // Lambda 表达式 Runnable r2 = () -> System.out.println("Hello"); ``` --- ### **2. 函数式接口(Functional Interface)** - **定义**:仅包含一个抽象方法的接口(如 `Runnable`, `Comparator`)。 - 常用内置接口: - `Consumer<T>`:接受一个输入,无返回(`void accept(T t)`)。 - `Supplier<T>`:无输入,返回一个值(`T get()`)。 - `Function<T, R>`:接受 T 类型输入,返回 R 类型结果(`R apply(T t)`)。 - `Predicate<T>`:接受输入,返回布尔值(`boolean test(T t)`)。 **示例**: ```java // 使用 Predicate 过滤集合 List<Integer> numbers = Arrays.asList(1, 2, 3, 4); List<Integer> even = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); // 输出 [2, 4] ``` --- ### **3. Lambda 表达式应用场景** #### **场景 1:集合遍历** ```java List<String> list = Arrays.asList("A", "B", "C"); list.forEach(s -> System.out.println(s)); // 等价于 forEach(System.out::println) ``` #### **场景 2:线程初始化** ```java new Thread(() -> System.out.println("线程运行")).start(); ``` #### **场景 3:排序** ```java List<String> words = Arrays.asList("apple", "banana", "cherry"); words.sort((s1, s2) -> s1.length() - s2.length()); ``` --- ### **4. 变量捕获与限制** - **规则**:Lambda 可以访问外部的 `final` 或 `effectively final`(隐式 final)变量。 - **示例**: ```java int offset = 10; Function<Integer, Integer> adder = x -> x + offset; // offset 必须为 effectively final ``` --- ### **5. 方法引用与构造器引用** - **语法**:进一步简化 Lambda- **静态方法引用**:`ClassName::methodName` - **实例方法引用**:`instance::methodName` - **构造器引用**:`ClassName::new` **示例**: ```java // 方法引用替代 Lambda List<String> names = Arrays.asList("Alice", "Bob"); names.forEach(System.out::println); // 等价于 s -> System.out.println(s) // 构造器引用 Supplier<List<String>> listSupplier = ArrayList::new; ``` --- ### **6. 注意事项** 1. **类型推断**:编译器根据上下文推断参数类型。 2. **作用域**:Lambda 内部 `this` 指向外层上下文,而非 Lambda 自身。 3. **性能**:Lambda 通过 `invokedynamic` 指令实现,性能接近传统代码。 --- ### **7. 总结** - **优点**: - 代码简洁,减少模板代码。 - 支持函数式编程风格(如 Stream API)。 - **适用场景**:集合操作、事件处理、多线程等。 通过 Lambda 表达式Java 实现了更现代的编程范式,尤其在处理集合和异步任务时显著提升开发效率。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值