函数式编程思想

  1. 1. 再数学中,函数就是输入量、输出量的一套计算方案,也即是"拿数据做操作"
  2. 1. Java是面向对象编程,就是所有的编程都是在有该类(实现类,子类)的对象的情况下,用通过对象的形式来做
  3. 1. 函数式思想则尽量忽略面向对象的复杂语法:"强迪做什么,而不是以什么形式去做"

— Lam就是函数式思想的体现

Lambda表达式

需求:启动一个线程,在控制台输出一句话: 多线程程序启动了
方式1:

  1. 1. 创建一个类实现Runnable接口,重写run()方法
  2. 1. 创建测试类,使用Thread类创建多条线程,并管理实现Runnable接口的实例对象
  3. 1. 启动线程

// 使用Java实现类,就是通过实例对象
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,”飞机”);
Thread t2 = new Thread(mr,”高铁”);
t1.start();
t2.start();
方式2:匿名内部类方式改进
// 使用匿名内部类的方式改进,直接实现该接口并重写所需要的方法
new Thread(new Runnable(){
_@Override
_public void run() {
System.out.println(“启动线程”);
}
})
.start();
方式3: 使用Lambda方式改进
// 使用Lambda表达式的方式改进
new Thread(()-> System.out.println(“Lambda:线程启动”)).start();

Lamdba表达式的格式

Lambda的三要素:

  1. - 形式参数 ()
  2. - 箭头 ->
  3. - 代码块 {}

Lambda的标准格式:

  1. - 格式: (形式参数)->{执行代码块}
  2. - 形式参数: 如果有多个参数,参数之间用逗号隔开。如果没有参数,留空就行
  3. - -> : Lambda固定写法,代表指向动作
  4. - 代码块: 是我们具体要做的事情, 也就是以前我们写的方法体内容

匿名内部类实现Runnabled接口的实现run()方法
new Thread(new Runnable(){
_@Override
_public void run() { System.out.println(“启动线程”); }}).start();

  1. 1. 匿名内部类重写方法中形式参数run(空),说明不需要传递对象
  2. 1. 方法返回值类型为void,说明方法执行没有结果返回
  3. 1. 方法体中的内容,是我们具体要做的事情

Lambada表达式的代码分析
new Thread(()-> System.out.println(“Lambda:线程启动”)).start();

  1. 1. (): 里面没有内容,可以看成是方法形式参数为空
  2. 1. ->: 用箭头指向后面要做的事情
  3. 1. {}: 包含一段代码,我们称之为代码块,就是方法体中的内容

Lambda表达式的使用

Lambda表达式的使用前提:

1.该类是一个接口

2.这个接口有且仅有一个抽象方法

Lambda使用案例1

  1. 1. 定义一个接口(Eatable), 里面只有一个抽象方法: void eat();
  2. 1. 定义一个测试类(EatableDemo), 在测试类中提供两个方法
  3. 1. 一个方法是: useEatable(Eatable e)
  4. 1. 一个方法是主方法, 在主方法中调用useEatable方法

// 定义一个接口,里面只有一个抽象方法
public interface Eatable { void eat();}
——————————————————————————————
// 定义接口实例方法
public class EatableImpl implements Eatable{
_@Override
_public void eat() { System.out.println(“实现类吃东西”); } }
——————————————————————————————
// 测试类使用三种方式调用接口方法
public class EatableDemo {
public static void main(String[] args) {
// 在主体方法中调用useEateble方法
Eatable e = new EatableImpl();
useEatable(e);
// 匿名内部类实现方法
useEatable(new Eatable() {
@Override
public void eat() {
System.out.println(“匿名内部类吃东西”);
}
});
// Lambda表达式使用
useEatable(() -> System.out.println(“Lambda吃东西”)); }
public static void useEatable(Eatable e) {
// 参数是Eatable 这个接口的实例对象,可以直接调用接口实例对象的方法
e.eat(); } }

Lambda使用案例2

带参方法(String s):

  1. 1. 定义一个接口(Flyable), 里面只有一个抽象方法: void fly(String s);
  2. 1. 定义一个测试类(FlyableDemo), 在测试类中提供两个方法
  3. 1. 一个方法是: useFlyable(Flyable f)
  4. 1. 一个方法是主方法, 在主方法中调用useFlyable方法

