Lambda01 编程范式、lambda表达式与匿名内部类、函数式接口、lambda表达式的写法

1 编程范式

  主要的编程范式有三种:命令式编程,声明式编程和函数式编程。

  1.1 命令式编程

    关注计算机执行的步骤,就是告诉计算机先做什么后做什么

  1.2 声明式编程

    表达程序的执行逻辑,就是告诉计算机要做什么,不指定具体怎么做

  1.3 函数式编程

    跟声明式编程类似,就是告诉计算机做什么,不指定具体怎么做

  参考博文

  1.4 demo实例

    1.4.1 需求

      随机产生一个int类型的数组,分别利用命令式编程和函数值编程获取数组中的最小值

    1.4.2 思路

      命令式编程:

        随记产生int类型数组 -> 定义一个int类型变量minNumber并取int的最大值 -> 利用循环遍历数组 -> 将数组中的每个元素和minNumber进行比较,如果比minNumber小就复制给minNumber,否则就进行下一次循环

      函数值编程:

        随记产生int类型数组 -> 定义一个int类型的变量minNumber并取int的最大值 -> 将数组转化成流 -> 调用的流的相关方法获取流中的最小值 -> 将获取到的最小值进行类型转化后在复制给minNumber

    1.4.3 代码实现

package a_test_demo;

import org.junit.Before;
import org.junit.Test; import java.util.Arrays;
import java.util.Random;
import java.util.stream.IntStream; /**
* @author 王杨帅
* @create 2018-06-24 17:14
* @desc
**/
public class Case02 { /**
* 需求:随机产生一个int类型的数组并通过命令式编程和函数式编程分别求出数组中的最小值
*/ private int[] nums; // 定义一个int类型的数组
int count = 10; // 数组长度
Random random; // 随机数对象 @Before
public void init() {
random = new Random(); // 实例化Random
nums = new int[count]; // 实例化数组
for (int i = 0; i < nums.length; i++) { // 修改数组内容
nums[i] = random.nextInt(30);
}
// System.out.println(Arrays.toString(nums)); // 打印数组信息【测试用】
} /**
* 利用命令式编程实现
*/
@Test
public void test01() {
int minNumber = Integer.MAX_VALUE; // 存储最小值【初始化时时最大值】 for (int i = 0; i < nums.length; i++) {
if (minNumber > nums[i]) {
minNumber = nums[i];
}
} System.out.println(Arrays.toString(nums) + " 中的最小值为: " + minNumber);
} /**
* 利用函数式编程实现
*/
@Test
public void test02() {
int minNumber = Integer.MAX_VALUE; minNumber = IntStream
.of(nums) // 将数组转化成流
.parallel() // 设置并行处理【PS: 如果是命令式编程需要自己实现相应逻辑】
.min() // 获取流中的最小值
.getAsInt(); // 将值转化成int类型 System.out.println(Arrays.toString(nums) + " 中的最小值为: " + minNumber);
} }

2 lambda表达式与匿名内部类

  2.1 需求

    现有一个名为getName的方法,该方法的参数是一个Student接口;怎么调用getName方法

  2.2 思路

    2.2.1 思路一

      创建一个名为StudentImpl的类,该类实现了Student接口 -> 调用getName方法时传入StudentImpl的实例即可

    2.2.2 思路二

      利用匿名内部类作为getName方法的实参

    2.2.3 思路三

      利用lambda表达式作为getName方法的实参

  2.3 代码实现

package a_test_demo;

import org.junit.Test;

public class Case01 {

