java8 Lambda表达式&函数接口&方法引用

Lambda表达式

java 8 的新特性,是一种语法糖,简化了只有一个方法的匿名内部类的编写,让编码更方便,代码更简洁。

只有一个方法的接口(又叫函数式接口)在写匿名内部类时看起来很啰嗦,真正有用的只是方法实现中的那一段代码。通常情况下我们使用匿名内部类是想将功能作为参数传递给另外的方法,例如点击按钮时应该采取的措施。Lambda表达式使我们能够执行此操作,将功能视为方法参数,或将代码视为数据。

个人理解:主要作用是为了更方便的创建 函数接口 的匿名内部类的实例,减少啰嗦的代码。

基本语法

主要是加了箭头符号 '->' 称为Lambda操作符或箭头操作符

(参数) -> {函数体}

左边是参数,中间是箭头符号,右边是方法体

这里以jdk内置Consumer 函数式接口示例:

原来的匿名内部类写法

Consumer<String> c = new Consumer<String>() {
    @Override
    public void accept(String str) {
        System.out.println("msg=" + str);
    }
};

新的Lambda表达式写法

Consumer<String> c1 = (String str) -> {
    System.out.println("msg=" + str);
};

可以看到代码简洁了很多

再比Runnable是一个函数接口,创建线程可以这样写

Thread t = new Thread(() -> {
    System.out.print("Lambda!");
    System.out.print("Do Something...");
});
t.run();

更简化的写法

参数可以声明类型,也可以根据类型推断而省略

如上面的c1可以省略参数类型String

Consumer<String> c1 = (str) -> {
    System.out.println("msg=" + str);
};

但是如果有两个参数不能只写一个参数的类型,要么都写类型,要么都不写类型

只有一个参数时可以省略参数的小括号
Consumer<String> c2 = str -> {
    System.out.println("msg=" + str);
};
方法体只有一条语句时花括号和语句的分号都可以省略
Consumer<String> c3 = str -> System.out.println(str);
只有一条返回语句时return可以不写

如返回一个String

Supplier<String> s1 = () -> {
    return "Lambad!";
};

可以写成

Supplier<String> s2 = () -> "Lambad!";

返回对象可以写成

Supplier<User> s3 = () -> new User("张三", 20);
使用方法引用时参数和箭头符号都可以省略

前提是:接口定义的参数 和 方法的参数匹配

如:

Consumer<String> c4 = System.out::println;

accept.accept(String t) 的参数与 System.out.println(String x)的参数匹配

构造器引用

Supplier<User> s4 = User::new;

静态方法引用
User对象中有一个叫getInstance的静态方法

Supplier<User> s5 = User::getInstance;

多参数+返回
传入两个int值返回较大的一个

BiFunction<Integer, Integer, Integer> bf = (x, y) -> x > y ? x : y;
# 可以写成
BiFunction<Integer, Integer, Integer> bf1 = Math::max;

原则就是让编译器能识别,不产生歧义!

Lambda表达式序列化问题

一个Lambda能否序列化, 要以它捕获的参数以及target type能否序列化为准。lambda表达式是可以序列化的,但是就像内部类一样,强烈建议不要对lambda表达式进行序列化。

ps:经测试 使用方法引用时不能序列化

反对序列化内部类(包括局部类和匿名类)。当Java编译器编译某些构造时,比如内部类,它会创建合成构造;这些是在源代码中没有相应构造的类、方法、字段和其他构造。合成构造使Java编译器能够在不更改JVM的情况下实现新的Java语言特性。然而,合成构造可能因不同的Java编译器实现而不同,这意味着.class文件也可能因不同的实现而不同。因此,如果您序列化一个内部类,然后用不同的JRE实现反序列化它,您可能会有兼容性问题。有关编译内部类时生成的合成构造的更多信息,请参阅获取方法参数名称一节中的隐式和合成参数一节。



接口默认方法和静态方法

从Java 8 开始,接口中引入了 缺省方法(default method) 和 静态方法 (static method) 的特性。

注意:接口中的方法锈蚀符号只能是 {抽象、默认、静态} 中的一种

默认方法

默认方法就是接口可以有实现方法,不需要实现类主动去实现默认会自动继承,当接口中的默认方法不满足要求时实现类可以对其进行重写,调用时重写的方法优先于默认方法被调用。

默认方法用default关键字修饰,默认是public权限

添加此功能是为了实现向后兼容,默认方法不会破坏原有的接口实现还便于给程序添加新特性。比如某一天我们需要给线上某个接口增加一个方法,在 Java 8 之前,我们需要去改每一个实现类,就算这个实现根本用不到这个功能也得加一个空实现,否则编译不了,改动起来非常麻烦。Java 8 之后就只需要在接口上增加一个默认方法就可以了。

java不支持多继承但是可以实现多个接口,有了默认方法之后我们可以将方法的相关逻辑写到接口中这样也能解决部分多继承的问题。

继承特性

interface A {
     // 默认方法
    default void say() {
        System.out.println("hello");
    }
}

class C1 implements A {

}

public static void main(String[] args) {
    C1 c1 = new C1();
    // 自动继承
    c1.say();
}

输出:

hello
子类方法优先

C1重写say方法

class C1 implements A {
    @Override
    public void say() {
        System.out.println("C1 hello");
    }
}

输出:

C1 hello

子类的子类会优先继承父类的方法,父类没有重写时才会使用接口中的方法

C2类继承C1类

class C2 extends C1{
    
}

