java8 Lambda的性能跟传统的相比怎样?

本文深入探讨Java8中Lambda表达式的概念、语法及原理,对比传统迭代器与Lambda结合流的性能,实测显示传统方法在某些场景下性能更优。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文系转载文章

原文链接:http://www.ccblog.cn/85.htm

目录

函数式接口

Lambda语法

Lambda原理

Lambda性能如何?


        Java8发布,Lambda表达式作为一项重要的特性随之而来。或许现在你已经在使用Lambda表达式来书写简洁灵活的代码。

Lambda 表达式是一种匿名函数(对 Java 而言这并不完全正确,但现在姑且这么认为),

简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。

        你可以将其想做一种速记,在你需要使用某个方法的地方写上它。当某个方法只使用一次,而且定义很简短,使用这种速记替代之尤其有效,这样,你就不必在类中费力写声明与方法了。

函数式接口

        函数式接口(functional interface)。简单来说,函数式接口是只包含一个方法的接口。比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。

        java 8提供 @FunctionalInterface作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),

        虚拟机会自动判断,但 最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。 

        Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现,

Lambda语法

        包含三个部分

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

        一个箭头符号:->

        方法体,可以是表达式和代码块,方法体函数式接口里面方法的实现,如果是代码块,则必须用{}来包裹起来,且需要一个        return 返回值,但有个例外,若函数式接口里面方法返回值是void,则无需{}

        (parameters) -> expression 或者 (parameters) -> { statements; }

        我们看个线程的demo

public class TestLambda {
    
    //lambda实现
    public static void runThreadUseLambda() {
        new Thread(() ->System.out.println("我是lambda实现的线程")).start(); 
    }
    
    //内部类实现 
    public static void runThreadUseInnerClass() {
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("内部类实现的线程");
            }
        }).start();
    }
    public static void main(String[] args) {
        TestLambda.runThreadUseLambda();
        TestLambda.runThreadUseInnerClass();
    }
}

Lambda原理

        以上是通过不同的方法来实现线程的,那么我们来看下JVM编译执行过程的原理是不是一样的?

        通过 javap -c TestLambda查看字节码 

 

        通过上面的对比发现红色部分不太一样Lambda是InvokeDynamic 内部类是 invokespecial,那么是什么意思呢?

        我们先温习下jvm指令

        invokeinterface:调用接口方法;

        invokespecial:专门用来调用父类方法、私有方法和初始化方法;

        invokestatic:调用静态方法;

        invokevirtual:调用对象的一般方法。

        这四个指令所对应的类、调用的方法在编译时几乎是固定的:invokestatic所对应的类为静态方法所在的类,方法为静态方法本身;invokespecial所对应的类为当前对象,方法是固定的;invokeinterface和invokevirtual所对应的类也为当前对象,方法可以因为继承和实现进行选择,但也仅限于整个继承体系中选择。

        在java7 JVM中增加了一个新的指令invokedynamic,用于支持动态语言,即允许方法调用可以在运行时指定类和方法,不必在编译的时候确定。

        字节码中每条invokedynamic指令出现的位置称为一个动态调用点,invokedynamic指令后面会跟一个指向常量池的调用点限定符,这个限定符会被解析为一个动态调用点。

        我们发现Lambda采用的是invokedynamic指令,所以Lambda还是有别于普通方式的调用的。

        我们在来看下List中Lambda的使用。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class TestLambdaList {
    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setId(i);
            user.setUserName("un" + i);
            user.setPassword("password" + i);
            userList.add(user);
        }
        // 通过Lambda取出User里面id的值
        List<Integer> idList_LB = userList.stream().map(user -> user.getId()).collect(Collectors.toList());
        System.out.println(idList_LB);
        // 普通方法
        List<Integer> idList_PT = new ArrayList<>();
        for (User user : userList) {
            idList_PT.add(user.getId());
        }
        System.out.println(idList_PT);
    }
}
class User {
    Integer id;
    String  userName;
    String  password;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

