java8新特性3--lambda表达式

本文介绍如何通过行为参数化提高代码灵活性,并利用Java 8的Lambda表达式简化实现过程。

开发中,经常需要给一个方法传递一个接口类型的参数,为了传递不同的行为,我们需要创建不同的实现类,而通常这些类没什么重用价值,只使用一次就没用了。

考虑一个从一堆苹果中筛选符合条件的苹果的场景。

为了更好的应对需求变更,一个比较好的解决办法是将过滤的标准抽象出来,我们先定义一个接口作为抽象的选择标准.

public interface ApplePredicate{
    boolean test(Apple apple);
}

接下来就可以定义多个ApplePredicate接口的实现类来代表不同的过滤标准。

public class AppleHeavyWeightPredicate implements ApplePredicate{ 
    public boolean test(Apple apple){
        return apple.getWeight() > 150;
    }
}

//select only green apple
public class AppleGreenColorPredicate implements ApplePredicate{ 
    public boolean test(Apple apple){
        return "green".equals(apple.getColor);
    }
}

上面每一个实现了ApplePredicate接口的类都代表了一种筛选策略。

在此基础上,我们可以将筛选方法修改成下面的样子,将ApplePredicate作为参数传入。这就是所谓的行为参数化。

public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p){
    List<Apple> result = new ArrayList<>();
    for(Apple apple : inventory){
        if(p.test(apple)){
            result.add(apple);
        }
    }
    return result;
}

现在的筛选方法很灵活,如果想改变筛选标准,只需创建不同的ApplePredicate对象,并传入filterApples方法即可。

例如新增了选出红色并且重量大于150g的苹果的需求,我们可以创建一个实现ApplePredicate接口的类即可,代码如下:

public class AppleRedAndHeavyPredicate implements ApplePredicate{
    public boolean test(Apple apple){
        return "red".equals(apple.getColor()) && apple.getWeight() > 150;
    }
}
 
List<Apple> redAndHeavyApples = filter(inventory, new AppleRedAndHeavyPredicate());

但是上面的实现有一个缺点,就是太啰嗦了,每新增一个筛选标准都要新增一个类。下面来继续优化一下。

使用匿名类

匿名类是没有名字的类,使用匿名类可以创建一个临时的实现。下面的代码展示了如何利用匿名类创建实现了ApplePredicate的对象。

List<Apple> redApples = filterApples(inventory, new ApplePredicate(){
    public boolean test(Apple apple){
        return "red".equals(apple.getColor());
    }
});

但是尽管匿名类解决了为一个接口声明多个实现类的问题,使用匿名类还不足够好。使用匿名类代码看起来有些笨重,可读性差,而且有一些开发者对匿名类感到困惑。

Lambda

Java 8中推出了解决这个问题的新工具——Lambda表达式。它可以让你很简洁地表示一个行为或传递代码。

可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

  1. 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称!
  2. 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
  3. 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
  4. 简洁——无需像匿名类那样写很多模板代码。

利用Lambda 表达式,你可以更为简洁地自定义一个Comparator对象。

先前:

 Comparator<Apple> byWeight = new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }; 
 }

用了Lambda表达式后:

 Comparator<Apple> byWeight =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

不得不承认,代码看起来更清晰了!

Lambda 表达式的结构

上面的Lambda表达式我们可以将其分解成三个部分:

  1. 参数列表——这里它采用了Comparator中compare方法的参数,两个Apple。
  2. 箭头—— 箭头->把参数列表与Lambda主体分隔开。
  3. Lambda主体——a1.getWeight().compareTo(a2.getWeight()),表达式就是Lambda的返回值。

以下是一些 Lambda 表达式的例子:

(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

Runnable run= () -> System.out.println("Hello World");

Lambda表达式的特点:

  • 一个 Lambda 表达式可以有零个或多个参数

  • 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)与(a)效果相同

  • 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)

  • 空圆括号代表参数集为空。例如:() -> 42

  • 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> a*a

  • Lambda 表达式的主体可包含零条或多条语句

  • 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。

  • 匿名函数的返回类型与该主体表达式一致

  • 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。

  • 表达式就是Lambda的返回值,意味着你无需写return,若表达式没有返回则为空

  • 可以显示的通过return关键字返回值,但必须将主体写在{}中,如(Integer i) -> return "Alan" + i;是错误的写法

  • Lambda表达式可以用变量接收。

在哪里以及如何使用Lambda

现在你可能在想,在哪里可以使用Lambda表达式。在上一个例子中,你把Lambda赋给了一
个Comparator<Apple>类型的变量。

你也可以在上面的filter方法中使用Lambda:

List<Apple> greenApples =
filter(inventory, (Apple a) -> "green".equals(a.getColor())); 

