Lambada表达式
面向对象的思想
做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。
函数式编程思想
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
冗余的Runnable代码
当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable接口来定义任务内容,并使用java.lang.Thread类来启动该线程。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12
| public class Demo01Runnable { public static void main(String[] args) { Runnable task = new Runnable() { @Override public void run() { System.out.println("多线程任务执行!"); } }; new Thread(task).start(); } }
|
本着一切皆对象的思想,这种做法是无可厚非的:首先创建一个Runnable接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。
对于Runnable的匿名内部类用法,可以分析出几点内容:
Thread类需要Runnable接口作为参数,其中的抽象run方法是用来指定线程任务内容的核心;
为了指定run的方法体,不得不需要Runnable接口的实现类;
为了省去定义一个RunnableImpl实现类的麻烦,不得不使用匿名内部类;
必须覆盖重写抽象run方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
而实际上,似乎只有方法体才是关键所在。
编程思想转换
我们真的希望创建一个匿名内部类对象吗?不,我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将run方法体内的代码传递给Thread类知晓。
传递一段代码,这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。
Lambda表达式的标准格式
解释说明格式:
():接口中抽象方法的参数列表,没有参数就空着,有参数就写出参数,多个参数使用逗号分隔
->:传递的意思,把参数传递给方法体{}
{}:重写接口的抽象方法的方法体
Lambda的使用前提
Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。无论是JDK内置的Runnable、Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
- 使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Demo02Lambda { public static void main(String[] args) { new Thread(new Runnable(){ @Override public void run() { System.out.println(Thread.currentThread().getName()+" 新线程创建了"); } }).start();
new Thread(()->{ System.out.println(Thread.currentThread().getName()+" 新线程创建了"); } ).start();
new Thread(()->System.out.println(Thread.currentThread().getName()+" 新线程创建了")).start(); } }
|
省略格式
凡是根据上下文推导出来的内容,都可以省略书写
可以省略的内容:
- (参数列表): 括号中参数列表的数据类型可以省略不写
- (参数列表): 括号中的参数只有一个,那么类型和()都可以省略
- {一些代码}: 如果{}中的代码只有一行,无论是否有返回值,都可以省略( {},return,分号 )
注意:要省略{},return,分号必须一起省略。
给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数字相加得到和值
1 2 3 4
| public interface Calculator { public abstract int calc(int a,int b); }
|
在下面的代码中,请使用Lambda的标准格式调用invokeCalc方法,完成120和130的相加计算
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
| public class Demo01Calculator { public static void main(String[] args) { invokeCalc(10, 20, new Calculator() { @Override public int calc(int a, int b) { return a+b; } });
invokeCalc(120,130,(int a,int b)->{ return a + b; }); invokeCalc(120,130,(a,b)-> a + b); }
public static void invokeCalc(int a,int b,Calculator c){ int sum = c.calc(a,b); System.out.println(sum); } }
|
方法引用
我们用Lambda表达式来实现匿名方法。但有些情况下,我们用Lambda表达式仅仅是调用一些已经存在的方法,除了调用动作外,没有其他任何多余的动作,在这种情况下,我们倾向于通过方法名来调用它,而Lambda表达式可以帮助我们实现这一要求,它使得Lambda在调用那些已经拥有方法名的方法的代码更简洁、更容易理解。
方法引用可以理解为Lambda表达式的另外一种表现形式。方法引用可以根据方法自动推断参数的类型
实例方法的引用
1 2 3 4 5 6 7 8
|
@FunctionalInterface public interface Printable { void print(String s); }
|
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 class Demo01Printable { public static void printString(Printable p) { p.print("HelloWorld"); }
public static void main(String[] args) { printString((s) -> { System.out.println(s); });
printString(System.out::println); } }
|
例如上例中, System.out 对象中有一个重载的 println(String) 方法恰好就是我们所需要的。
那么对于 printString 方法的函数式接口参数,对比下面两种写法,完全等效。
Lambda表达式写法:
1
| s -> System.out.println(s);
|
方法引用写法:
第一种语义是指:拿到参数之后经Lambda之手,继而传递给 System.out.println 方法去处理
第二种等效写法的语义是指:直接让 System.out 中的 println 方法来取代Lambda
两种写法的执行效果完全一 样,而第二种方法引用的写法复用了已有方案,更加简洁。
注:Lambda 中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常
静态方法的引用
1 2 3 4 5
| @FunctionalInterface public interface Calcable { int calsAbs(int number); }
|
静态方法引用要用类名
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
| class Demo01StaticMethodReference { public static int method(int number,Calcable c){ return c.calsAbs(number); }
public static void main(String[] args) { int number = method(-10,(n)->{ return Math.abs(n); }); System.out.println(number);
int number2 = method(-10, Math::abs); System.out.println(number2); } }
|
类的构造器引用
1 2 3 4 5 6 7 8 9 10 11 12 13
| @FunctionalInterface public interface PersonBuilder { Person builderPerson(String name); }
public class Person { private String name; public Person() {} public Person(String name) {this.name = name;} public String getName() {return name;} public void setName(String name) {this.name = name;} }
|
构造方法new Person(String name) 已知,创建对象已知 new
就可以使用Person引用new创建对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Demo { public static void printName(String name,PersonBuilder pb){ Person person = pb.builderPerson(name); System.out.println(person.getName()); }
public static void main(String[] args) { printName("迪丽热巴",(String name)->{ return new Person(name); });
printName("古力娜扎",Person::new); } }
|
数组的构造器引用
1 2 3 4 5
| @FunctionalInterface public interface ArrayBuilder { int[] builderArray(int length); }
|
已知创建的就是int[]数组,数组的长度也是已知的
可以使用方法引用int[] 引用new,根据参数传递的长度来创建数组
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
| public class Demo {
public static int[] createArray(int length, ArrayBuilder ab){ return ab.builderArray(length); }
public static void main(String[] args) { int[] arr1 = createArray(10,(len)->{ return new int[len]; }); System.out.println(arr1.length);
int[] arr2 =createArray(10,int[]::new); System.out.println(Arrays.toString(arr2)); System.out.println(arr2.length); } }
|