// 定义一个接口,里面只有一个抽象方法
public interface Flyable { void fly(String s);}
// 定义接口实例方法并接收参数
public class FlyImpl implements Flyable{
@Override
_public void fly(String _s
) { System.out.println(s); } }
// 测试类使用三种方式调用接口方法
public class FlyableDemo {
public static void main(String[] args) {
// 匿名内部类实现对象
useFlyable(new Flyable() {
@Override
_public void fly(String _s
) {
System.out.println(s);
}
})
;
// 实现类对象实现
Flyable flyable = new FlyImpl();
useFlyable(flyable);
// Lambda表达式
useFlyable((String s)-> {
System.out.println(s);
System.out.println(“飞机”); }); }
private static void useFlyable(Flyable f) { f.fly(“hello,world”); **} }

Lambda使用案例3

带两个参数并确定数据类型 int add(int x,int y):

  1. 1. 定义一个接口(Addable), 里面只有一个抽象方法:int add(String s);
  2. 1. 定义一个测试类(AddableDemo), 在测试类中提供两个方法
  3. 1. 一个方法是: useAddable(Addable a)
  4. 1. 一个方法是主方法, 在主方法中调用useAddable方法

// 定义一个接口,里面只有一个抽象方法,该方法定义返回值类型
public interface Addable { int add(int x, int y);}
// 测试类使用Lambda方式调用接口方法
public class AddableDemo {
public static void main(String[] args) {
// Lambda表达式,形式参数()中添加两个参数
// 因为定义的抽象方法规定了返回值数据类型,所以需要回调函数return
useAddable((int a, int b) -> {
// 实现该方法是负责执行的代码语句,与方法名无关
return a + b; // 30
return a - b; // -10}); }
private static void useAddable(Addable a) {
// 将接口作为参数传递过来,实例该接口对象,调用方法
int sum = a.add(10, 20);
System.out.println(sum); } }

Lambda表达式的省略模式

  • 省略规则:
    • Lambda语句中的参数类型可以省略 ,但是在多个参数下, 也必须全部省略.不能单独省略某一个
      • (String s)->{} 简化: (s) ->{sout(“xxxx”);} ,
      • (int x+ int y)-> {return x + y} 简化 (x,y) ->{ return x+ y;}
    • 并且如果接口方法中的参数只有一个的时候可以省略小括号()
      • (s) ->{sout(“xxxx”);} 简化 s -> {sout(“xxxx”);}
    • 如果代码块中又且仅有一条执行语句的时候,可以省略大括号{}以及分号;
      • s -> {sout(“xxxx”);} 简化 s -> sout(“xxxx”)
    • 但是如果该接口方法是回调函数,同时只有一条执行语句,也需要将return省略
      • (x,y) ->{ return x+ y;} 简化 (x,y)-> x+y

// 定义一个接口,里面只有一个抽象方法,该方法定义返回值类型
public interface Addable { int add(int x, int y);}
// 定义一个接口,里面只有一个抽象方法
public interface Flyable { void fly(String s);}
// 定义一个测试类,使用Lambda简化方法实现
public class LambdaDemo {
public static void main(String[] args) {
// Lambda 标准表达式
useAddable((int x,int y)->{ return x + y; });
useFlyable((String s)->{ System.out.println(s); });
// Lambda 省略模式,参数的类型可以省略
// Tips: 若果使用省略模式省略参数类型,不论参数是几个必须全部省略
// 错误示范: useAddable(((int x, y) -> {return x+y;})); X
useAddable(((x, y) -> {return x+y;}));
// 如果代码块的语句只有一条,可以省略大括号和分号,
// 如果有回调函数return,return 也要进行省略
useAddable(((x, y) -> x +y));
————————————————————————-
// 如果参数有且仅有一个,(s)的小括号也可以省略为s
useFlyable(s -> { System.out.println(s); });
// 如果代码块的语句只有一条,可以省略大括号和分号
useFlyable(s -> System.out.println(s)); }
private static void useAddable(Addable a) {
// 将接口作为参数传递过来,实例该接口对象,调用方法
int sum = a.add(10, 20);
System.out.println(sum); }
private static void useFlyable(Flyable f){
f.fly(“起飞飞飞飞~”); } }

Lambda表达式的注意事项

注意事项:

  1. - 使用Lambda必须要有的接口, 并且要求接口中有且仅有一个抽象方法
  2. - 必须要有上下文环境,才能推导出Lambda对应的接口
  3. - 根据**局部变量的赋值**得知Lambda对应的接口: Runnable r = ()->sout("实例Runnable接口对象Lambda的表达式");
  4. - 根据**调用方法的参数**得知Lambda对应的接口: new Thread(()-> sout("根据Thread类Lambda表达式")).start();

public class LambdaDemo {
public static void main(String[] args) {
/ lambda表达式注意事项:
1. 实现的接口类中又且仅有一个方法
2. 必须有上下文环境,才能推导出lambda表达式
/

  1. // lambda简化: 无参数直接使用小括号, 执行语句只有一条省略大括号分号<br /> // 实现的接口类中又且仅有一个方法,否则报错<br /> **useInter(()**-> System.out.println**(**"展示!"**))**;<br /> // 必须有上下文环境,才能推导出lambda表达式<br /> // 例如下列多线程Thread类 创建匿名内部类使用Runnable接口<br /> new Thread**(**new Runnable**() {<br /> **_@Override<br /> _public void run**() {<br /> **System.out.println**(**"多线程匿名内部类环境"**)**;<br /> **}<br /> })**.start**()**;<br /> // 根据多线程Thread类以及接口推导出lambda表达式<br /> // 使用lambda简化 没有参数类型则是(),只有一条语句省略{}以及分号;<br /> new Thread**(()**-> System.out.println**(**"多线程lambda表达式"**))**.start**()**;<br /> // 使用Runnable接口的对象实现lambda表达式<br /> Runnable r = **() **-> System.out.println**(**"Runnable的lambda表达式"**)**;<br /> new Thread**(**r**)**.start**()**; **}
  2. **private static void useInter**(**Inter _i_**){ **_i_.show**()**; **}}**

Lambda表达式和匿名内部类的区别

区别1: 所属类型不同

  1. - 匿名内部类: 可以是接口,也可以是抽象类,也可以是具体类
  2. - Lambda表达式只能是接口

区别2: 使用限制不同

  1. - 如果接口中又且仅有一个抽象方法, 可以使用Lambda表达式, 也可以使用匿名内部类
  2. - 如果接口中多于一个抽象方法, 只能使用匿名内部类. 而不能使用Lambda表达式

区别3: 实现原理不同

  1. - 匿名内部类: 编译后,会产生一个单独的.class字节码文件
  2. - Lambda表达式: 编译后,并没有产生单独的.class字节码文件. 对应的字节码会在运行时候动态生产

// 定义一个具体的实现类,里面有一个具体类的方法
public class Student { public void study(){ System.out.println(“小学生学Java”); };}
// 定义一个抽象类,里面有一个抽象方法
public abstract class Animal { public abstract void method();}
// 定义一个接口,里面有抽象方法
public interface Inter {
// 接口中定义一个抽象方法无参无返回值类型
void show();
// 但是接口定义两个抽象方法,并不能使用lambda表达式
void play(); }
// 定义一个测试类,分别使用匿名内部类和lambda表达式调用方法
public class LambdaDemo {
public static void main(String[] args) {
// 匿名内部类调用方法
useStudent(new Student(){
@Override
_public void study() {
super.study();
System.out.println(“具体类匿名内部类”); } });
useAnimal(new Animal() {
@Override
public void method() { System.out.println(“抽象类匿名内部类”); } });
useInter(new Inter() {
@Override
public void show() { System.out.println(“接口匿名内部类”); } });
//使用lambda表达式调用方法
useInter(()-> System.out.println(“接口lambda表达式”));
// 抽象类报错lambda转换的目标类型必须为接口,抽象类和具体类不行
useAnimal(()-> System.out.println(“抽象类lambda表达式”));
useStudent(()-> System.out.println(“具体类lambda表达式”));
// 接口中只有一个抽象方法的时候可以推演出lambda,超过一个方法就不能使用
// 但是可以使用匿名内部类实现一个以上的抽象方法实现
useInter(new Inter() {
@Override
public void show() {
System.out.println(“接口匿名内部类show()方法”); }
@Override
public void play() {
System.out.println(“接口匿名内部类play()方法”); } }); }
——————————————————————————————————————

// 定义三个方法,将各个类作为形参,并调用各个类的方法
private static void useStudent(Student _s
){s.study();}
private static void useAnimal(Animal a){a.method();}
private static void useInter(Inter i){i.show();i.play();**} }