Java基础知识(22)- Java 面向对象(四)| Effectively final、Lambda 表达式
本文上接 "Java基础知识(11)- Java 面向对象(三)",补充以下内容。
1. Effectively final
Java 中局部内部类和匿名内部类访问的局部变量必须由 final 修饰,以保证内部类和外部类的数据一致性。从 Java 8 开始,可以不加 final 修饰符,由系统默认添加,Java 将这个功能称为 Effectively final 功能。
示例代码:
1 package com.example; 2 3 public class App { 4 public static void main( String[] args ) { 5 6 String[] data = {"String 1", "String 2"}; 7 8 // 匿名类方式实现 Out 接口 9 Out out = new Out() { 10 @Override 11 public void display(String title) { 12 data[0] = "Data Changed"; 13 System.out.println(title + ": " + data[0]); 14 } 15 16 }; 17 out.display("Anonymous Class"); 18 } 19 } 20 21 interface Out { 22 void display(String title); 23 }
同样的代码, Java 7 中会出现代码错误,提示 data 必须显式的声明这个变量为 final。Java 8 开始,它不要求程序员必须将访问的局部变量显式的声明为 final。
final 变量能被显式地初始化并且只能初始化一次。被声明为 final 的对象的引用不能指向不同的对象。但是 final 对象里的数据可以被改变。也就是说 final 对象的引用不能改变,但是里面的值可以改变。
在 Lambda 表达式中,使用局部变量的时候,也要求该变量必须是 final 的,所以 effectively final 在 Lambda 表达式上下文中非常有用。
Lambda 表达式在编程中是经常使用的,而匿名内部类是很少使用的。那么,我们在 Lambda 编程中每一个被使用到的局部变量都去显示定义成 final 吗?显然这不是一个好方法。所以,Java 8 引入了 effectively final 新概念。
总结一下,规则没有改变,Lambda 表达式和匿名内部类访问的局部变量必须是 final 的,只是不需要程序员显式的声明变量为 final 的,从而节省时间。
2. Lambda 表达式
Lambda 表达式(Lambda expression)是一个匿名函数,基于数学中的 λ 演算得名,也可称为闭包(Closure)。现在很多语言都支持 Lambda 表达式,如 C++、C#、Java、 Python 和 JavaScript 等。
Lambda 表达式是推动 Java 8 发布的重要新特性,它允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
1) Lambda 表达式语法
(参数列表) -> {
// Lambda 表达式体
}
-> 被称为箭头操作符或 Lambda 操作符,箭头操作符将 Lambda 表达式拆分成两部分:
左侧:Lambda 表达式的参数列表。
右侧:Lambda 表达式中所需执行的功能,用 {} 包起来,即 Lambda 体。
示例代码:
1 package com.example; 2 3 public class App { 4 public static void main( String[] args ) { 5 6 String[] data = {"String 1", "String 2"}; 7 8 // 匿名类方式实现 Out 接口 9 Out out = new Out() { 10 @Override 11 public void display(String title) { 12 data[0] = "Data Changed"; 13 System.out.println(title + ": " + data[0]); 14 } 15 16 }; 17 out.display("Anonymous Class"); 18 19 // Lambda 表达式实现 Out 接口 20 Out out2 = (String title) -> { 21 System.out.println(title + ": " + data[1]); 22 }; 23 out2.display("Lambda"); 24 25 } 26 } 27 28 interface Out { 29 void display(String title); 30 }
很显然,实现同样功能,Lambda 表达式比匿名类方式代码更简洁。
2) Lambda 表达式的特性
优点:
(1) 代码简洁,开发迅速
(2) 方便函数式编程
(3) 非常容易进行并行计算
(4) Java 引入 Lambda,改善了集合操作(引入 Stream API)
缺点:
(1) 代码可读性变差
(2) 在非并行计算中,很多计算未必有传统的 for 性能要高
(3) 不容易进行调试
3) 函数式接口
Lambda 表达式实现的接口不是普通的接口,而是函数式接口。如果一个接口中,有且只有一个抽象的方法(Object 类中的方法不包括在内),那这个接口就可以被看做是函数式接口。这种接口只能有一个方法。如果接口中声明多个抽象方法,那么 Lambda 表达式会发生编译错误:
The target type of this expression must be a functional interface
这说明该接口不是函数式接口,为了防止在函数式接口中声明多个抽象方法,Java 8 提供了一个声明函数式接口注解 @FunctionalInterface,上文的 Out 接口可以修改成如下代码:
@FunctionalInterface
interface Out {
void display();
}
在接口之前使用 @FunctionalInterface 注解修饰,那么试图增加一个抽象方法时会发生编译错误。但可以添加默认方法和静态方法。
@FunctionalInterface 注解与 @Override 注解的作用类似。Java 8 中专门为函数式接口引入了一个新的注解 @FunctionalInterface。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
需要注意的是,即使不使用 @FunctionalInterface 注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
提示:Lambda 表达式是一个匿名方法代码,Java 中的方法必须声明在类或接口中,那么 Lambda 表达式所实现的匿名方法是在函数式接口中声明的。
4) Lambda 表达式的简写方式
1) 可以省略参数类型,因为就只有一个抽象方法,会自动匹配抽象方法中的参数类型;
2) 当只有一个参数时,可以省略 ();
3) 当方法体中只有一句代码语句时,可以省略 {}。如果有返回值,省略的话要同时省略 {} 和 return;
示例代码:
1 package com.example; 2 3 public class App { 4 public static void main( String[] args ) { 5 6 String[] data = {"String 1", "String 2"}; 7 8 // 匿名类方式实现 Out 接口 9 Out out = new Out() { 10 @Override 11 public void display(String title) { 12 data[0] = "Data Changed"; 13 System.out.println(title + ": " + data[0]); 14 } 15 16 }; 17 out.display("Anonymous Class"); 18 19 // Lambda 表达式实现 Out 接口 20 Out out2 = (String title) -> { 21 System.out.println(title + ": " + data[1]); 22 }; 23 out2.display("Lambda"); 24 25 // Lambda 表达式实现 Out 接口,省略参数类型 26 Out out3 = (title) -> { 27 System.out.println(title + ": " + data[1]); 28 }; 29 out3.display("Lambda 2"); 30 31 // Lambda 表达式实现 Out 接口,当只有一个参数时,可以省略 () 32 Out out4 = title -> { 33 System.out.println(title + ": " + data[1]); 34 }; 35 out4.display("Lambda 3"); 36 37 // Lambda 表达式实现 Out 接口,方法体中只有一句代码语句时,可以省略 {} 38 Out out5 = title -> System.out.println(title + ": " + data[1]); 39 out5.display("Lambda 4"); 40 41 } 42 } 43 44 @FunctionalInterface 45 interface Out { 46 void display(String title); 47 }
5) Lambda 表达式的使用
(1) 作为参数使用
Lambda 表达式一种常见的用途就是作为参数传递给方法,这需要声明参数的类型为函数式接口类型。
示例代码如下:
1 package com.example; 2 3 public class App { 4 public static void main( String[] args ) { 5 6 String[] data = {"String 1", "String 2"}; 7 8 // 作为参数使用的 Lambda 表达式 9 display(title -> { 10 System.out.println(title + ": " + data[1]); 11 }, "Lambda 5"); 12 13 } 14 15 public static void display(Out out, String title) { 16 out.display(title); 17 } 18 } 19 20 @FunctionalInterface 21 interface Out { 22 void display(String title); 23 }
(2) 访问变量
Lambda 表达式可以访问所在外层作用域定义的变量,包括成员变量和局部变量。
1 package com.example; 2 3 public class LambdaApp { 4 private static String global = "Global Variable"; 5 6 public static void main( String[] args ) { 7 8 String[] data = {"String 1", "String 2"}; 9 10 // 访问局部变量 data 和 成员变量 global 11 Out out6 = title -> { 12 System.out.println(title + ": " + data[1] + " | " + global); 13 }; 14 out6.display("Lambda 7"); 15 } 16 } 17 18 @FunctionalInterface 19 interface Out { 20 void display(String title); 21 }
(3) 方法引用
方法引用可以理解为 Lambda 表达式的快捷写法,它比 Lambda 表达式更加的简洁,可读性更高,有很好的重用性。如果实现复杂,复用的地方多,推荐使用方法引用。
Java 8 之后增加了双冒号::运算符,该运算符用于 “方法引用” ,注意不是调用方法。“方法引用” 虽然没有直接使用 Lambda 表达式,但也与 Lambda 表达式有关,与函数式接口有关。 方法引用的语法格式如下:
ObjectRef::methodName
其中,ObjectRef 是类名或者实例名,methodName 是相应的方法名。
注意:被引用方法的参数列表和返回值类型,必须与函数式接口方法参数列表和方法返回值类型一致,示例代码如下。
1 class LambdaDemo { 2 3 public static void display1(String str) { 4 System.out.println(str); 5 } 6 7 public void display2(String str) { 8 System.out.println(str); 9 } 10 }
LambdaDemo 类中提供了一个静态方法 add,一个实例方法 sub。这两个方法必须与函数式接口方法参数列表一致,方法返回值类型也要保持一致。
1 public class App { 2 public static void main(String[] args) { 3 4 String[] data = {"String 1", "String 2"}; 5 6 // 方法引用 7 display2(LambdaDemo::display1, "Lambda 8", data[0]); 8 9 // 访问成员方法需要先创建对象 10 LambdaDemo lambdaDemo = new LambdaDemo(); 11 display2(lambdaDemo::display2, "Lambda 9", data[1]); 12 } 13 14 public static void display2(Out out, String title, String str) { 15 out.display(title + ": " + str); 16 } 17 } 18 19 @FunctionalInterface 20 interface Out { 21 void display(String title); 22 }
浙公网安备 33010602011771号