文章目录
- Java8新特性:Lambda表达式
- 1. Lambda表达式概念
- 2. Lambda表达式语法
- 3. 函数式接口(Functional Interface)
- 4. Lambda表达式精简语法
- 5. 方法引用(Method Reference)
- 5.1 实例方法引用
- 5.2 静态方法引用
- 5.3 构造方法引用
Java8新特性:Lambda表达式
1. Lambda表达式概念
首先 Lambda 表达式是Java 8引入的重要新特性。
Lambda 是一个匿名函数(Lambda 表达式简化了匿名内部类的形式,并且可以达到同样的效果,当然Lambda 要优雅得多),我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码 像数据一样进行传递)。
lambda表达式与匿名函数虽然最终达到的效果是一样的,但其底层实现原理却并不相同:
- 匿名内部类在编译之后会创建一个新的匿名内部类出来
- 而 Lambda 是调用 JVM invokedynamic指令实现的,并不会产生新类。
还有一个重点:Lambda表达式返回的是接口对象实例。
其基本语法如下:
(参数列表) -> {方法体
};
其中->
是Lambda运算符,它的英文名是goes to
,嗯,就是转到的意思
整个的lambda表达式等价于:
new 接口名() {@Overridepublic void method() {}
};
看下面以Lambda语法创建线程和匿名内部类创建线程的例子
package com.yyl.lambda;public class Test {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println("公众号:毒力舌---点关注,不迷路!");}}).start();new Thread(() -> System.out.println("公众号:毒力舌---点关注,不迷路!")).start();}
}
结果是一样的:
2. Lambda表达式语法
接口中函数的声明总共6种情况:
- 接口方法无返回值和有返回值分2种
- 其中无参数、单个参数和多个参数又分3种情况。
两两结合,2×3=6,一种有六种
如下六种函数示例代码:
interface I01 {void method();
}
interface I02 {void method(int a);
}
interface I03 {void method(int a, int b);
}
interface I04 {int method();
}
interface I05 {int method(int a);
}
interface I06 {int method(int a, int b);
}
我们使用lambda表达式重写这几个方法并运行:
public class Test {public static void main(String[] args) {I01 i01 = () -> {System.out.println("无返回值、无参数");};I02 i02 = (int a) -> {System.out.println("无返回值,单个参数。a=" + a);};I03 i03 = (int a, int b) -> {System.out.println("无返回值,多个参数。a=" + a + ",b=" + b);};I04 i04 = () -> {System.out.println("有返回值、无参数");return 4;};I05 i05 = (int a) -> {System.out.println("有返回值,单个参数。a=" + a);return 5;};I06 i06 = (int a, int b) -> {System.out.println("有返回值,多个参数。a=" + a + ",b=" + b);return 6;};i01.method();i02.method(5);i03.method(5,10);System.out.println(i04.method());System.out.println(i05.method(5));System.out.println(i06.method(5, 10));}
}
输出:
无返回值、无参数
无返回值,单个参数。a=5
无返回值,多个参数。a=5,b=10
有返回值、无参数
4
有返回值,单个参数。a=5
5
有返回值,多个参数。a=5,b=10
6
看完以上代码已经能大概了解lambda表达式的用法了。
不过细心的你已经发现,要是这些接口里面定义的方法不止一个呢?那岂不是无法确定Lambda表达式究竟是要实现哪一个接口方法?
这里就要提到函数式接口的概念
3. 函数式接口(Functional Interface)
Java 8为了使现有的函数更加友好地支持Lambda表达式,引入了函数式接口的概念。
函数式接口本质上是一个仅有一个抽象方法的普通接口,所以又叫SAM接口(Single Abstract Method Interface)。
函数式接口在实际使用过程中很容易出错,比如某人在接口定义中又增加了另一个方法,则该接口不再是函数式接口,此时将该接口转换为Lambda表达式会报错。为了克服函数式接口的脆弱性,并且能够明确声明接口是作为函数式接口的意图,Java 8增加了**@FunctionalInterface**注解来标注函数式接口。
使用@FunctionalInterface注解标注的接口必须是函数式接口,也就是说该接口中只能声明一个抽象方法,如果声明多个抽象方法就会报错。但是默认方法和静态方法不属于抽象方法,因此在函数式接口中也可以定义默认方法和静态方法。
比如这样声明一个函数式接口是被允许的:
@FunctionalInterface
interface InterfaceDemo {void method(int a);static void staticMethod() {...}default void defaultMethod() {...}
}
@FunctionalInterface注解不是必须的,如果一个接口符合"函数式接口"的定义,那么加不加该注解都没有影响,就像下面的接口。
interface InterfaceDemo2 {void method(int a);
}
当然加上该注解能够更好地让编译器进行检查,也能提高代码的可读性。
4. Lambda表达式精简语法
-
参数类型可以省略
比如I02 i02 = (int a) -> {System.out.println(...);};
可以写成I02 i02 = (a) -> {System.out.println(...);};
-
假如只有一个参数,那么()
括号可以省略
比如I02 i02 = (a) -> {System.out.println(...);};
可以写成I02 i02 = a -> {System.out.println(...);};
-
假如方法体只有一条语句,那么语句后的;
分号和方法体的{}
大括号可以一起省略
比如I02 i02 = a -> {System.out.println(...);};
可以写成I02 i02 = a -> System.out.println(...);
-
如果方法体中唯一的语句是return返回语句,那么在省略第3种情况的同时,return
也必须一起省略
比如I05 i05 = a -> {return 1;};
可以写成``I05 i05 = a -> 1;`
5. 方法引用(Method Reference)
在Java 8中可以用方法引用来进一步简化Lambda表达式。(虽然两者在底层实现原理上略有不同,但在实际使用中完全可以视为等价)
有时候多个Lambda表达式的实现函数是一样的,我们可以封装成一个通用方法,再通过方法引用来实现接口。
这话说的吧,就很难理解,我是这样理解的:因为是函数式接口嘛,所以接口里面就一个要实现的函数,然后我们可以通过lambda表达式来实现这个接口,进而实现其中的方法,也可以直接引用一个方法来作为这个方法的实现,然后这样就能实现这个接口对象了。
方法引用可以引用三种方法,他们的语法如下:
- 如果是实例方法:
对象名::实例方法名
- 如果是静态方法:
类名::实例方法名
- 如果是构造方法:
类名::new
下面通过例子来理解一下方法引用的流程:
5.1 实例方法引用
示例代码:
public class Test {public void eat(int a) {System.out.println("吃东西。" + "a=" + a);}public static void main(String[] args) {Dog dog1 = (a) -> System.out.println("吃东西。" + "a=" + a);Cat cat1 = (a) -> System.out.println("吃东西。" + "a=" + a);dog1.doSomething(5);cat1.doSomething(5);Test test = new Test();Dog dog2 = test::eat;Cat cat2 = test::eat;dog2.doSomething(10);cat2.doSomething(10);}
}
@FunctionalInterface
interface Dog {void doSomething(int a);
}
@FunctionalInterface
interface Cat {void doSomething(int a);
}
输出结果:
吃东西。a=5
吃东西。a=5
吃东西。a=10
吃东西。a=10
5.2 静态方法引用
示例代码:
package com.yyl.lambda;public class Test {public static void eat(int a) {System.out.println("吃东西。" + "a=" + a);}public static void main(String[] args) {Dog dog1 = (a) -> System.out.println("吃东西。" + "a=" + a);Cat cat1 = (a) -> System.out.println("吃东西。" + "a=" + a);dog1.doSomething(5);cat1.doSomething(5);Test test = new Test();Dog dog2 = Test::eat;Cat cat2 = Test::eat;dog2.doSomething(10);cat2.doSomething(10);}
}@FunctionalInterface
interface Dog {void doSomething(int a);
}
@FunctionalInterface
interface Cat {void doSomething(int a);
}
5.3 构造方法引用
如果函数式接口的实现恰好可以通过调用一个类的构造方法来实现(比如说接口方法与这个构造方法的参数个数、参数类型和返回值都对的上),那么就可以使用构造方法引用。
代码如下:
public class Test {public void eat(int a) {System.out.println("吃东西。" + "a=" + a);}public static void main(String[] args) {DogService dogService1 = (name, age) -> new Dog(name, age);System.out.println(dogService1.getDog("大黄", 5));DogService dogService2 = Dog::new;System.out.println(dogService2.getDog("二黑", 3));}
}
@FunctionalInterface
interface DogService {Dog getDog(String name, int age);
}
class Dog {String name;int age;public Dog(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Dog{" +"name='" + name + '\'' +", age=" + age +'}';}
}
运行结果如下:
Dog{name='大黄', age=5}
Dog{name='二黑', age=3}