【Java8 函数式编程】之函数式接口与lambda表达式

函数式接口使用背景

我们知道,java是一门面向对象编程语言,java中一切都是面向对象的(除了原始数据类型)。在java中函数(方法)是类/对象的一部分,不能单独存在。而其他一些函数式编程语言如C++、Javascript等语言,可以编写单独的函数并可以直接调用它们。

开胃菜

package org.byron4j.cookbook.java8.basic;

import org.junit.jupiter.api.Test;

/**
 * 比较java8与之前的编码方式
 */
public class CompareJava8AndPreVersion {

    @Test
    public void test(){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("Java8之前的编码方式.");
            }
        };

        Runnable runnable4Java8 = () -> {
            System.out.println("Java8编码方式.");
        };
    }

}

面向对象并非不好,只是有时候需要编写冗长的代码。举个简单的例子,我们需要创建一个Runnable实例,通常我们会使用匿名内部类如下:

Runnable r = new Runnable(){
            @Override
            public void run() {
                System.out.println("My Runnable");
            }};

其实在这段代码中,真实有用的仅仅只是内部的run方法,其他的代码只是java面向对象要求的。

java8函数式接口和lambda表达式可以让我们编写少量代码就能达到上述效果。

java8函数式接口

在java8中,本身只有一个抽象方法的接口即可称之为函数式接口,可以使用@FunctionalInterface注解显示标明接口是函数式接口。这个注解并非必须的,如果加上该注解,则接口若存在多于一个的抽象方法则会提示编译错误。

java8函数式接口的最大好处是可以使用lambda表达式来初始化函数式接口从而避免匿名内部类样式的笨重写法。

java8的集合API已经重写了,并且引进了使用很多的函数式接口的新的流式API。在java.util.function包下定义了很多函数式接口如:ConsumerSupplierFunction Predicate

lambda表达式

通过lambda表达式我们可以将函数式编程在java的面向对象中形象化。
对象是java语言的基本,我们不可能离开对象单独去使用方法,这也是为什么java提供lambda表达式仅仅能使用函数式接口的原因。

如果只有一个抽象方法,那么使用lambda表达式就不会存在困惑了。
lambda表达式的签名:
(argument1, argument2,...) -> (body)

  • (argument1, argument2,...)表示方法签名,argument1, argument2,...是参数列表

  • -> 是箭头,指向方法体

  • (body) 是方法体,可以使用{}包裹代码块来表示

  • 如果是无参方法,则方法签名可以使用 ()

  • 如果只有一个参数的话,()可以省略

前面创建Runnable实例的代码可以使用lambda表达式实现:

Runnable r1 = () -> System.out.println("My Runnable");

解释下这段代码:

  • Runnable 是一个函数式接口,所以我们可以使用lambda表达式创建它的实例

  • 因为 run()方法咩有参数,所以我们的lambda表达式也没有参数

  • 就像if-else语句一样,如果只有一行代码的话我们可以省去{}符号了。

为什么要使用lambda表达式

减少代码量

使用匿名内部类和lambda表达式的代码量区分已经很明显了

支持连续地、并行地执行

lambda的另外一个好处就是我们可以使用流式API连续并行地执行程序。
为了说明这点,我们举个例子。判断一个数是不是质数:
这段代码不是最优的,但是可以达到目的:

private static boolean isPrime(int number) {        
    if(number < 2) return false;
    for(int i=2; i<number; i++){
        if(number % i == 0) return false;
    }
    return true;
}

解决这个问题的代码是连续的,如果给定的数字很大的话很耗时。另外一个缺陷是分支返回太多可读性不好。使用lambda和流式API的写法:

private static boolean isPrime(int number) {        
    return number > 1
            && IntStream.range(2, number).noneMatch(
                    index -> number % index == 0);
}

IntStream是一个自然排好序的元素为原始类型int的支持连续并行执行的流。为了更好阅读,代码可以进一步优化为:

private static boolean isPrime(int number) {
    IntPredicate isDivisible = index -> number % index == 0;
    
    return number > 1
            && IntStream.range(2, number).noneMatch(
                    isDivisible);
}

range(arg1,arg2)方法返回一个IntStream包含arg1,但是不包含arg2的步长为1的序列。

noneMatch()返回是否没有元素匹配不上给定的预定义条件Predicate。

给方法传递行为action

我们需要对一个list中满足某个条件的元素进行求和。

public static int sumWithCondition(List<Integer> numbers, Predicate<Integer> predicate) {
        return numbers.parallelStream()
                .filter(predicate)
                .mapToInt(i -> i)
                .sum();
    }

使用方法如下:

//对所有元素求和
sumWithCondition(numbers, n -> true)
//对是偶数的元素求和
sumWithCondition(numbers, i -> i%2==0)
//对所有大于5的元素求和
sumWithCondition(numbers, i -> i>5)

高效率的懒加载

如果我们需要找出3-11之间的最大的奇数,并求出它的平方。

我们可能使用这样的代码:

private static int findSquareOfMaxOdd(List<Integer> numbers) {
        int max = 0;
        for (int i : numbers) {
            if (i % 2 != 0 && i > 3 && i < 11 && i > max) {
                max = i;
            }
        }
        return max * max;
    }

上述代码是在一个序列中处理我们可以使用流式API代替:

public static int findSquareOfMaxOdd(List<Integer> numbers) {
        return numbers.stream()
                .filter(NumberTest::isOdd)  
                .filter(NumberTest::isGreaterThan3)
                .filter(NumberTest::isLessThan11)
                .max(Comparator.naturalOrder())
                .map(i -> i * i)
                .get();
    }

    public static boolean isOdd(int i) {
        return i % 2 != 0;
    }
    
    public static boolean isGreaterThan3(int i){
        return i > 3;
    }
    
    public static boolean isLessThan11(int i){
        return i < 11;
    }

冒号表达式是方法的引用,NumberTest::isOdd(i) -> isOdd(i) 或者 i -> NumberTest.isOdd (i) 的缩写。

更多的lambda表达式示例

() -> {}                     // 无参无方法体

() -> 42                     // 无参有方法体
() -> null                   // 无参有方法体
() -> { return 42; }         // 无参,代码块中返回结果
() -> { System.gc(); }       // 

// 复杂的代码块
() -> {
  if (true) return 10;
  else {
    int result = 15;
    for (int i = 1; i < 10; i++)
      result *= i;
    return result;
  }
}                          

(int x) -> x+1             // 单个的声明类型的参数
(int x) -> { return x+1; } // 
(x) -> x+1                 // 单个参数,单条代码同上
x -> x+1                   // 同上

(String s) -> s.length()   // 
(Thread t) -> { t.start(); } // 
s -> s.length()              // 单个的编译器可以推断的类型参数
t -> { t.start(); }          // 单个的编译器可以推断的类型参数

(int x, int y) -> x+y      // 多个的声明类型的参数
(x,y) -> x+y               // 多个的可以推断的类型参数
(x, final y) -> x+y        // 错误。不能修改final变量y
(x, int y) -> x+y          // 错误,无法推断混合类型

方法、构造器引用

java8可以使用冒号表达式来引用方法:

System::getProperty
System.out::println
"abc"::length
ArrayList::new
int[]::new
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容