Java8新特性(二)-Lambda表达式和函数式接口

1. Lambda

java8开始引入了Lambda表达式.lambda表达式是Stream API编程的基础。本篇文章不打算研究java中Lambda的底层实现。主要关注以下两点。

  • 主要关注什么是Lambda
  • 在Java中如何使用Lambda
  • 如何使用函数式接口

1.1 什么是Lambda表达式

广义上

广义上讲Lambda表达式是一种匿名函数.即无序标识符的函数, 只拥有形参,和方法体.

Java中的Lambda

Java中的Lambda表达式是可以认为是一种匿名内部类的语法糖.

1.2 如何使用Lambda表达式

前言

Lambda表达式最主要的一个应用场景就是用来简化匿名内部类的写法, 使得代码更加的简短, 简洁.但是如果不了解Lambda表达式, 可能对于其他开发人员来说你写的代码可能就不是那么好维护.

基本语法

基本语法:
(parameters) ->{statements;}

分类

我们按照匿名内部类来对Lambda表达式进行分类。可以分为以下几类。

一. 无参,无返回值

Runnable r1 = ()->{System.out.println("Hello Lambda");};
new Thread(r1).start()

可以写得更简洁new Thread( ()->{System.out.println("Hello Lambda");}).start()

二. 一个参数, 无返回值

Consumer<String>con = (String str)->{System.out.println(str);};

这里的Consumer是函数式接口, 表示的是一个消费型函数式接口。如下是Consumer的定义. 这边的lambda表达式, 相当于new了一个匿名内部类对象,并实现了其accept方法。lambda表达式就是不用写要实现的方法名, 应为JVM会给我们自动推断, 该lambda表达式是要绑定的对应方法.

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

三. 需要两个参数或两个以上, 且有返回值

例如用lambda替换创建匿名内部类Comparator

         Comparator<Integer> comparator = new Comparator<Integer>() { // 匿名内部类方式
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        };
        
        // lambda方式
        Comparator<Integer> comparator2 = (Integer o1,  Integer o2)->{ o1.compareTo(o2)};

四. 简略写法

上述写法还就可以简略, 遵守以下规则。

简化规则总结:
  • 参数小括号可以省略, 当且仅当只有一个参数.
  • 参数数据类型可以省略, 当且仅当lambda表达式.
  • 方法体return大括号可以省略, 当且仅当只有一条语句.

以Comparator接口为例子

Comparator<Integer> comparator2 = (o1,  o2)-> o1.compareTo(o2);

类型推断与lambda参数

如上述总结的规则,lambda表达式参数列表的类型可以省略。是因为编译器在后面给我们做了很多事, 编译器可以根据上下文推断出lambda表达式的参数类型

1.3 函数式接口

函数式接口(Funcation Interface)是一种特殊的接口。这类接口只包含一个抽象方法的接口, 因此也被称为SAM(Single Abstract Method).

JDK 8中又增加了java.util.function包, 提供了常用的函数式接口。

为什么会单单从接口中定义出此类接口呢?

原因是在Java Lambda的实现中, 开发组不想再为Lambda表达式单独定义一种特殊的Structural函数类型,称之为箭头类型(arrow type), 依然想采用Java既有的类型系统(class, interface, method等), 原因是增加一个结构化的函数类型会增加函数类型的复杂性,破坏既有的Java类型,并对成千上万的Java类库造成严重的影响。 权衡利弊, 因此最终还是利用SAM 接口作为 Lambda表达式的目标类型

定义

只包含一个抽象方法的接口, 称为函数式接口(除了隐含的Object对象的公共方法)

  • 函数式接口中可以额外定义多个抽象方法,但这些抽象方法签名必须和Objectpublic方法一样(必须满足即使Object的方法, 又是public的接口, 像Object的clone()接口就不是public)
@FunctionalInterface
public interface ObjectMethodFunctionalInterface {
    void count(int i);
    
    String toString(); //same to Object.toString
    int hashCode(); //same to Object.hashCode
    boolean equals(Object obj); //same to Object.equals
}
  • 函数式接口的抽象方法可以声明 受检查异常(checked exception)。 在调用目标对象的这个方法时必须catch这个异常。
