JDK8新特性 Lambda表达式
Posted pautcher
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK8新特性 Lambda表达式相关的知识,希望对你有一定的参考价值。
一、接口的默认方法
二、Lambda 表达式
三、函数式接口
四、方法与构造函数引用
五、Lambda 作用域
六、访问局部变量
七、访问对象字段与静态变量
八、访问接口的默认方法
九、Date API
十、Annotation 注解:支持多重注解
一、接口的默认方法
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:
- public interface Formula {
- double calculate(int a);
- // jdk8能给接口添加一个非抽象的方法实现
- default double sqrt(int a){
- return Math.sqrt(a);
- }
- }
Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。
- @Test
- public void test1() {
- Formula formula = new Formula() {
- //这里只实现了一个方法,没有实现用default修饰的方法
- @Override
- public double calculate(int a) {
- return sqrt(a * 100);
- }
- };
- System.out.println(formula.calculate(100)); //100.0
- System.out.println(formula.sqrt(100)); //10.0
- }
1).如果一个类实现两个接口,这两个接口同时有相同的抽象方法,在类中只需要重写一次这个方法。
2).如果接口中有default修饰的方法不需要重写。
3).如果两个接口里的方法名相同都是default方法,里面的方法体不同,在类中需要重写该方法。
4).如果两个接口中方法名,参数都相同的方法,一个接口是抽象方法,另一个是default修饰有方法体。这是该类也必须重写该方法。
5).Lambda表达式中是无法访问到默认方法的
二、Lambda 表达式
- @Test
- public void test2() {
- // 如果用Lambda表达式,一定要写明泛型
- List<String> list = Arrays.asList("peter","anna","make");
- // ①.老版本的Java中是这样排列字符串的
- Collections.sort(list, new Comparator<String>() {
- @Override
- public int compare(String a, String b) {
- return a.compareTo(b);
- }
- });
- // ②.Java 8提供了更简洁的语法,lambda表达式:
- /*
- Collections.sort(list ,(String a,String b) -> {
- return a.compareTo(b);
- });
- // ③.还可以写得更短
- Collections.sort(list, (String a, String b) -> a.compareTo(b));
- // ④.还可以这么写
- Collections.sort(list, String::compareTo);
- System.out.println(Collections.singletonList(list)); //[[anna, make, peter]]
- }
1. 什么是λ表达式
λ表达式本质上是一个匿名方法。让我们来看下面这个例子:
public int add(int x, int y) {
return x + y;
}
转成λ表达式后是这个样子:
(int x, int y) -> x + y;
参数类型也可以省略,Java编译器会根据上下文推断出来:
(x, y) -> x + y; //返回两数之和
或者
(x, y) -> { return x + y; } //显式指明返回值
可见λ表达式有三部分组成:参数列表,箭头(->),以及一个表达式或语句块。
下面这个例子里的λ表达式没有参数,也没有返回值(相当于一个方法接受0个参数,返回void,其实就是Runnable里run方法的一个实现):
() -> { System.out.println("Hello Lambda!"); }
如果只有一个参数且可以被Java推断出类型,那么参数列表的括号也可以省略:
c -> { return c.size(); }
lambda包含3个部分:
(1)括弧包起来的参数
(2)一个箭头
(3)方法体,可以是单个语句,也可以是语句块
参数可以写类型,也可以不写,jvm很智能的,它能自己推算出来
方法可以有返回,也可以无返回,如果有多个语句,还要返回值,需要加上return 。
一个很有意思的事情:
之前我们说Object是一切类的父类,然而在加入了lambda以后,这种大一统的局面将不复存在:
- @Test
- public void test33() {
- Object r = ()->System.out.println("hello,lambda");
- }
编译报错:incompatible types: Object is not a functional interface
很显然,编译器会检查变量的引用类型里面是否真的是一个函数式接口。那么如何让这段代码通过编译呢?只需要加一个强制类型转换就可以了:
- @Test
- public void test33() {
- Object r = (Runnable)()->System.out.println("hello,lambda");
- }
如果你认为lambda表达式仅仅是为了从语法上简化匿名内部类,那就太小看jdk8的lambda了!
lambda的定义:
(1)lambda是方法的实现
(2)lambda是延迟执行的
- public static void main(String[] args) {
- // 正常实现
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- System.out.println("hello lambda!");
- }
- };
- runnable.run();
- // 用lambda实现如下(如果run方法体里只有一行数据可以省去{)
- Runnable runnable1 = () -> {
- System.out.println("hello lambda2!");
- System.out.println("2");
- };
- runnable1.run();
- }
lambda是如何做到的呢?可以反编译后查看字节码。(里面有一个叫做invokedynamic的指令。invokedynamic是从jdk7开始引入的,jdk8开始落地。)可以看出来lambda并不是语法糖,它不是像匿名内部类那样生成那种带有$的匿名类。简单的说,这里只是定义了一个方法调用点,具体调用那个方法要到运行时才能决定,这就是前面所说的:延迟执行。具体的细节请google:invokedynamic。
为了配合lambda,jdk8引入了一个新的定义叫做:函数式接口(Functional interfaces)
三、函数式接口
(1)是一个接口
(2)只有一个待实现的方法 !!!!
因为jdk8开始,接口可以有default方法,所以,函数式接口也是可以有default方法的,但是,只能有一个未实现的方法。
与此对应,新引入了一个注解: @FunctionalInterface。这个注解只是起文档的作用,说明这个接口是函数式接口,编译器并不会使用这个注解来决定一个接口是不是函数式接口。不管加不加@FunctionalInterface这个注解,下面的接口都是函数式接口:
interface Something {
public String doit(Integer i);
}
Lambda表达式是如何在Java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
- @FunctionalInterface
- public interface Converter<E,T> {
- // 如将字条串转为int类型
- T convert(E str);
- // 函数式接口只能有 一个 抽象方法
- // T convert2(E str, E sre);
- }
- @Test
- public void test3() {
- Converter<String,Integer> converter = (str) ->Integer.valueOf(str);
- //上面还可以通过静态方法引用来表示:
- // Converter<String,Integer> converter = Integer::valueOf;
- Integer integer = converter.convert("123");
- System.out.println(integer);
- }
需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的。
译者注 将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如Rhino javascript解释器,
五、Lambda 作用域
在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。
- public class InnerClassExamples {
- public static void main(String... args) {
- Hello h = new Hello();
- h.r.run();
- }
- }
- class Hello {
- public Runnable r = new Runnable() {
- public void run() {
- // 这里的this指的是匿名类,而非Hello类。
- System.out.println("-->1 "+this);
- System.out.println("-->2 "+toString());
- // 想要引用Hello类需要Hello.this这样!!!
- System.out.println("++1 "+Hello.this);
- System.out.println("++2 "+Hello.this.toString());
- }
- };
- public String toString() {
- return "Hello‘s custom toString()";
- }
- }
-->1 [email protected] -->2 [email protected] ++1 Hello‘s custom toString() ++2 Hello‘s custom toString() |
System.out.println(this);这里的this指的是匿名类,而非Hello类。
想要引用Hello类需要Hello.this这样:System.out.println(Hello.this);下面我们就来看一下伟大的lambda是什么样子的:
- public class Test7{
- public static void main(String args[]){
- Hello2 h = new Hello2();
- h.r.run();
- }
- }
- class Hello2{
- public Runnable r = () -> {
- System.out.println(this);
- System.out.println(toString());
- };
- public String toString() {
- return "Hello‘s custom toString()";
- }
- }
Hello‘s custom toString() Hello‘s custom toString() |
六、访问局部变量
匿名内部类只能引用作用域外面的final的变量,在lambda中对这个限制做了削弱,只需要是“等价final”就可以,没必要用final关键字来标识。
我们可以直接在lambda表达式中访问外层的局部变量:
- public static void main(String args[]){
- String message = "Howdy, world!";//不需要是final的
- Runnable runnable = () -> System.out.println(message);
- runnable.run();
- }
“等效final”的意思是:事实上的final,所以,一旦赋值也是不可以改变的!比如
- public static void main(String args[]){
- String message = "Howdy, world!";//不需要是final的
- Runnable runnable = () -> System.out.println(message);
- runnable.run();
- message = "change it";
- }
error: local variables referenced from a lambda expression must be final or effectively final
七、方法引用与构造函数引用
真正的大杀器出现了:
现在我们要把多个Student对象进行排序,有时候是按照firstName来排,有时候是按照lastName或者是age来排,使用lambda可以这样来做:
- public class Student {
- public String firstName;
- public String lastName;
- public int age;
- public Student (String firstName, String lastName, int age){
- this.firstName = firstName;
- this.lastName = lastName;
- this.age = age;
- }
- public String toString(){
- return firstName+","+lastName+","+age;
- }
- }
- @Test
- public void test12() {
- Student[] students = new Student[]{
- new Student("Ted", "Neward", 41),
- new Student("Charlotte", "Neward", 41),
- new Student("Michael", "Neward", 19),
- new Student("Matthew", "Neward", 13)
- };
- //sort by firstName
- Arrays.sort(students, (lhs,rhs)->lhs.firstName.compareTo(rhs.firstName));
- System.out.println(Arrays.asList(students));
- }
我们可以把Comparator抽取出来,变成是Student对象的成员变量:
- public class Student {
- public String firstName;
- public String lastName;
- public int age;
- public Student (String firstName, String lastName, int age){
- this.firstName = firstName;
- this.lastName = lastName;
- this.age = age;
- }
- public final static Comparator<Student> compareFirstName =
- (lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName);
- public final static Comparator<Student> compareLastName =
- (lhs, rhs) -> lhs.lastName.compareTo(rhs.lastName);
- public final static Comparator<Student> compareAge =
- (lhs, rhs) -> lhs.age - rhs.age;
- public String toString(){
- return firstName+","+lastName+","+age;
- }
- }
- @Test
- public void test12() {
- Student[] students = new Student[]{
- new Student("Ted", "Neward", 41),
- new Student("Charlotte", "Neward", 41),
- new Student("Michael", "Neward", 19),
- new Student("Matthew", "Neward", 13)
- };
- //sort by firstName
- Arrays.sort(students, (lhs,rhs)->lhs.firstName.compareTo(rhs.firstName));
- Arrays.sort(students, Student.compareFirstName); //这里直接引用lambda
- System.out.println(Arrays.asList(students));
- }
能起到同样的作用,但是语法看上去很奇怪,因为之前我们都是创建一个满足Comparator签名的方法,然后直接调用,而非定义一个变量,
然后引用这个变量!所以,还有这么一种调用方法: -----------------------------------
- public class Student {
- public String firstName;
- public String lastName;
- public int age;
- public Student (String firstName, String lastName, int age){
- this.firstName = firstName;
- this.lastName = lastName;
- this.age = age;
- }
- public static int compareFirstName(Student lhs, Student rhs){
- return lhs.firstName.compareTo(rhs.firstName);
- }
- public static int compareLastName(Student lhs, Student rhs){
- return lhs.lastName.compareTo(rhs.lastName);
- }
- public String toString(){
- return firstName+","+lastName+","+age;
- }
- }
- @Test
- public void test12() {
- Student[] students = new Student[]{
- new Student("Ted", "Neward", 41),
- new Student("Charlotte", "Neward", 41),
- new Student("Michael", "Neward", 19),
- new Student("Matthew", "Neward", 13)
- };
- //sort by firstName
- Arrays.sort(students, Student::compareFirstName);
- System.out.println(Arrays.asList(students));
- }
看Student::compareFirstName这种调用方式,
如果是static方法使用:类名::方法名
如果是instance方法:instance::方法名
但是,上面的代码还是不是很美观,因为Student只是一个数据对象,它不应该的对外提供compareFirstName或者是compareLastName这样的方法,我们需要的仅仅是根据某个字段排序而已!很幸运的是jdk的api帮我们做了这件事:
- public class Student {
- public String firstName;
- public String lastName;
- public int age;
- public Student (String firstName, String lastName, int age){
- this.firstName = firstName;
- this.lastName = lastName;
- this.age = age;
- }
- // get方法
- public String getFirstName() {
- return firstName;
- }
- public String getLastName() {
- return lastName;
- }
- public String toString(){
- return firstName+","+lastName+","+age;
- }
- }
- @Test
- public void test12() {
- Student[] students = new Student[]{
- new Student("Ted", "Neward", 41),
- new Student("Charlotte", "Neward", 41),
- new Student("Michael", "Neward", 19),
- new Student("Matthew", "Neward", 13)
- };
- //sort by firstName
- Arrays.sort(students, Comparator.comparing(Student::getFirstName));
- System.out.println(Arrays.asList(students));
- }
注意这里的Person::getFirstName,很显然getFirstName()并不是static的,这是jdk做了封装的缘故!
假如我们的排序算法改为:先按照lastName,然后按照age排序呢?
- @Test
- public void test12() {
- Student[] students = new Student[]{
- new Student("Ted", "Neward", 41),
- new Student("Charlotte", "Neward", 41),
- new Student("Michael", "Neward", 19),
- new Student("Matthew", "Neward", 13)
- };
- //sort by firstName
- /* Collections.sort(Arrays.asList(students), (lhs, rhs)->{
- if(lhs.getLastName().equals(rhs.getLastName())){
- return lhs.getAge()-rhs.getAge();
- }else{
- return lhs.getLastName().compareTo(rhs.getLastName());
- }
- });
- */
- // 上面可以改为这样子
- Collections.sort(Arrays.asList(students),
- Comparator.comparing(Student::getLastName).thenComparing(Student::getAge));
- System.out.println(Arrays.asList(students));
- }
还有更多的诸如:andThen()这样的方法,可以查api。
构造函数引用
- public class Person {
- String firstName;
- String lastName;
- public Person() {}
- public Person(String firstName, String lastName) {
- this.firstName = firstName;
- this.lastName = lastName;
- }
- }
接下来我们指定一个用来创建Person对象的对象工厂接口:
- public interface PersonFactory<P extends Person> {
- P create(String firstName, String lastName);
- }
这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:
- @Test
- public void test4() {
- //我们只需要使用 Person::new 来获取Person类构造函数的引用,
- // Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。
- PersonFactory<Person> personFactory = Person::new;
- Person person = personFactory.create("Peter", "Parker");
- }
我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。
八、访问对象字段与静态变量
和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:
- class Lambda4 {
- static int outerStaticNum;
- int outerNum;
- void testScopes() {
- Converter<Integer, String> stringConverter1 = (from) -> {
- outerNum = 23;
- return String.valueOf(from);
- };
- Converter<Integer, String> stringConverter2 = (from) -> {
- outerStaticNum = 72;
- return String.valueOf(from);
- };
- }
- }
以上是关于JDK8新特性 Lambda表达式的主要内容,如果未能解决你的问题,请参考以下文章
jdk8新特性--使用lambda表达式的延迟执行特性优化性能