        以上是通过两种不同的方式实现取(出User里面id的值),很明显使用Lambda要简洁很多。

Lambda性能如何?

        有许许多多关于 Java 8 中流效率的讨论,但根据 Alex Zhitnitsky 的测试结果显示:坚持使用传统的 Java 编程风格——iterator 和 for-each 循环——比 Java 8 的实现性能更佳。他们测出的结果是如下图

        他们的结论:Java 8 中提供的任何一种新方式都会产生约 5 倍的性能差异。有时使用简单迭代器循环比混合 lambda 表达式和流更有效,即便这样需要多写几行代码,且需要跳过甜蜜的语法糖(syntactic suger)。

使用迭代器或 for-each 循环是遍历 ArrayList 最有效的方式,性能比采用索引值的传统 for 循环方式好两倍。

在 Java 8 的方法中,并行流的性能最佳。但是请小心,在某些情况下它也可能会导致程序运行得更慢。

 

        我们自己来亲自测试下到底怎么样

        将上面的代码添加个时间看下耗时情况

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class TestLambdaList {
    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            User user = new User();
            user.setId(i);
            user.setUserName("un" + i);
            user.setPassword("password" + i);
            userList.add(user);
        }
        // 通过Lambda取出User里面id的值
        long t1=System.currentTimeMillis();
        List<Integer> idList_LB = userList.stream().map(user -> user.getId()).collect(Collectors.toList());
        //System.out.println(idList_LB);
        System.out.println(System.currentTimeMillis()-t1);
        // 普通方法
        long t2=System.currentTimeMillis();
        List<Integer> idList_PT = new ArrayList<>();
        for (User user : userList) {
            idList_PT.add(user.getId());
        }
        System.out.println(System.currentTimeMillis()-t2);
       // System.out.println(idList_PT);
    }
}
class User {
    Integer id;
    String  userName;
    String  password;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

 

测试1:在一个Main方法里面执行完
i=100     多次测试的结果是:Lambda耗时在120左右。但是foreach每次的结果都是0;
i=100000   多次测试的结果是:Lambda耗时在120左右。但是foreach每次的结果都是10秒以内;
i=1000000  多次测试的结果是:Lambda耗时在120左右。但是foreach每次的结果都是10秒以内;  
i=10000000  多次测试的结果是:Lambda耗时在120左右。但是foreach每次的结果都是130秒以内; 


测试2:分别在一个Main方法里面执行完各自执行,(将其中的一个注释掉)
i=100     多次测试的结果是:Lambda耗时在100左右。但是foreach每次的结果都是0;
i=100000   多次测试的结果是:Lambda耗时在100-120左右。但是foreach每次的结果都是12-16秒以内;
i=1000000 多次测试的结果是:Lambda耗时在120~180左右。但是foreach每次的结果都是30-50秒以内;  
i=10000000  多次测试的结果是:Lambda耗时在120~180左右。但是foreach每次的结果都是40~60秒以内;

        由此可见:使用简单迭代器循环比混合 lambda 表达式和流更有效.

总结:

