做者:汤圆java
我的博客:javalover.cc设计模式
你们好啊,我是汤圆,今天给你们带来的是《Java8中的Lambda表达式》,但愿对你们有帮助,谢谢安全
文章纯属原创,我的总结不免有差错,若是有,麻烦在评论区回复或后台私信,谢啦
Lambda表达式是一个可传递的代码块,能够在之后执行一次或屡次;并发
下面贴个对比代码:app
// Java8以前:旧的写法 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("old run"); } }; Thread t = new Thread(runnable); // Java8以后:新的写法 Runnable runnable1 = ()->{ System.out.println("lambda run"); }; Thread t1 = new Thread(runnable1);
能够看到,有了lambda,代码变得简洁多了ide
你能够把lambda看成一个语法糖函数
下面让咱们一块儿来探索lambda的美好世界吧ui
下面列出本文的目录this
下面分别说下语法中的三个组成部分spa
参数: ( Dog dog )
Comparator<String> comparatorTest = (a, b)->a.length()-b.length();
,能够推导出a,b都为String->
主体:{ System.out.println("javalover"); }
{;}
(好比上图所示)a.length()- b.length()
)为了简化代码
由于Java是面向对象语言,因此在lambda出现以前,咱们须要先构造一个对象,而后在对象的方法中实现具体的内容,再把构造的对象传递给某个对象或方法
可是有了lambda之后,咱们能够直接将代码块传递给对象或方法
如今再回头看下开头的例子
能够看到,用了lambda表达式后,少了不少模板代码,只剩下一个代码块(最核心的部分)
就是只定义了一个抽象方法的接口
@FunctionalInterface public interface FunctionInterfaceDemo { void abstractFun(); default void fun1(){ System.out.println("fun1"); } default void fun2(){ System.out.println("fun2"); } }
这里的注解@FunctionalInterface能够省略,可是建议加上,就是为了告诉编译器,这是一个函数式接口,此时若是该接口有多个抽象方法,那么编译器就会报错
// 编译器会报错,Multiple non-overriding abstract methods found in XXX @FunctionalInterface public interface NoFunctionInterfaceDemo extends FunctionInterfaceDemo{ void abstractFun2(); }
上面的父接口FunctionInterfaceDemo中已经有了一个抽象方法,此时NoFunctionInterfaceDemo又定义了一个抽象方法,结果编译器就提示了:存在多个抽象方法
在Java8以前,其实咱们已经接触过函数式接口
好比Runnable 和 Comparable
只是没有注解@FunctionalInterface。
那这个函数式接口要怎么用呢?
配合lambda食用,效果最佳(就是把lambda传递给函数式接口),示例代码以下:
new Thread(() -> System.out.println("run")).start();
其中用到的函数式接口是Runnable
就是把行为定义成参数,行为就是函数式接口
相似泛型中的类型参数化<T>
,类型参数化是把类型定义成参数
行为参数化,通俗点来讲:
这样来看的话,行为参数化和设计模式中的策略模式有点像了(后面章节会分别讲经常使用的几种设计模式)
下面咱们手写一个函数式接口来加深理解吧
下面咱们按部就班,先从简单的需求开始
public static String processFile() throws IOException { // Java7新增的语法,try(){},可自动关闭资源,减小了代码的臃肿 try( BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\JavaProject\\JavaBasicDemo\\test.txt"))){ return bufferedReader.readLine(); } }
能够看到,核心的行为动做就是 return bufferedReader.readLine();
,表示读取第一行的数据并返回
那若是咱们想要读取两行呢?三行?
@FunctionalInterface interface FileReadInterface{ // 这里接受一个BufferedReader对象,返回一个String对象 String process(BufferedReader reader) throws IOException; }
能够看到,只有一个抽象方法process()
,它就是用来处理第一步中的核心动做(读取文件内容)
至于想读取多少内容,那就须要咱们在lambda表达式中定义了
// 读取一行 FileReadInterface fileReadInterface = reader -> reader.readLine(); // 读取两行 FileReadInterface fileReadInterface2 = reader -> reader.readLine() + reader.readLine();
processFile()
,让其接受一个函数式接口,并调用其中的抽象方法,代码以下:// 参数为第二步咱们本身手写的函数式接口 public static String processFile(FileReadInterface fileReadInterface) throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ // 这里咱们再也不本身定义行为,而是交给函数式接口的抽象方法来处理,而后经过lambda表达式的传入来实现多个行为 return fileReadInterface.process(bufferedReader); } }
public class FileReaderDemo { public static void main(String[] args) throws IOException { // 第三步: // lambda表达式1 传给 函数式接口:只读取一行 FileReadInterface fileReadInterface = reader -> reader.readLine(); // lambda表达式2 传给 函数式接口:只读取两行 FileReadInterface fileReadInterface2 = reader -> reader.readLine() + reader.readLine(); // 最后一步: 不一样的函数式接口的实现,表现出不一样的行为 String str1 = processFile(fileReadInterface); String str2 = processFile(fileReadInterface2); System.out.println(str1); System.out.println(str2); } // 第四步: 读取文件方法,接受函数式接口做为参数 public static String processFile(FileReadInterface fileReadInterface) throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ // 调用函数式接口中的抽象方法来处理数据 return fileReadInterface.process(bufferedReader); } } // 第一步: public static String processFile() throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ return bufferReader.readLine(); } } } // 第二步: 咱们手写的函数式接口 @FunctionalInterface interface FileReadInterface{ String process(BufferedReader reader) throws IOException; }
其实你会发现,咱们手写的这个函数式接口,其实就是Function<T>
去除泛型化后的接口,以下所示:
@FunctionalInterface public interface Function<T, R> { // 都是接受一个参数,返回另外一个参数 R apply(T t); }
下面咱们列出Java中经常使用的一些函数式接口,你会发现自带的已经够用了,基本不会须要咱们本身去写
这里的手写只是为了本身实现一遍,能够加深理解程度
咱们先看一个例子
前面咱们写的lambda表达式,其实还能够简化,好比
// 简化前 Function<Cat, Integer> function = c->c.getAge(); // 简化后 Function<Cat, Integer> function2 = Cat::getAge;
其中简化后的Cat::getAge
,咱们就叫作方法引用
方法引用就是引用类或对象的方法;
下面咱们列出方法引用的三种状况:
像咱们上面举的例子就是第三种:类的实例方法
下面咱们用代码演示上面的三种方法:
public class ReferenceDemo { public static void main(String[] args) { // 第一种:引用对象的实例方法 Cat cat = new Cat(1); Function<Cat, Integer> methodRef1 = cat::getSum; // 第二种:引用类的静态方法 Supplier<Integer> methodRef2 = Cat::getAverageAge; // 第三种:引用类的实例方法 Function<Cat, Integer> methodRef3 = Cat::getAge; } } class Cat { int age; public Cat(int age) { this.age = age; } // 获取猫的平均年龄 public static int getAverageAge(){ return 15; } // 获取两只猫的年龄总和 public int getSum(Cat cat){ return cat.getAge() + this.getAge(); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
为啥要用这个方法引用呢?
方法引用比如lambda表达式的语法糖,语法更加简洁,清晰
一看就知道是调用哪一个类或对象的哪一个方法
上面介绍了方法引用,就是直接引用某个方法
这里的构造引用同理可得,就是引用某个类的构造方法
构造引用的表达式为:Class::new
,仅此一种
若是你有多个构造函数,那编译器会本身进行推断参数(你看看,多好,多简洁)
好比下面的代码:
// 这里调用 new Cat() Supplier<Cat> constructRef1 = Cat::new; // 这里调用 new Cat(Integer) Function<Integer, Cat> constructRef2 = Cat::new;
要求引入lambda表达式中的变量,必须是最终变量,即该变量不会再被修改
好比下面的代码:
public static void main(String[] args) { String str = "javalover.cc"; Runnable runnable = ()->{ str = "1";// 这里会报错,由于修改了str引用的指向 System.out.println(str); } }
能够看到,lambda表达式引用了外面的str引用,可是又在表达式内部作了修改,结果就报错了
为啥要有这个限制呢?
为了线程安全,由于lambda表达式有一个好处就是只在须要的时候才会执行,而不是调用后立马执行
这样就会存在多个线程同时执行的并发问题
因此Java就从根源上解决:不让变量被修改,都是只读的
那你可能好奇,我不把str的修改代码放到表达式内部能够吗?
也不行,道理是同样的,只要lambda有用到这个变量,那这个变量不论是在哪里被修改,都是不容许的
否则的话,我这边先执行了一次lambda表达式,结果你就改了变量值,那我第二次执行lambda,不就乱了吗
最后是lambda的必杀技:组合操做
在这里叫组合或者复合均可以
概述:组合操做就是先用一个lambda表达式,而后再在后面组合另外一个lambda表达式,而后再在后面组合另另外一个lambda表达式,而后。。。有点像是链式操做
学过JS的都知道Promise,里面的链式操做就和这里的组合操做很像
用过Lombok的朋友,应该很熟悉@Builder注解,其实就是构造者模式
下面咱们用代码演示下组合操做:
// 重点代码 public class ComposeDemo { public static void main(String[] args) { List<Dog> list = Arrays.asList(new Dog(1,2), new Dog(1, 1)); // 1. 先按年龄排序(默认递增) // Dog::getAge, 上面介绍的方法引用 // comparingInt, 是Comparator的一个静态方法,返回Comparator<T> Comparator<Dog> comparableAge = Comparator.comparingInt(Dog::getAge); // 2. 若是有相同的年龄,则年龄相同的再按体重排序(若是年龄已经比较出大小,则下面的体重就不会再去比较) Comparator<Dog> comparableWeight = Comparator.comparingInt(Dog::getWeight);; // 3. 调用list对象的sort方法排序,参数是Comparator<? super Dog> list.sort(comparableAge.thenComparing(comparableWeight)); System.out.println(list); } } // 非重点代码 class Dog{ private int age; private int weight; public Dog(int age, int weight) { this.age = age; this.weight = weight; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } @Override public String toString() { return "Dog{" + "age=" + age + ", weight=" + weight + '}'; } }
输出:[Dog{age=1, weight=1}, Dog{age=1, weight=2}]
比较的流程以下所示:
(a,b)->{System.out.println("javalover.cc");}
方法引用:lambda的语法糖,总共有三种:
Class::new
好比list.sort(comparableAge.thenComparing(comparableWeight));
最后,感谢你们的观看,谢谢