public static void main(String[] args) {
   C2 c2 = new C2();
   c2.say();
}

输出:

C1 hello
实现多个接口中有相同的方法时需指定

如子类实现两个接口,两个接口中有相同的方法(参数和方法名相同),实现类需要重写该,否则编译器不知道该调用哪一个

interface A {
    default void say() {
        System.out.println("[interface A] say hello");
    }
}

interface B {
    default void say() {
        System.out.println("[interface B] say hello");
    }
}

class C1 implements A, B {
    @Override
    public void say() {
        System.out.println("需要重写say方法,否则编译器不知道是调用 A.say 还是 B.say");
    }
}
显示引用接口的默认实现

通过使用super,可以显式的引用被继承接口的默认实现,语法如下:InterfaceName.super.methodName()

class C1 implements A, B {
    @Override
    public void say() {
        A.super.say();
    }
}

输出:

[interface A] say hello

静态方法

接口中的静态方法和类中定义的静态方法一样,不属于特定对象,所以它们不是实现接口的api的一部分,必须使用InterfaceName.staticMethod来调用它们。

接口中的所有方法声明(包括静态方法)都是隐式的public,因此可以省略public修饰符

interface A {
    static String STR = "静态字段可以被继承";
    static void sayA() {
        System.out.println("static sayA");
    }
}

class C1 implements A {
}

//main
public static void main(String[] args) {
    // 通过接口名称调用接口的静态方法
    A.sayA();

    // 不能通过子类实例调用 new C1().sayA()
    // 也不能通过子类调用 C1.sayA()
    
    //可以通过子类实例获取静态字段值
    System.out.println(new C1().STR);
}

实现接口的类或者子接口不会继承接口中的静态方法,只能是静态方法所属的接口来调用。但是接口中静态字段可以被继承(默认用public static final修饰)。

在java8中很多接口中都使用默认方法和静态方法进行了增强,如:Comparator,接口静态方法适合于提供实用方法,例如空检查、集合排序等。



函数式接口

只有一个抽象方法的接口

可以有多个默认方法或者静态方法等

接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object类中的public方法,那么 也不算抽象方法,因为任何接口的实现都会从其父类Object或其它地方获得这些方法的实现。

如:

@FunctionalInterface
public interface FunctionalInterfaceT1<T> {

    // 抽象方法
    void doSomething(T t);

    // Object类中的方法
    boolean equals(Object obj);

    // 默认方法
    default void printT(T t) {
        System.out.println(t);
    }

    // 默认方法2
    default void printT2(T t) {
        System.out.println(t);
    }

    // 静态方法
    public static void print() {
        System.out.println("FunctionalInterfaceT1.print");
    }
}

@FunctionalInterface 注解

该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。

默认方法不能覆盖Object中的方法

接口不能实现Object的toString、equals、hashCode等方法,因为接口多继承如果多个接口都实现了equals方法会不知道调用哪一个

编译过不了

内置的核心函数式接口

java8中内置的函数式接口位于包:java.util.function

4大常用的函数式接口

Consumer<T>

消费型 接口

方法:void accept(T t)
用来消费 T 类型的对象

Supplier<T>

供给型 接口

方法: T get()
提供 T 类型的对象

Function<T,R>

函数型 接口

方法: R apply(T t)
对类型为 T 的对象进行操作返回 R 类型的对象

Predicate<T>

断言型 接口

方法: boolean test(T t)
判断 T 类型的对象是否满足某些约束

其他类型的函数式接口

也都在 java.util.function 包下,大体上都差不多,记住上面常用的4个就行了

BiFunction<T, U, R>

参数为 T,U,返回值为 R
方法为 R apply(T t, U u)

UnaryOperator<T> (Function子接口)

参数为 T,对参数为T的对象进行一元操作,并返回T类型结果
方法为 T apply(T t)

BinaryOperator<T> (BiFunction子接口)

参数为T,对参数为T得对象进行二元操作,并返回T类型得结果
方法为 T apply(T t1, T t2)

BiConsumer(T, U)

参数为 T,U 无返回值
方法为 void accept(T t, U u)

BiPredicate<T, U>

参数为 T,U 返回boolean值
方法为: boolean test(T t, U u)

ToIntFunction<T>、ToLongFunction<T>、ToDoubleFunction<T>

参数类型为T,返回值分别为int,long,double
分别计算int,long,double的函数
方法为:int applyAsInt(T value);long applyAsLong(T t, U u)

IntFunction<R>、LongFunction<R>、DoubleFunction<R>

参数分别为int,long,double,返回值为R。
方法为: R apply(int value); R apply(long value);



方法引用

方法引用是一个新特性,方法类似指针一样可以被直接引用。
新的操作符"::"(两个冒号)用来引用 或者 实例 的方法。

方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

静态方法引用

BiFunction<Integer, Integer, Integer> bf = Math::max;
System.out.println(bf.apply(1, 3));

实例方法引用

Consumer<String> c = System.out::println;
c.accept("hello");

任意实例引用

String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
Arrays.stream(stringArray).forEach(System.out::println);

可以引用任意的一个类型的实例。等价的lambda表达式的参数列表为(String a, String b),方法引用会调用a.compareToIgnoreCase(b)。

构造函数引用

Supplier<User> s = User::new;

对构造函数的引用类似对静态方法的引用,只不过方法名是new。 一个类有多个构造函数, 会根据target type选择最合适的构造函数。

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

推荐阅读更多精彩内容