public class FunctionalInterfaceWithException {
    public static void main(String[] args) {
        InterfaceWithException target = i -> {};
        try {
            target.apply(10);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
@FunctionalInterface
interface InterfaceWithException {
    void apply(int i) throws Exception;
}
  • 如果在Lambda表达式中抛出异常, 而目标接口中的抽象函数没有声明这个可检查.则不能在lambda表达式中抛出异常
public class FunctionalInterfaceWithException {
    public static void main(String[] args) {
        InterfaceWithException target = i -> {throw new Exception();};
    }
}
@FunctionalInterface
interface InterfaceWithException {
    void apply(int i);
}

静态方法

函数式接口是在JDK8后才引入的,而JDK8之后的接口可以添加公共静态公共方法。而函数式接口又是一种特殊的接口, 其既然是接口,当然也可以添加公共静态方法。
如下代码, 依然是公共静态方法

@FunctionalInterface
interface FunctionalInterfaceWithStaticMethod {
    static int sum(int[] array) {
        return Arrays.stream(array).reduce((a, b) -> a+b).getAsInt();
    }
    
    void apply();
}
public class StaticMethodFunctionalInterface {
    public static void main(String[] args) {
        int sum = FunctionalInterfaceWithStaticMethod.sum(new int[]{1,2,3,4,5});
        
        FunctionalInterfaceWithStaticMethod f = () -> {};
    }
}

默认方法

语法

default 数据类型 方法名(参数列表) {

}

默认方法解耦

默认方法也是JDK8引入的新特性。它主要为了解决JDK1.8之前,接口和类之前的耦合性问题, 在开发中当我们想给接口新的方法,其所有实现类也必须实现该接口,这样带来的代码改动很大。为了向后兼容, JDK8提供了default关键字,用该关键字修饰的方法,在接口中就提供了实现(所以才叫方法), 就是为了解决问题。

因为默认方法不是抽象方法,所以不影响我们判断一个接口是否是函数式接口

如下代码依然是一个函数式接口

@FunctionalInterface
interface InterfaceWithDefaultMethod {
    void apply(Object obj);
    
    default void say(String name) {
        System.out.println("hello " + name);
    }
}
class FunctionalInterfaceWithDefaultMethod {
    public static void main(String[] args) {
        InterfaceWithDefaultMethod i = (o) -> {};
        i.apply(null);
        i.say("default method");
    }
}

@FunctionalInterface

该注解主要是为了来标注一个接口是函数式接口, 而且标注后可以让编译器来检查该接口是不是符合函数式接口的定义。作为开发者, 我们应该为每一个函数式接口添加上该注解.

  • 规范, 提高代码可读性。

JDK8提供的新的函数式接口

java.util.function中定义了几组类型的函数式接口以及针对基本数据类型的子接口.

四大核心函数式接口

四大核心函数式接口

如上图根据这四种接口的定义我们可以写出其对应的lambda表达式

  • 对于Consumer<T>
    ()->{ statement };
  • 对于Supplier<T>
    (t)->{ statement};
  • 对于Fuction<T, R>
    (t)->{ R = statement; return R; };
  • 对于Predicate<T>
    (t)->{ boolean r = statement;return r;}

其他函数式接口

其他函数式接口

这里简单介绍下BiFuncationUnaryOperator接口, 其他其实大同小异.

  • BiFuncation<T, U, R>接口规定了对应的lambda表达式是给定两个参数类型T和U, 经过计算返回R类型.
    @Test
    public void testBiFuncation() {

        BiFunction<String, String, String> concate = (o1, o2)-> o1 + "---" + o2;
        System.out.println(concate.apply("hello", "Mr su")); // 输出hello---Mr su
    }
  • BinaryOperator<T>接口是BiFuncation的特殊例子, 其T, U,R都是同一种类型.
    @Test
    public void testBinaryOperator() {

        BinaryOperator<Integer> op = (o1, o2)-> o1 > o2? o1: o2;
        Integer max = op.apply(2, 3);
        System.out.println(max);  // 输出3
    }
  • UnaryOperator<T>接口规定了参数只有一个且是T类型, 经过计算返回类型T
    @Test
    public void testUnaryOperator() {
        UnaryOperator<String> upper = (s)-> s.toUpperCase();
        System.out.println(upper.apply("i love china"));
    }

方法引用与构造器引用

方法引用

函数式接口可以直接引用已经实现好的方法, 只要该方法符合其规定的规范模式。有如下三种引用方式

  • 对象::实例方法名
  • 类名::静态方法名
  • 类名::实例方法名
// 如System.out.println()就符合Consumer定义的模式.是消费型接口
// 给定一个数据就消费掉, 不返回.所以我们可以这样写
        Consumer<String> con = (s)->System.out.println(s);
        Consumer<String> con2 = System.out::println;

构造器引用

语法: 引用类型名::new

Funcation<Integer, MyClass> fun = MyClass::new;

数组引用

语法: 数组类型::new

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

推荐阅读更多精彩内容