Java函数式编程:一、icode9函数式接口,lambda表达式和方法引用


Java函数式编程

什么是函数式编程

通过整合现有代码来产生新的功能,而不是从零开始编写所有内容,由此我们会得到更加可靠的代码,并获得更高的效率

我们可以这样理解:面向对象编程抽象数据,函数式编程抽象行为。

通常而言,方法会根据所传递的数据产生不同的结果,但如果想让一个方法在每次调用时都有不同的表现该怎么办?你可能会想到多态,没错,多态是一种通过改变实际执行的方法所属对象,以此来改变实际执行的方法代码的方式。而这里我们要聊聊其他的两种方法。

如果我们直接将代码传递给方法,就可以控制其行为,Java 8以后,我们可以通过lambda表达式和方法引用这两个新的方法来实现这一点。


1、函数式接口

什么是函数式接口

什么是函数式接口?这是我们理解Lambda表达式以及方法引用的重点,这些接口是lambda表达式和方法引用的目标类型,这里我们引用一个比较容易理解的说法:函数式接口是一个只有一个抽象方法 (不包含祖先类Object中的公共方法,如hashcode()等) 的接口

当我们在编写接口时,这种函数式方法模式可以使用@FunctionalInterface注解来强制实施,如果被注解的接口不符合标准那么就会在编译时报错。下面给出例子:

interface Functional{// 是函数式接口
    String speak();
}

interface NoFunctional{// 不是函数式接口
    String speak();
    String laugh();
}

interface IsFunctional{// 是函数式接口,因为toString()是Object祖先类的公共方法,不算在内
    String speak();
    String toString();
}

它们的意义是什么呢?

这里拿出一个例子:

interface Say{
    void say();
}

class Speaker{
    public static void speak(){
        System.out.println("Hello, my friend!");
    }
    
    public static void main(String[] args){
        Say say = Speaker::speak; // 这里的::表示我们引用了Speaker类的speak方法
        say.say();
        // 输出Hello, my friend!
    }
}

很神奇是不是?我们直接将一个方法引用赋给了一个接口的对象。这里初看的话显然问题一堆,首先方法怎么能作为对象赋值,其次该类也没有实现该接口,最后,就算能说通,我们的接口和Speaker类根本没有相同的方法啊!怎么就调用say.say()效果等同于Speaker.speak()呢?

重要:这是Java 8增加的一个小魔法:如果我们将一个方法引用或一个lambda表达式赋给一个函数式接口(且两个方法的返回值类型参数类型可以匹配上,方法名并不重要),那么Java会自动调整这个赋值操作,使其能够匹配目标接口。
对于底层来说,这里是Java编译器创建一个实现了目标接口的类的实例,并将我们的方法引用或lambda表达式包裹在其中。

事实上,很容易预见,在这里如果我们直接将一个Speaker对象赋给一个Say接口,那么是无法做到的,因为Speaker虽然符合Say的模式,但却没有显式的实现Say接口。幸运的是,Java 8的函数式接口允许我们直接把一个实例方法赋给这样的一个接口,这样语法更好,更简单。

Java为我们准备了大量的函数式接口,这样我们就可以尽量避免自己创建大量的接口,这些接口都可以在Java.util.function包中轻松找到。
不过Java最多只准备了具有两个参数的函数式接口,但是接口又不难写,只要我们理解了函数式接口的意义和用法,自己写一个能容纳更多参数的函数式接口不过是信手拈来罢了。

// 举个例子,一个有四个泛型参数的函数式接口
@FunctionalInterface
public interface TriFunction<T, U, V, R>{
    R apply(T t, U u, V v, R r);
}

补充

在Java给出的函数式接口中,我们只能看到一部分涉及基本类型的函数式接口。其余都是由泛型完成的,为什么要这么做呢?估计就是因为对于某些很常用的函数式接口,如整形输入+Double输出这样。如果我们采用泛型Function<Integer, Double>来实现,就涉及到自动装箱和自动拆箱的性能问题,如果该方法会被大量调用,那么还是直接声明清楚其中的类型对于我们的整体性能更为有利


2、Lambda表达式

lambda表达式本质上是一个匿名方法,其中以->为分隔符,在其前的是输入参数,在其后的是lambda表达式代表的匿名方法的方法体(有时我们会简写成一个返回变量的样子)。

lambda表达式是函数式接口的其中一个"成果"——另一个是方法引用,我们可以将相同输入输出参数 (类型和数量都匹配) 的lambda表达式赋给一个函数式接口,通过调用赋值后的接口再来调用我们创建的lambda表达式。

lambda表达式的语法如下所示:

  • 参数
  • ->,它可以读作产生(produces)
  • 方法体
msg -> msg + "!";

msg -> msg.upperCase();

() -> "hello world";

h -> {
    System.out.println(h + "abcdefg");
    return h.lowerCase();
}

有以下这些问题需要注意:

  • lambda表达式如果只有一个参数,可以只写该参数不写括号,但你要知道这是一种特殊情况而不是相反
  • 通常需要括号将参数包裹起来,为了一致性考虑,单个参数时也可以使用括号

本站声明:
1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;

2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;

3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;

4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;

5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/294607.html

(0)
上一篇 2022年12月4日
下一篇 2022年12月4日

相关推荐

发表回复

登录后才能评论