Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本,Java 8 增加了许多的新特性,在此先记录一下 Lambda 表达式,接口默认方法和方法引用这三个特性。
Lambda 表达式
Lambda 表达式,也可称为闭包,允许把函数作为一个方法的参数(函数作为参数传递进方法中),使用 Lambda 表达式可以使代码变的更加简洁紧凑。
语法格式:
1
| (parameters) -> expression 或 (parameters) ->{ statements; }
|
主要特征:
- 可选类型声明,不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号,一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号,如果主体只包含一个语句,就不需要使用大括号。
- 可选的返回关键字,如果主体只有一个表达式返回值则编译器会自动返回值,大括号的话需要指明表达式返回了一个数值。
下面通过一些小例子来展示,首先先定义两个函数式接口:
1 2 3
| interface MathOperation { int operate(int a, int b); }
|
1 2 3
| interface Greeting { void sayMessage(String greeting); }
|
创建一个测试类,通过 main 方法来进行测试,代码中有详细注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public static void main(String[] args) { Java8 java8 = new Java8(); //参数类型可声明可不声明 MathOperation addition = (int a, int b) -> a + b; MathOperation subtraction = (a, b) -> a - b; //可选的参数圆括号 Greeting greeting = message -> System.out.println("hello " + message); Greeting greeting1 = (message) -> System.out.println("hello " + message); greeting.sayMessage("Tom"); greeting1.sayMessage("Jim"); //可选的大括号以及可选的返回关键字,当有大括号时返回值需指定 return 语句 MathOperation multiplication = (int a, int b) -> { return a * b; }; MathOperation division = (a, b) -> a / b; //测试执行输出,将上面定义的函数传进 operate 方法中 System.out.println(java8.operate(1, 2, addition)); //上面的另一种写法 System.out.println(addition.operate(1, 2)); System.out.println(java8.operate(1, 2, subtraction)); System.out.println(java8.operate(1, 2, multiplication)); System.out.println(java8.operate(1, 2, division)); System.out.println(java8.operate(1,2, (a, b) -> a + b)); }
private int operate(int a, int b, MathOperation mathOperation) { return mathOperation.operate(a, b); }
|
接口默认方法
默认方法就是接口可以有实现方法,而且不需要实现类去实现这个默认方法,定义时我们只需在方法名前面加个 default 关键字即可实现默认方法。
至于为什么会产生这个特性?首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是当需要修改接口时候,需要修改它的全部实现类,目前 java 8之前的集合框架没有 foreach 方法,通常能想到的解决办法是在 JDK 里给相关的接口添加新的方法定义及给相应实现类添加实现,然而对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现,所以才引进的默认方法。目的就是为了解决接口的修改与现有的实现不兼容的问题。
下面是接口默认方法和接口静态默认方法的基本实现:
1 2 3 4 5 6 7 8 9 10 11
| interface Action1 { //默认方法 default void eat() { System.out.println("吃东西1"); } //静态默认方法 static void fun() { System.out.println("玩"); } void call(); }
|
1 2 3 4 5
| interface Action2 { default void eat() { System.out.println("吃东西2"); } }
|
这里接口 Action1 和 Action2 都有 eat 这个相同的默认方法,当一个实现类同时实现了这个两个接口时就需要实现类显式的覆盖两个接口中相同的 eat 方法,至于实现类中如何重写 eat 方法取决于实现类,实现类既可以完完全全由自己重写,也可以使用 super 来调用两个接口中的 eat 方法。上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Cat implements Action1, Action2 { @Override public void eat() { Action1.super.eat(); }
/*@Override public void eat() { Action2.super.eat(); }*/ /*@Override public void eat() { Action1.super.eat(); Action2.super.eat(); Action1.fun(); }*/ @Override public void call() { } }
|
方法引用
方法引用通过方法的名字来指向一个方法,方法引用可以使语言的构造更紧凑简洁,减少冗余代码,方法引用使用一对冒号 :: 表示。当要传递给Lambda体内的操作已经有实现的方法了,就可以使用方法引用了。
先定义一个函数式接口和一个测试类以及类中的一些方法:
1 2 3
| interface Say<T> { void accept(T t1, T t2); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class Student {
private String name = "default";
public Student() { }
public Student(String name) { this.name = name; }
public static Student create(Supplier<Student> studentSupplier) { return studentSupplier.get(); }
public static String study() { System.out.println("study"); return "knowledge"; }
public String fun() { System.out.println( "fun"); return "fun"; }
public void ask() { System.out.println(this.getName() + " ask"); } public void say(Student student) { System.out.println(this.getName() + " say to" + student.getName()); } //set get 方法已省略 }
|
通过 main 方法测试方法引用的具体使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static void main(String[] args) { //构造器引用:它的语法是Class::new,或者更一般的Class< T >::new Student student = Student.create(Student :: new ); Student student1 = Student.create(() -> new Student("小红")); Student student2 = Student.create(() -> new Student("小绿")); //静态方法引用:它的语法是Class::static_method Supplier<String> supplier1 = Student :: study; System.out.println(supplier1.get());
//特定类的任意对象的方法引用:它的语法是Class::method Consumer<Student> consumer = Student :: ask; //下面 student 参数是 ask 方法的调用者 consumer.accept(student); //特定类的任意对象的方法引用:它的语法是Class::method Say<Student> say = Student :: say; //下面 student1 参数是 say 方法的调用者,student2 参数是 say 方法的参数 say.accept(student1, student2);
//特定对象的方法引用:它的语法是instance::method supplier1 = student :: fun; System.out.println(supplier1.get()); }
|
注意上面静态方法引用和特定类的任意对象的方法引用的区别,前者是一个静态方法,可通过类访问,后者则是参数列表的第一个参数是实例方法的调用者,第二个参数(或无参)是实例方法的参数时才会采取这种语法。
在使用方法引用时一定注意:
- 方法引用所引用的方法的参数列表必须要和函数式接口中抽象方法的参数列表完全一致。
- 方法引用所引用的方法的的返回值必须要和函数式接口中抽象方法的返回值完全一致