        开始使用 Java 8 的第一件事情是在实践中使用 lambda 表达式和流。但是请记住:它确实非常好,好到可能会让你上瘾!但是,我们也看到了,使用传统迭代器和 for-each 循环的 Java 编程风格比 Java 8 中的新方式性能高很多。

当然,这也不是绝对的。但这确实是一个相当常见的例子,它显示可能会有大约 5 倍的性能差距。如果这影响到系统的核心功能或成为系统一个新的瓶颈,那就相当可怕了。

<think>在Java 8中,lambda表达式和switch case是两个独立的特性,但可以结合使用。虽然lambda表达式本身不能直接作为switch的表达式或case分支,但可以在switch的case代码块中使用lambda表达式,或者将switch封装在lambda表达式中。以下是几种结合方式: ### 1. **在switch case代码块中使用lambda表达式** 可以在某个case分支内定义并调用lambda表达式。 ```java import java.util.function.IntSupplier; public class SwitchWithLambdaInCase { public static void main(String[] args) { int operation = 2; switch (operation) { case 1: // 定义并调用一个lambda表达式 IntSupplier square = () -> 10 * 10; System.out.println("平方结果: " + square.getAsInt()); break; case 2: // 另一个lambda IntSupplier cube = () -> 10 * 10 * 10; System.out.println("立方结果: " + cube.getAsInt()); break; default: System.out.println("默认操作"); } } } ``` ### 2. **将switch逻辑封装在lambda表达式中** 将整个switch逻辑定义为一个函数式接口的实现(如`Supplier`、`Function`),然后通过lambda表达式实现多分支逻辑。 ```java import java.util.function.Function; public class SwitchEncapsulatedInLambda { public static void main(String[] args) { // 定义一个函数,根据输入字符串返回处理逻辑 Function<String, Runnable> actionMapper = (String day) -> { switch (day) { case "MONDAY": return () -> System.out.println("启动工作"); case "FRIDAY": return () -> System.out.println("准备周末"); default: return () -> System.out.println("日常活动"); } }; // 使用 Runnable mondayAction = actionMapper.apply("MONDAY"); mondayAction.run(); // 输出:启动工作 } } ``` ### 3. **在流式操作中使用switch模拟分支** 在Java 8的Stream API中,可以通过`filter`和`map`模拟分支逻辑,但复杂分支仍适合用switch。 ```java import java.util.stream.Stream; public class StreamWithSwitch { public static void main(String[] args) { String input = "A"; Stream.of(input) .map(s -> { switch (s) { case "A": return "选项A"; case "B": return "选项B"; default: return "未知"; } }) .forEach(System.out::println); // 输出:选项A } } ``` ### 4. **注意事项** - **作用域限制**:在lambda表达式中使用外部变量时,该变量必须是`final`或等效final(effectively final)。 - **可读性**:过度嵌套会降低代码可读性,建议将复杂逻辑拆分为独立方法。 - **性能**:简单switch在性能上优于lambda,因为lambda涉及对象创建(如`Function`实例)。 ### 5. **更高级结合:策略模式+枚举** 对于复杂分支,推荐使用策略模式,结合枚举和lambda实现: ```java import java.util.function.Consumer; enum Operation { PRINT_HELLO(() -> System.out.println("Hello")), PRINT_WORLD(() -> System.out.println("World")); private final Runnable action; Operation(Runnable action) { this.action = action; } public void execute() { action.run(); } } public class EnumStrategyPattern { public static void main(String[] args) { Operation op = Operation.PRINT_HELLO; op.execute(); // 输出:Hello } } ``` ### 结论 虽然Java 8lambda表达式不能直接替代switch语法,但可以通过上述方式结合: 1. **在case块内使用lambda**:实现分支内的灵活逻辑。 2. **将switch封装为函数式接口**:通过lambda传递分支行为。 3. **在流中使用switch**:处理元素时进行分支转换。 4. **策略模式+枚举**:更面向对象的设计,避免大型switch。 这些方法在保持代码简洁性的同时,充分利用了Java 8的函数式特性[^1][^2]。 §§相关问题§§ 1. Java 8中如何在lambda表达式内使用switch case? 2. 与传统的switch case相比,结合lambda的方式有哪些优缺点? 3. Java 8的流式操作中如何处理多分支逻辑? 4. 策略模式如何替代复杂的switch case结构? 5. Java 12的switch表达式与lambda表达式结合有何改进?</think>### Java 8lambda表达式与switch case的结合使用详解 在Java 8中,虽然lambda表达式不能直接作为`switch`的表达式或`case`分支,但可以通过以下几种方式实现有效结合: --- #### 1. **在case代码块中使用lambda表达式** 在`switch`的某个分支中定义并执行lambda表达式: ```java import java.util.function.IntSupplier; public class LambdaInSwitchCase { public static void main(String[] args) { int operation = 2; switch (operation) { case 1: // 在case中定义lambda表达式 IntSupplier square = () -> 5 * 5; System.out.println("平方结果: " + square.getAsInt()); break; case 2: // 另一个lambda IntSupplier cube = () -> 5 * 5 * 5; System.out.println("立方结果: " + cube.getAsInt()); break; default: System.out.println("无效操作"); } } } ``` **输出:** ``` 立方结果: 125 ``` --- #### 2. **将switch封装在lambda表达式中** 将整个`switch`逻辑封装为函数式接口的实现: ```java import java.util.function.Function; public class SwitchInsideLambda { public static void main(String[] args) { // 定义函数:根据颜色返回操作 Function<String, Runnable> colorAction = (String color) -> { switch (color.toUpperCase()) { case "RED": return () -> System.out.println("停止"); case "GREEN": return () -> System.out.println("前进"); case "YELLOW": return () -> System.out.println("减速"); default: return () -> System.out.println("无效颜色"); } }; // 使用 colorAction.apply("green").run(); // 输出:前进 } } ``` --- #### 3. **在Stream API中使用switch** 结合Stream和`switch`处理集合元素: ```java import java.util.stream.Stream; public class SwitchInStream { public static void main(String[] args) { Stream.of("A", "B", "C") .forEach(s -> { switch (s) { case "A": System.out.println("优秀"); break; case "B": System.out.println("良好"); break; default: System.out.println("待提升"); } }); } } ``` **输出:** ``` 优秀 良好 待提升 ``` --- #### 4. **策略模式 + 枚举 + Lambda** 更优雅的替代方案(推荐): ```java import java.util.function.Consumer; enum Operation { ADD(x -> System.out.println("结果: " + (x + x))), MULTIPLY(x -> System.out.println("结果: " + (x * x))); private final Consumer<Integer> action; Operation(Consumer<Integer> action) { this.action = action; } public void execute(int num) { action.accept(num); } } public class StrategyPatternExample { public static void main(String[] args) { Operation.ADD.execute(5); // 输出:结果: 10 Operation.MULTIPLY.execute(5); // 输出:结果: 25 } } ``` --- ### 关键注意事项 1. **作用域限制** 在lambda内使用外部变量时,该变量必须是`final`或等效final(effectively final): ```java int base = 10; // 等效final Function<Integer, Integer> multiplier = x -> { switch (x) { case 2: return base * 2; // 合法 default: return base; } }; ``` 2. **性能考虑** 简单分支逻辑直接用`switch`更高效,lambda会创建额外对象。 3. **可读性平衡** 复杂逻辑建议用策略模式替代大型`switch`,避免嵌套过深: ```java // 不推荐:多层嵌套降低可读性 x -> { switch(a) { case 1: { switch(b) {...} } } } ``` --- ### 适用场景对比 | **场景** | **传统switch** | **switch+lambda** | |--------------------------|----------------|-------------------------| | 简单值匹配 | ✅ 最佳 | ⚠️ 过度复杂 | | 分支需返回函数 | ❌ 不支持 | ✅ 完美适用 | | 流式处理中的条件分支 | ❌ 不直观 | ✅ 流畅集成 | | 需动态生成分支逻辑 | ❌ 硬编码 | ✅ 灵活实现 | --- ### 总结 Java 8中可通过三种方式结合lambda与switch: 1. **case内定义lambda**:适合分支逻辑独立且简单的场景 2. **封装switch为函数**:适合需要返回函数对象的场景 3. **Stream中嵌入switch**:适合集合元素的动态处理 对于复杂分支逻辑,**策略模式+枚举+lambda**是更面向对象且可维护的替代方案,能避免大型switch语句的膨胀问题[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值