Java登陆第十八天——Java8之Lambda表达式
在实例化Thread类时,需要传入一个Runnable接口的实现类。
public Thread(Runnable target)
实际开发中,通常是使用匿名内部类实现Runnable接口。
栗子:
public class Test27 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {//匿名内部类实现Runnable接口
@Override
public void run() {//run()方法的传参
System.out.println("hello,world!");//run()方法的方法体
}
});
t.start();
}
}
上述代码中对于开发人员,有书写价值的是
//run()方法的传参和方法体
() {
System.out.println("hello,world!");
}
于是乎,Java8提供了Lambda表达式,对于这种类似的匿名内部类的写法,进行缩减。
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {//匿名内部类实现Runnable接口
@Override
public void run() {
System.out.println("hello,world!");
}
});
t.start();
}
//上下等价
public static void main(String[] args) {
Thread t = new Thread( () -> { //lambda优化
System.out.println("hello,world!"); } );
t.start();
}
}
Lambda表达式让书写更具有简洁性,但可读性降低
验证语法糖
首先使用匿名内部类手动地抛一个异常。
栗子:
public class Test27 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
throw new ArrayIndexOutOfBoundsException();
}
});
t.start();
}
}
程序运行结果:
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException
at module3.Test27$1.run(Test27.java:8)
at java.lang.Thread.run(Thread.java:750)
注意看运行结果的第二行 at module3.Test27$1.run(Test27.java:8)
匿名内部类的形式会在编译后自动生成一个类。详情请看基于接口的匿名内部类内部结构解析
再次使用Lambda表达式手动地抛一个异常。
栗子:
public class Test27 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
throw new ArrayIndexOutOfBoundsException();
});
t.start();
}
}
程序运行结果:
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException
at module3.Test27.lambda$main$0(Test27.java:6)
at java.lang.Thread.run(Thread.java:750)
注意看运行结果的第二行 at module3.Test27.lambda$main$0(Test27.java:6)
可以看到,实际上是Test27类中的lambda$main$0()方法抛出的异常。
由此可确定Lambda表达式并不是匿名内部类的语法糖,而是编译后自动生成了一个方法供给接口使用。
Lambda规范
标准格式
([参数类型] 参数名,...) -> { 方法体(包括返回值) };
且Lambda仅支持接口,不支持抽象类。
接口内部必须且仅有一个抽象方法。(可以有多个默认方法,必须仅有一个抽象方法)
可以在接口上添加注解@FunctionalInterface,检查接口合法性。
@FunctionalInterface//添加了此注解的接口,都支持lambda表达式,符合函数式接口定义
public interface Runnable {
public abstract void run();
}
因此,接口Runnable可以由Lambda表达式缩写成:
public class Test27 {
public static void main(String[] args) {
Runnable r = () -> { /*方法体*/ };
}
}
自定义一个函数式接口
注解@FunctionalInterface检验合法的接口,称之为函数式接口。
@FunctionalInterface
interface TestLambda{//检验合法,接口TestLambda可以称之为函数式接口
String getString(String str);//接口中的方法默认public abstract
default void sayString(){
System.out.println("可以有多个默认方法,但必须仅有一个抽象方法");
}
}
使用Lambda表达式和匿名内部类,实现该接口
栗子:
public class Test27 {
public static void main(String[] args) {
TestLambda t1 = (str) -> {//lambda
return "123";
};
TestLambda t2=new TestLambda() {//匿名内部类
@Override
public String getString(String str) {
return "123";
}
};
}
}
@FunctionalInterface
interface TestLambda {
String getString(String str);//接口中的方法默认public abstract
default void sayString() {
System.out.println("可以有多个默认方法,但必须仅有一个抽象方法");
}
}
对于仅需一个参数的接口方法,可以省略小括号(多个参数不能省略小括号)
栗子:
public class Test27 {
public static void main(String[] args) {
TestLambda t = str -> {//一个参数可以省略小括号
return "123";
};
//lambda
TestLambda t2= (str) -> {
return "123";
};
}
}
@FunctionalInterface
interface TestLambda {
String getString(String str);//接口中的方法默认public abstract
default void sayString() {
System.out.println("可以有多个默认方法,但必须仅有一个抽象方法");
}
}
如果方法体中仅有返回语句一行,可以省略return和花括号
栗子:
public class Test27 {
public static void main(String[] args) {
TestLambda t = str -> "123";//可以省略return和花括号
TestLambda t2= str -> {"123";};//报错!!!不可以仅省略return"123"
}
}
@FunctionalInterface
interface TestLambda {
String getString(String str);//接口中的方法默认public abstract
default void sayString() {
System.out.println("可以有多个默认方法,但必须仅有一个抽象方法");
}
}
Lambda引用方法
除了手动编写方法体之外,还可以直接引用方法。
如果一个方法的传参和返回值均与某函数式接口中抽象方法相同时,就可以直接引用该方法。
如果该方法是静态方法
栗子:
public class Test27 {
public static void main(String[] args) {
TestLambda t1= Test27::test27Impl;//使用 类名::方法名 的形式来直接引用一个静态方法作为实现
TestLambda t2=str -> test27Impl(str);//省略小括号,return和花括号的lambda
TestLambda t3=(str) ->{//最基础的lambda
return test27Impl(str);
};
}
public static String test27Impl(String str){//该传参列表,返回值。都与函数式接口中getString()抽象方法相同。
return str+"lambda还可以直接引用已经存在的方法";
}
}
@FunctionalInterface
interface TestLambda {
String getString(String str);//接口中的方法默认public abstract
default void sayString() {
System.out.println("可以有多个默认方法,但必须仅有一个抽象方法");
}
}
上述栗子中,t1,t2,t3等价
如果该方法是普通方法
栗子:
public class Test27 {
public static void main(String[] args) {
Test27 test27 = new Test27();//使用 对象名::方法名 的形式来直接引用一个普通方法作为实现
TestLambda t= test27::test27Impl;
TestLambda t2=str -> test27.test27Impl(str);
TestLambda t3=(str) ->{
return test27.test27Impl(str);
};
}
public String test27Impl(String str){//该传参列表,返回值。都与函数式接口中getString()抽象方法相同。
return str+"lambda还可以直接引用已经存在的方法";
}
}
@FunctionalInterface
interface TestLambda {
String getString(String str);//接口中的方法默认public abstract
default void sayString() {
System.out.println("可以有多个默认方法,但必须仅有一个抽象方法");
}
}
如果该方法是构造方法
对于函数式接口TestLambda,符合其抽象方法的传参列表和返回值的构造方法。有String的构造方法
public String(String original) {//String的构造方法
this.value = original.value;
this.hash = original.hash;
}
栗子:
public class Test27 {
public static void main(String[] args) {
TestLambda t= String::new;//使用 类名::new 的形式直接引用一个构造方法
TestLambda t2=str -> new String(str);
TestLambda t3=(str) ->{
return new String(str);
};
}
}