事实上,你可以在函数式接口上使用Lambda表达式。

函数式接口

函数式接口就是只定义了一个抽象方法的接口。像Java中的Runnable,Comparator等等都只定义了一个方法,这种接口就都是函数式接口。

Java8中接口还可以拥有默认方法(即在类没有对方法进行实现时, 其主体为方法提供默认实现的方法)。哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。

继承了其他接口的接口,如果它总共的抽象方法不止一个,也不是函数式接口。

Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。你用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后 再直接内联将它实例化。

请注意,任何函数式接口都不允许抛出受检异常(checked exception)。如果你需要Lambda
表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda 包在一个try/catch块中。

示例:

//旧方法:
new Thread(new Runnable() {
@Override
public void run() {
    System.out.println("Hello from thread");
}
}).start();

//新方法:
new Thread(
() -> System.out.println("Hello from thread")
).start();

@FunctionalInterface

如果你去看看新的Java API,会发现函数式接口带有@FunctionalInterface的标注.

这个标注用于表示该接口会设计成 一个函数式接口。

如果你用@FunctionalInterface定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误,表明存在多个抽象方法。请注意,@FunctionalInterface不是必需的,但对于为此设计的接口而言,使用它是比较好的做法。它就像是@Override 标注表示方法被重写了。

我相信,从此你会对Lambda爱不释手的,赶紧用起来吧



作者:不迷失
链接:https://www.jianshu.com/p/a879229b64e9
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【顶级EI完整复现】【DRCC】考虑N-1准则的分布鲁棒机会约束低碳经济调度(Matlab代码实现)内容概要:本文介绍了名为《【顶级EI完整复现】【DRCC】考虑N-1准则的分布鲁棒机会约束低碳经济调度(Matlab代码实现)》的技术资源,聚焦于电力系统中低碳经济调度问题,结合N-1安全准则与分布鲁棒机会约束(DRCC)方法,提升调度模型在不确定性环境下的鲁棒性和可行性。该资源提供了完整的Matlab代码实现,涵盖建模、优化求解及仿真分析全过程,适用于复杂电力系统调度场景的科研复现与算法验证。文中还列举了大量相关领域的研究主题与代码资源,涉及智能优化算法、机器学习、电力系统管理、路径规划等多个方向,展示了广泛的科研应用支持能力。; 适合人群:具备一定电力系统、优化理论和Matlab编程基础的研究生、科研人员及从事能源调度、智能电网相关工作的工程师。; 使用场景及目标:①复现高水平期刊(如EI/SCI)关于低碳经济调度的研究成果;②深入理解N-1安全约束与分布鲁棒优化在电力调度中的建模方法;③开展含新能源接入的电力系统不确定性优化研究;④为科研项目、论文撰写或工程应用提供可运行的算法原型和技术支撑。; 阅读建议:建议读者结合文档提供的网盘资源,下载完整代码与案例数据,按照目录顺序逐步学习,并重点理解DRCC建模思想与Matlab/YALMIP/CPLEX等工具的集成使用方式,同时可参考文中列出的同类研究方向拓展研究思路。
内容概要:本文详细介绍了一个基于MATLAB实现的电力负荷预测项目,采用K近邻回归(KNN)算法进行建模。项目从背景意义出发,阐述了电力负荷预测在提升系统效率、优化能源配置、支撑智能电网和智慧城市建设等方面的重要作用。针对负荷预测中影响因素多样、时序性强、数据质量差等挑战,提出了包括特征工程、滑动窗口构造、数据清洗与标准化、K值与距离度量优化在内的系统性解决方案。模型架构涵盖数据采集、预处理、KNN回归原理、参数调优、性能评估及工程部署全流程,并支持多算法集成与可视化反馈。文中还提供了MATLAB环境下完整的代码实现流程,包括数据加载、归一化、样本划分、K值选择、模型训练预测、误差分析与结果可视化等关键步骤,增强了模型的可解释性与实用性。; 适合人群:具备一定MATLAB编程基础和机器学习基础知识,从事电力系统分析、能源管理、智能电网或相关领域研究的研发人员、工程师及高校师生;适合工作1-3年希望提升实际项目开发能力的技术人员; 使用场景及目标:①应用于短期电力负荷预测,辅助电网调度与发电计划制定;②作为教学案例帮助理解KNN回归在实际工程中的应用;③为新能源接入、需求响应、智慧能源系统提供数据支持;④搭建可解释性强、易于部署的轻量级预测模型原型; 阅读建议:建议结合MATLAB代码实践操作,重点关注特征构造、参数调优与结果可视化部分,深入理解KNN在时序数据中的适应性改进方法,并可进一步拓展至集成学习或多模型融合方向进行研究与优化。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值