让我给您介绍一个编程场景,如果您已经使用Java编程一段时间,那么您可能已经很熟悉了。 通常,您需要覆盖功能界面为事件监听器提供回调或提供函数的自定义实现,让我们看一下下面的例子作为复习,我们将一个回调方法传递给Greeter类,它在延迟5秒后调用我们提供的回调方法,这是一个函数接口的例子:
interface onGreetCallback{
public void greet(String greetMsg);
}
class Greeter{
private onGreetCallback callback;
Greeter(onGreetCallback callback){
this.callback = callback;
//call the provided callback after 5 seconds delay
try{
Thread.sleep(5000);
this.callback.greet("Hello, Pardon me, I am 5 seconds late to greet you.");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
class DEMO{
public static void main(String args[]){
Greeter greeter = new Greeter(new onGreetCallback(){
@Override
public void greet(String greetMsg){
System.out.println(greetMsg);
}
});
}
}
上面的代码非常常见,您每天都会在java中遇到类似的代码。正如您所看到的,编写正在传递到Greeter类构造函数的重载方法需要一些时间。
从Java 8开始,添加了Lambda表达式来简化这部分代码,使其看起来更干净、更优雅,更短。
让我们再次编写主函数,这次使用Lambda表达式。
public static void main(String args[]){
Greeter greeter = new Greeter(msg -> System.out.println(msg));
}
Lambda表达式仅为我们节省了至少4行代码。 在下面的文章中,我将详细讨论lambda表达式的语法、使用位置以及与旧方法的一些比较。 因此,事不宜迟,让我们深入探讨Java Lambda Expression的细节。
在上面修改的代码中,msg -> System.out.println(msg)是lambda表达式代码,其中msg是输入参数
println(msg)是lambda表达式的主体,->将输入参数从lambda的主体中分离出来。
上面的lambda表达式是用一种非常精简的方式编写的,更扩展的版本应该是这样的。
Greeter greeter = new Greeter((msg) -> {
System.out.println(msg);
});
如果只有一个输入参数,则可以跳过方括号,但是如果有多个输入参数,则必须使用方括号,例如 让我们也传递名称参数放入我们的示例中。
Greeter greeter = new Greeter((msg, name) -> {
System.out.println("Hello " + name);
System.out.println(msg);
});
仅当您具有多行代码时,才需要大括号,这就是为什么我向您展示的第一个lambda表达式非常紧凑。
public static void main(String args[]){
Greeter greeter = new Greeter(msg -> System.out.println(msg));
}
如果只有一条语句,则也不需要return关键字。 让我们看一个示例,其中在lambda主体中有多个语句,然后查看如何仅用一行就可以编写相同的lambda表达式,以便删除显式的return关键字。
interface FilterInterface{
boolean filter(int number);
}
//Class to call the user implementation of method, on given data
class Utils {
private FilterInterface filterInterface;
//User provided Transformation Method
void setFilterMethod(FilterInterface filterMethod) {
this.filterInterface = filterMethod;
}
//Transform method to apply user provided Method on given data
List transform(List data) {
List filtered_data = new ArrayList<>();
for (int i = 0; i < data.size(); i++) {
//Test with user provided Filter method
if (this.filterInterface.filter(data.get(i))) {
filtered_data.add(data.get(i));
}
}
return filtered_data;
}
}
public class Main{
public static void main(String[] args) {
List data = new ArrayList<>();
data.add(1);
data.add(2);
data.add(3);
data.add(4);
data.add(5);
data.add(6);
Utils utils = new Utils(); //Instantiate object of our custom util class
//Let's provide a Lambda Expression to filter Even numbers
/*
* Because we have more than a single line of code, we have to use the return keyword
* */
utils.setFilterMethod(x -> {
if (x % 2 ==0){
return true;
}
return false;
});
//Apply the provided transformation function
List filtered_data = utils.transform(data);
}
}
我故意以这种长的方式编写了上面的lambda表达式,让我们用一行代码来编写它,并跳过return关键字。
utils.setFilterMethod(x -> x % 2 == 0);
Lambda表达式到函数接口的映射
为了将lambda映射到一个功能接口,必须满足一些条件。
- 接口必须至少包含一个抽象方法。
- Lambda表达式的输入参数必须与抽象方法的输入参数匹配。
- lambda表达式的返回类型必须与抽象方法的返回类型相匹配。
Lambda表达式还允许捕获变量。
您可以捕获在lambda主体之外声明的变量功能。 可以在主体内部捕获以下变量 lambda。
- 局部变量。
- 实例变量。
- 静态变量。
局部变量捕获
class DEMO{
final String name = "Aamir";
public static void main(String args[]){
Greeter greeter = new Greeter((msg) -> {
System.out.println("Hello " + name);
System.out.println(msg);
});
}
}
在上面的示例中,我们正在捕获名称lambda体内的变量。被捕获的局部变量必须声明为final,如果未将变量声明为final,则Java编译器将抱怨。
实例变量捕获
Lambda表达式还允许您捕获实例变量,这里需要注意的重要区别是,这在经典方法中是不可能实现的,在经典方法中,您匿名实现接口,这是因为匿名接口具有其自己的主体,而lambda表达式没有。
class MyClass{
private String name = "Aamir";
Greeter greeter = new Greeter((msg) -> {
System.out.println("Hello " + this.name);
System.out.println(msg);
});
}
静态变量捕获
class DEMO{
private static String name = "Aamir";
public static void main(String args[]){
Greeter greeter = new Greeter((msg) -> {
System.out.println("Hello " + name);
System.out.println(msg);
});
}
}
既然我们已经了解了lambda表达式的工作原理,其结构和不同的语法风格,下面让我们从中查看更多实际示例Java Stream API,它允许您以声明的方式编写代码。
跨数组或列表应用转换/映射函数
public class Main{
public static void main(String[] args) {
int fav_nums[] = {1,2,3,4,5};
//Apply the array transformation Using a Lambda Expression
int new_fav_nums[] = Arrays.stream(fav_nums).map(x -> x *2).toArray(); // 2,4,6,8,10
}
}
根据起始字符筛选名称
public class Main{
public static void main(String[] args) {
String names[] = {"Aamir" ,
"Ali" ,
"Atif" ,
"Siraj" ,
"Shehriyaar" ,
"Shehroz" ,
"Hassan" ,
"Jibran"};
//Applying filter to only get names starting with "A"
String filtered_names[] = Arrays.stream(names).filter(name -> name.startsWith("A")).toArray(String[]::new);
//Aamir
// Ali
// Atif
}
}
实际上,通过抽象,我们可以使上面的代码更加紧凑 lambda表达式中的逻辑,并通过传入方法参考将其放入on方法中。
class MyFilters {
public static boolean filterName(String name){
return name.startsWith("A");
}
}
public class Main{
public static void main(String[] args) {
String names[] = {"Aamir" ,
"Ali" ,
"Atif" ,
"Siraj" ,
"Shehriyaar" ,
"Shehroz" ,
"Hassan" ,
"Jibran"};
//Applying filter to only get names starting with "A"
//Using a method reference
String filtered_names[] = Arrays.stream(names).filter(MyFilters::filterName).toArray(String[]::new);
//Aamir
// Ali
// Atif
}
}
Filter将在数组中的每个项目上调用我们提供的Method参考。
对于那些询问将lambda表达式逻辑抽象到其自己的单独类中的人有什么用,让我分享一些用例,如果您需要应用某种类似的过滤器,那么此过程将非常有用, 映射、减少对不同数组或列表集的操作。或者您的lambda表达式主体变得过于混乱。 这也使您的代码对其他开发人员更具可读性,其他开发人员将通过您传入的方法引用的名称立即获得代码的含义,这将使他们更容易理解您的思考过程。 疯了,如果6个月后您决定进行一些更改。我知道我们都去过那里。
::通知Java编译器知道这是一个方法参考
您可以引用以下类型的方法:
- 静态方法。
- 参数对象的实例方法。
- 实例方法。
- 构造函数。
希望到目前为止,您已经对Java Lambda表达式是什么、如何编写它们以及它们的用法有了很好的了解。 了解并熟练掌握它们,它们将是您处理数据收集时的首选工具。
“始终专注于您已经走了多远,而不是必须走多远。”