    /**
* getName方法:用户打印学生的姓名
* @param student Student接口
*/
public void getName(Student student) {
System.out.println("学生的姓名为:" + student.name());
} /**
* 利用实现类实现
*/
@Test
public void test01() {
Student student = new StudentImpl();
getName(student);
} /**
* 利用匿名内部类实现
*/
@Test
public void test02() {
getName(new Student() {
@Override
public String name() {
return "fury";
}
});
} /**
* 利用lambda表达式实现
*/
@Test
public void test03() {
getName(() -> "zeus");
} } /**
* Student接口
*/
interface Student {
/**
* 抽象方法:返回String类型的姓名
* @return
*/
String name();
} /**
* Student接口的实现类
*/
class StudentImpl implements Student { @Override
public String name() {
return "warrior";
}
}

  2.4 三种方法使用场景

    2.4.1 实现类

      当某个类需要实例化多次时使用

    2.4.2 匿名内部类

      某个类只需要实例化一次而且不需要知道实例名称时使用【通常这个类需要进行重写的方法不止一个】;如果需要知道实例名称时就需要使用内部类

    2.4.3 lambda表达式

      某个类需要实例化一次而且不需要知道实例名字【这个类中通常有且只有一个需要重写的方法】

  2.5 为什么Lambda表达式可以作为方法的参数呢

    Lambda 可以作为方法的参数。Lambda 表达式是函数式接口实现类的实例,所以,Lambda 表达式作为方法的参数,实际就是把一个类实例作为方法的参数。Lambda 表达式表达或是设计了一组功能,把它传递给方法作为参数,实际上,可以理解为把一组功能传递给了方法。因此,为了让代码更加简洁,编程更加高效,调用方法(该方法的参数类型是接口类型)时,若接口有多个抽象方法,我们可以创建这个接口的匿名实现类的实例,作为方法的参数。若是接口只有唯一一个抽象方法,比如函数式接口,我们可以创建这个接口的 Lambda 表达式,作为调用方法的参数,把一组功能传递给方法。

    技巧01:lambda表达式 “->” 左边相当于函数式接口中那个方法的形参,“->” 的右边相当于函数式接口中那个方法的返回值

       Lambda01 编程范式、lambda表达式与匿名内部类、函数式接口、lambda表达式的写法

    技巧02:函数式接口就是只有一个抽象方法的接口

3 函数式接口

  就是只有一个抽象函数的接口

  3.1 默认实现的方法

    如果一个函数式接口中有一个方法有default修饰,那么这个方法就叫做默认实现的方法【即:在接口中已经实现了方法相应的逻辑(从JDK1.8新增的特性)】,那么这个默认实现的方法不会算作是函数式接口的抽象方法,因为函数式接口的定义是:接口中只有一个抽象方法。

@FunctionalInterface
interface IntFace {
int doubuleNum(int i);
// int test(); // 加了这个方法后,就会报错 /**
* 默认实现的方法【即:接口中的方法已经实现了业务逻辑】
* @param a
* @param b
* @return
*/
default int add(int a, int b) {
return a + b;
} }

  3.2 注解 @FunctionalInterface

    该注解的作用是标名接口是函数式接口,并对该接口进行检查,如果有超过一个抽象方法就会报错

    技巧01:如果某个接口中只有一个抽象方法,那么会被默认为是一个函数式接口,不用添加@FunctionalInterface注解也可以,@FunctionalInterface注解只是一个信息型注解而已

  3.3 自定义函数式接口

    3.3.1 需求

      自定义一个函数式接口MoneyFormat,该接口中有一个接收int类型并且返回String类型的抽象方法format;MyMoney类中有一个printMoney方法的参数类型是MoneyFormat;创建MyMoney后如何调用printMoney方法

/**
* 自定义函数式接口
*/
@FunctionalInterface
interface MoneyFormat {
String format(int money);
} /**
* 自定义类
*/
class MyMoney { private int money; /**
* 有参构造器
* @param money
*/
public MyMoney(int money) {
this.money = money;
} /**
*
* @param moneyFormat 自定义函数式接口类型
*/
public void printMoney(MoneyFormat moneyFormat) {
System.out.println("我的存款为:" + moneyFormat.format(this.money));
}
}

    3.3.2 思路

      实现类实现:太low,很麻烦,需要创建一个MoneyFormat的实现类

      匿名内部类实现:还行,但是不美观

      lambda:简单、粗暴、有成效

package a_test_demo;

import org.junit.Test;

import java.text.DecimalFormat;

/**
* @author 王杨帅
* @create 2018-06-24 22:04
* @desc
**/
public class Case03 { /**
* 实现类实现
*/
@Test
public void test01() {
MyMoney myMoney = new MyMoney(99912333);
MoneyFormatImpl moneyFormat = new MoneyFormatImpl();
myMoney.printMoney(moneyFormat);
} /**
* 利用匿名内部内实现
*/
@Test
public void test02() {
MyMoney myMoney = new MyMoney(9999999);
myMoney.printMoney(new MoneyFormat() {
@Override
public String format(int money) {
return new DecimalFormat("#,###").format(money);
}
});
} /**
* lambda表达式实现
*/
@Test
public void test03() {
MyMoney myMoney = new MyMoney(999888888);
myMoney.printMoney(money -> new DecimalFormat("#,###").format(money));
} } /**
* 自定义函数式接口
*/
@FunctionalInterface
interface MoneyFormat {
String format(int money);
} /**
* 自定义类
*/
class MyMoney { private int money; /**
* 有参构造器
* @param money
*/
public MyMoney(int money) {
this.money = money;
} /**
*
* @param moneyFormat 自定义函数式接口类型
*/
public void printMoney(MoneyFormat moneyFormat) {
System.out.println("我的存款为:" + moneyFormat.format(this.money));
}
} class MoneyFormatImpl implements MoneyFormat { @Override
public String format(int money) {
return new DecimalFormat("#,##").format(money);
}
}

    3.3.3 改进

      JDK1.8开始提供了多个可供选择的函数是接口,所以我们可以不用自己定义啦,选择一个合适的拿过来用就可以啦

