文章目录
1.传统代码写法(线程举例)
1.使用实现类创建线程
-
要启动一个线程,需要创建一个
Thread
类的对象并调用start
方法。而为了指定线程执行的内容,需要调用Thread
类的构造方法:public Thread(Runnable target)
-
为了获取
Runnable
接口的实现对象,可以为该接口定义一个实现类RunnableImpl
:package Lambda; /** * @author JIN * @description * @createTime 2020-08-25 08:55 **/ public class RunnableImpl implements Runnable { @Override public void run() { System.out.println("启动线程了....."); } }
-
然后创建该实现类的对象作为
Thread
类的构造参数:package Lambda; /** * @author JIN * @description 使用实现类 创建线程 * @createTime 2020-08-25 08:57 **/ public class Demo1 { public static void main(String[] args) { Runnable task = new RunnableImpl(); new Thread(task).start(); } }
2.使用实现类创建线程的问题
- 刚刚我们创建的RunableImpl类只是为了实现Runnable接口而存在的,而且仅仅是被使用唯一的一次,且若以后你创建的线程数量很多,且需要执行的方法不同,那么就要创建相同数量的实现类,造成整体代码的冗余。
- 因此,就诞生了使用匿名内部类来创建线程。
3.使用匿名内部类创建线程
-
通过刚刚的分析,我们知道RunableImpl实现类的只是为了实现Runnable接口而存在的,该方法则要重新创建一个类,实在太麻烦了,因此使用匿名内部类还可以省去类的单独定义。
-
代码如下:
package Lambda; /** * @author JIN * @description 使用匿名内部类创建线程 * @createTime 2020-08-25 09:05 **/ public class Demo2 { public static void main(String[] args) throws InterruptedException { //第一种匿名内部类的使用 Runnable task = new Runnable() { @Override public void run() { System.out.println("线程启动了1...."); } }; new Thread(task).start(); Thread.sleep(1000); System.out.println("-----------------"); //我们还发现第一种方法中还可以简化,因此引出下面的方法 //更加简化 new Thread(new Runnable() { @Override public void run() { System.out.println("线程启动了2......"); } }).start(); } }
4.使用匿名内部类创建线程的好处与弊端
1.好处
- 匿名内部类当然可以帮我们省去实现类的定义。使我们实现代码起来更加简单快捷。
2.弊端
-
语法的复杂性。使用匿名内部类,必须覆盖重写抽象方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错。(但是实际上开发工具都有自动帮你写好的功能,其实这个弊端也不算什么).
-
还是不够简化,始终还是有冗余,代码如下:
package Lambda; /** * @author JIN * @description 匿名内部类的冗余 * @createTime 2020-08-25 09:18 **/ public class Demo3 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("A........."); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("B........."); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("C........."); } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("D........."); } }).start(); } }
-
细心的人应该发现了,代码中存在大量重复的,比如下面:
new Thread(new Runnable() { //new Runnable 重复 @Override //重复 public void run() { //重复 } }).start();
2.分析两种传统方法的实现
- 我们通过刚刚的上面的代码,可以分析出以下几点内容:
Thread
类需要Runnable
接口作为参数,其中的抽象run
方法是用来指定线程任务内容的核心。- 为了指定
run
的方法体,不得不需要Runnable
接口的实现类。 - 为了省去定义一个
RunnableImpl
实现类的麻烦,不得不使用匿名内部类。 - 必须覆盖重写抽象
run
方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错。 - 而实际上,似乎只有方法体才是关键所在,因为我们需要的使run方法内部中执行的内容。
3.使用Lamda表达式
1.函数式编程思想概述
- 面向对象的思想,过分强调“必须通过对象的形式来做事情”,即做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。(比如:电脑坏了,但是自己又不会修,那就去找专业人士(专业人士即是对象),然后专业人士使用他的专业技能(即调用对象的方法)修好了电脑)
- 函数式编程思想,只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。(比如你要去北京,可以走路,坐飞机,坐火车等等方式,但是你都无所谓,只要能到北京就好了)
2.编程思想转换
- 我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将
run
方法体内的代码传递给Thread
类知晓。 - 传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。
- 现在我们有更加快捷的方式-----Lambda表达式。
3. 使用Lambda创建线程
1.Lambda标准格式
- Lambda表达式的标准格式为 (参数类型 参数名称) -> { 代码语句 }
- 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
->
是新引入的语法格式,代表指向动作,将前面的参数传递给后面的代码。- 大括号内的语法与传统方法体要求基本一致。
2.初步使用Lambda
-
代码如下:
package Lambda; /** * @author JIN * @description 使用Lambda表达式 * @createTime 2020-08-25 09:44 **/ public class Demo4 { public static void main(String[] args) { new Thread(() -> { System.out.println("线程启动了..."); }).start(); } }
3.Lambda省略格式
-
Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以根据上下文推导得知的信息,都可以省略。
-
在Lambda标准格式的基础上,使用省略写法的规则为:
-
小括号内参数的类型可以省略;
-
如果小括号内有且仅有一个参,则小括号可以省略;
-
如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号且这三个必须同时省略。
-
代码如下:
package Lambda; /** * @author JIN * @description 使用Lambda表达式省略模式 * @createTime 2020-08-25 09:44 **/ public class Demo5 { public static void main(String[] args) { new Thread(() -> System.out.println("线程启动了...")).start(); } }
4.Lambda练习
-
给定一个计算器
Calculator
接口,内含抽象方法calc
可以将两个int数字相加得到和值: -
代码如下:
package Lambda; public interface Calculator { int add(int a,int b); }
package Lambda; /** * @author JIN * @description 使用Lambda标准格式(有参有返回) * @createTime 2020-08-25 09:53 **/ public class Demo6 { public static void main(String[] args) { //标准模式 invokeCalc(10,20,(int a, int b)->{ return a + b; }); //省略模式,可以看出更加省略 invokeCalc(100,200,(a, b) -> a + b); } private static void invokeCalc(int a, int b, Calculator calculator){ int result = calculator.add(a, b); System.out.println(result); } }
-
使用Lambda标准格式(只有一个参且有返回)
package Lambda; public interface Calculator { int add(int a); }
package Lambda; /** * @author JIN * @description 使用Lambda标准格式(有一个参有返回) * @createTime 2020-08-25 09:53 **/ public class Demo7 { public static void main(String[] args) { //只有一个参数则小括号可以省略 //标准模式 invokeCalc(10,a ->{ return a; }); //省略模式,可以看出更加省略 invokeCalc(100,a ->a); } private static void invokeCalc(int a,Calculator calculator){ int result = calculator.add(a); System.out.println(result); } }
5.总结
- Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。无论是JDK内置的
Runnable
、Comparator
接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。 - 使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
- 有且仅有一个抽象方法的接口,称为“函数式接口”。