Lambda表达式是Java8中新增的特性,lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。在以前定义的方法中,只能将基本类型或者引用类型的变量作为方法参数,在Java 8以后可以将一个代码片段作为方法参数。
在集合中Java为开发者提供了遍历集合的简洁方式,如下例所示:
package cn.bytecollege;
import java.util.List;
import java.util.ArrayList;
public class LambdaDemo {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.forEach(e->System.out.println(e));
}
}
在上面的示例中,调用了list对象的foreach方法,从程序可以看出,传入foreach的并不是一个变量,而是一段代码,这就是Lambda表达式。从上面的语法可以看出,Lambda表达式的主要作用就是代替匿名内部类的烦琐语法。
Lambda由3部分组成:
下面,通过示例来学习Lambda的写法:
//表达式只有1个参数
(a)->{
System.out.print(a);
}
//表达式可以简写为
a->{
System.out.print(a);
}
//如果代码块中只有1条语句,可以省略大括号
a->System.out.print(a)
Lambda表达式的目标类型必须是函数式接口,所谓函数式接口代表只包含一个抽象方法的接口,函数式接口可以包含多个默认方法、类方法,但是只能声明一个抽象方法。
如果采用匿名内部类语法来创建函数式接口的实例,则只需要实现一个抽象方法,在这种情况下可采用Lambda表达式来创建对象。
注意:Java8 专门为函数式接口提供了@FunctionalInterface注解,该注解通常放在接口定义前,该注解对程序功能没有任何作用,它的作用是用于告诉编译器执行更严格的检查,检查该接口必须是函数式接口,否则编译器出错。
Lambda表达式的结果就是被作为对象,程序中晚期可以使用Lambda表达式进行赋值,例如在多线程Thread类的构造器中可以传入Runnable接口的子类对象。查看Runnable接口发现,该接口也被声明为一个函数式接口:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
所以,就可以使用Lambda表达式来创建线程:
package cn.bytecollege;
public class ThreadLambdaDemo {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
});
thread.start();
}
}
Lambda 表达式实现的是匿名方法——因此它只能实现特定函数式接口中的唯一方法。这意味着 Lambda 表达式有如下两个限制。
下面定义一个函数式接口深入学习Lambda表达式
package cn.bytecollege;
//函数式接口只能有一个抽象方法,并且要使用@FunctionalInterface声明
@FunctionalInterface
public interface Consumer {
int add(int a,int b);
}
定义一个方法,方法参数是Consumer接口:
package cn.bytecollege;
public class MyTest {
public static int test(Consumer consumer) {
int a = 5;
int b = 4;
return consumer.add(a, b);
}
public static void main(String[] args) {
int k = test((a,b)->{
return a+b;
});
System.out.println(k);
}
}
在上例中定义了一个函数式接口,在测试类的test方法传入了接口并调用了Consumer接口的add方法,需要注意的是,此时add方法并没有方法实现,在main方法中调用了test,并将一段代码(即add方法的实现)也就是lambda表达式当做参数传入了test方法。换句话说在上例中使用了lambda表达替代了烦琐的匿名内部类。对比下面的代码就可以看出Lambda表达式的独到之处。
package cn.bytecollege;
public class MyTest {
public static int test(Consumer consumer) {
int a = 5;
int b = 4;
return consumer.add(a, b);
}
public static void main(String[] args) {
int k = test(new Consumer() {
@Override
public int add(int a, int b) {
return a+b;
}
});
System.out.println(k);
}
}
从前面的程序可以看出Lambda表达式的使用离不开函数式接口,通常函数式接口中有且只能有1个抽象方法,这样使用Lambda表达式时也就明确了是哪个抽象方法的实现,如果接口中出现了多个抽象方法,那么就不能在接口上使用@FunctionInterface注解,会编译出错。因此,Java 8在java.util.function包中预定义了大量函数式接口,通常情况下这些接口完全可以满足开发需要:
下面在程序中示范上述接口的使用:
package cn.bytecollege.lambda;
import java.util.function.Function;
/**
* 数据转换
* @author MR.W
*
*/
public class CastUtil {
/**
* 定义方法将Object类型转换为String类型
* @param function
* @param o
* @return
*/
public static String castToString(Function
在上面的CastUtil类中定义了castToString,在该方法中第一个参数是一个Java 8 预定义的函数式接口,在方法内调用了Function接口的apply()方法,作用是将任意类型转换成String。但是此时这个方法并没有方法的实现,需要在调用此方法时传入方法的实现。
package cn.bytecollege.lambda;
import java.util.function.Function;
public class Test {
public static void main(String[] args) {
Integer a = 10010;
//使用Lambda表达式,此时castToString方法的第一个参数
//就是Function函数式接口apply()的实现
String s = CastUtil.castToString((o)->{
return String.valueOf(o);
}, a);
System.out.println(s);
}
}
在测试类中,调用了CastUtil的castToString()方法,并传入了Lambda表达式,以此Lambda表达式作为apply()方法的实现,在表达式中使用了String.valueOf()方法将对象转换成String类型。
前面已经介绍过,如果Lambda 表达式的代码块只有一条代码,程序就可以省略 Lambda 表达式中代码块的花括号。不仅如此,如果Lambda 表达式的代码块只有一条代码,还可以在代码块中使用方法引用和构造器引用。方法引用和构造器引用可以让 Lambda表达式的代码块更加简洁。方法引用和构造器引用都需要使用两个英文冒号。
Lambda 表达式支持如下表所示的几种引用方式。
下面的示例将演示类方法的引用,首先定义一个函数式接口,接口中定义抽象方法castToString(),该方法的作用是将一个对象转换成String对象。
package cn.bytecollege.lambda;
@FunctionalInterface
public interface Function {
R castToString(T t);
}
在String的学习中可以知道,String类有提供了类方法valueOf(Object o),该方法可以将任意对象转换成String类型,因此可以使用该方法作为Lambda表达式的实现代码:
package cn.bytecollege.lambda;
public class RefTest {
public static void main(String[] args) {
Function
在上面的代码中,创建了Lambda表达式作为了Function接口中castToString()方法的实现。在Lambda表达式中调用了String.valueOf()方法来进行对象到字符串的转换,在代码第8行调用了function接口的castToString()方法,实际上调用了就是代码第5行创建的Lambda表达式。
上面的Lambda表达式的代码块只有一行调用类方法的代码,因此可以使用如下方法引用进行替换。代码如下:
package cn.bytecollege.lambda;
public class RefTest {
public static void main(String[] args) {
Function
对于上面的类方法的引用,也就是调用了String类的valueOf()方法来实现Function函数式接口中唯一抽象方法。当调用castToString()方法时,调用参数将会传给String类的valueOf()类方法。
下面演示第二种方法引用,引用对象的实例方法,首先使用Lambda表达式创建一个Function接口的子类对象:
Function
上面的Lambda表达式只有一条语句,因此省略了该代码的花括号。
接下来程序调用function对象的castToString()方法:
package cn.bytecollege.lambda;
public class RefTest {
public static void main(String[] args) {
Function
上面的程序调用了function对象的castToString()方法时,由于function对象是Lambda表达式创建,castToString()方法的执行体就是Lambda表达式的代码部分,因此上面的程序输出了100.
上面的Lambda表达式代码只有一行,且调用了对象的o的toString()实例方法。因此代码可以进行如下替换:
package cn.bytecollege.lambda;
public class RefTest {
public static void main(String[] args) {
Function
上面的Lambda表达式的代码只有一条语句,因此省略了代码块的花括号;而且由于表达式实现的castToString方法需要返回值,因此Lambda表达会将这行代码的值作为返回值。此时就可以使用方法引用进行替换,直接引用Object的toString()方法作为Lambda表达式的代码块。其中Function接口的castToString方法有个参数,当执行Lambda表达式代码块时,会自动调用传入参数的toString()方法。
下面的实例将演示如何引用构造器,首先定义函数式接口:
package cn.bytecollege.lambda;
@FunctionalInterface
public interface MyInterface {
StringBuilder get(String s);
}
该函数式接口包含了一个get()抽象方法,该方法的作用是使用String对象生成一个StringBuilder对象,接着使用Lambda表达式创建一个MyInterface的对象:
package cn.bytecollege.lambda;
public class RefTest3 {
public static void main(String[] args) {
MyInterface myInterface = (s)-> new StringBuilder(s);
StringBuilder sb = myInterface.get("张三");
}
}
上面的代码调用了myInterface对象的get()方法时,由于该对象是Lambda表达式创建的,因此get()方法执行体就是Lambda表达式的代码块部分,即执行体就是执行new StringBuilder(a)语句,并将这条语句的值作为方法的返回值。因此上面代码中Lambda表达式的代码可以进行如下替换:
package cn.bytecollege.lambda;
public class RefTest3 {
public static void main(String[] args) {
MyInterface myInterface = StringBuilder::new;
StringBuilder sb = myInterface.get("张三");
}
}
对于上面的构造器引用,也就是调用StringBuilder类的构造方法来实现MyInteface函数式接口中唯一的抽象方法,当调用MyInterface接口的get()方法时,调用参数会传给StringBuilder构造器,从上面的程序中可以看出,调用myInterface对象的get()方法时,实际只传入了一个String类型的参数,这个String类型的参数会被传给StringBuilder的构造器。
从前面介绍可以看出,Lambda 表达式是匿名内部类的一种简化,因此它可以部分取代匿名内部类的作用,Lambda 表达式与匿名内部类存在如下相同点。
首先创建函数式接口:
package cn.bytecollege.ano;
@FunctionalInterface
public interface Display {
int add(int a,int b);
default void print() {
System.out.println("Hello!");
}
}
package cn.bytecollege.ano;
public class LambdaTest {
private int age = 18;
private static String name = "Byte科技";
public void test() {
String book = "Java编程思想";
Display display = (a,b)->{
//访问外部类的实例变量
System.out.println(age);
//访问外部类的类变量
System.out.println(name);
//访问局部变量
System.out.println(book);
return a+b;
};
//调用display对象从接口继承的默认方法
display.print();
// book = "Java核心技术卷";
System.out.println(display.add(1, 2));
}
}
创建测试类:
package cn.bytecollege.ano;
public class Test {
public static void main(String[] args) {
LambdaTest test = new LambdaTest();
test.test();
}
}
上面的程序使用Lambda表达式创建了一个Display接口的对象,Lambda表达式分别访问了外部类的实例变量,类变量从这些来看Lambda表达式的代码块和匿名内部类的方法体是相同的。
和匿名内部类相似,由于Lambda表达式访问了了book局部变量,因此该局部变量相当于有一个隐式的final修饰,不允许对book局部变量重新赋值。
当程序使用 Lambda 表达式创建了 Display 的对象之后,该对象不仅可调用接口中唯一的抽象方法,也可调用接口中的默认方法。
Lambda表达式与匿名内部类主要存在如下区别:
页面更新:2024-05-01
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号