package a_test_demo;

import org.junit.Test;

import java.text.DecimalFormat;
import java.util.function.Function; /**
* @author 王杨帅
* @create 2018-06-24 22:04
* @desc
**/
public class Case03 { /**
* lambda表达式实现
*/
@Test
public void test03() {
MyMoney myMoney = new MyMoney(999888888);
myMoney.printMoney(money -> new DecimalFormat("#,###").format(money));
} @Test
public void test04() {
MyMoney myMoney = new MyMoney(8988888);
Function<Integer, String> function = money -> new DecimalFormat("#,##").format(money); // 函数是接口的链式操作
myMoney.printMoney(function.andThen(s -> {
return "人民币" + s;
})); } } /**
* 自定义类
*/
class MyMoney { private int money; /**
* 有参构造器
* @param money
*/
public MyMoney(int money) {
this.money = money;
} /**
*
* @param Function jdk1.8提供的函数接口
*/
public void printMoney(Function<Integer, String> function) {
System.out.println("我的存款为:" + function.apply(this.money));
}
}

4 lambda表达式的写法

  4.1 概念

    lambda表达式其实是实现了某个函数式接口的类的一个实例;

    lambda表达式的箭头左边相当于函数式接口中未实现方法的入参,箭头右边相当于函数式接口中未实现方法的函数体;

    lambda表达式就相当于函数式接口中那个未实现的方法的实现而已。

    技巧01:编写Lambda 表达式,实际是编写函数式接口唯一的抽象方法的实现。因此,它是具备某种行为,或者说是具备某种功能的代码单元,这样的功能代码,可以传递给方法的参数。

    坑01:lambda 表达式是函数式接口的实现类实例,所以,定义 lambda 表达式,实际上要经历两件事情。第一件事情是定义函数式接口实现类,第二件事情是创建该实现类实例。 this 称之为当前对象,但是, 定义 lambda 表达式时,也就是定义函数式接口实现类时, lambda 表达式代表的实现类本身没有 this 对象,此时若是使用 this 对象,指的是把 lambda 表达式围住的类的当前对象,而不是 lambda 表达式代表的实现类的当前对象。(源自:gitchat)

    坑02:在lambda表达式使用了this关键字后,lambda表达式经过编译过后的方法就是一个静态方法;没有使用this关键字时编译过后就是一个普通的成员方法。

package demo05_webflux.chapter02;

/**
* @author 王杨帅
* @create 2018-07-29 9:28
* @desc Lambda表达式的4中写法
**/
public class Case04_LambdaDemo { public static void main(String[] args) {
FuryInterface f1 = i -> i * 3;
System.out.println(f1.result(20));
System.out.println(f1.add(3, 44)); FuryInterface f2 = (i) -> i * 3; FuryInterface f3 = (Integer i) -> i * 3; FuryInterface f4 = i -> {
System.out.println("hello boy");
return i * 3;
};
} } @FunctionalInterface
interface FuryInterface {
Integer result(Integer number); /**
* Java8开始可以利用default关键字在接口中定义已经实现了的方法
* @param a
* @param b
* @return
*/
default Integer add (Integer a, Integer b) {
return a + b;
}
} /**
* notes:
* 1 函数式接口
* 1.1 只有一个为实现方法的接口称为函数式接口
* 1.2 函数式接口也可以被实现,而且函数接口中的默认方法和为实现的方法都可以被实现
* 1.3 函数式接口中的默认方法可以不在实现类中实现,但是函数式接口中的未实现方法必
* 须在实现类中进行实现
* 1.4 如果有两个函数式接口的默认方法一样,有一个接口同时继承了这两个接口,那么必须在
* 接口中指明继承的式哪一个父接口中的默认方法
* 2 lambda表达式:
* 2.1 lambda表达式其实是实现了某个函数式接口的类的一个实例
* 2.2 lambda表达式的箭头左边相当于函数式接口中未实现方法的入参,
* 箭头右边相当于函数式接口中未实现方法的函数体
*/ class test implements FuryInterface { @Override
public Integer result(Integer number) {
return null;
} @Override
public Integer add(Integer a, Integer b) {
return null;
}
}

  

      

上一篇:CPU内存管理和linux内存分页机制


下一篇:写出优质Java代码的4个技巧