Lambda表达式(一)
  TEZNKK3IfmPf 2023年11月14日 39 0

 

  • Java 8 的 Lambda 表达式借鉴了​​C#​​​ 和​​Scala​​ 等语言中的类似特性
  • 简化了匿名函数的表达方式
  • Lambda 表达式可以直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例
  • 什么是函数式接口?
  • 简单的来说就是​​只包含一个抽象方法​​​ 的接口,允许有默认的实现(使用​​default​​ 关键字该描述方法)
  • 函数式接口建议使用​​@FunctionalInterface​​ 注解标注,虽然这不是必须的,但是这样做更符合规范
  • 在 Java 8之前,实现 Runnable 常用方式是编写一个匿名类:
 /**
* @author BNTang
* @program jdk8
**/
public class Demo {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
thread.start();
}
}

 

  • 使用 Lambda 表达式之后,上面的代码可以改造为下方的代码:

 

/**
* @author BNTang
* @program jdk8
**/
public class Demo {
public static void main(String[] args) {
Thread thread = new Thread(() -> System.out.println("hello"));
thread.start();
}
}
  • 是不是很神奇?很简洁?
  • Lambda 表达式的基本语法如下:
(parameters) -> expression
or
(parameters) -> { statements; }
  • 从语法中可以看到,Lambda 表达式主要包含了​​三个部分​​:
  • 参数列表:parameters,只有一个参数的时候可以省略括号
  • 箭头​​->​​ 把参数列表与 Lambda 主体内容分隔开来
  • Lambda 主体(expression),只有一行代码的时候可以省略大括号({})和​​return​​ 关键字
  • 比如下面这些 Lambda 表达式都是合法的:
(String str) -> str.length()
(String str) -> { return str.length(); }

() -> System.out.println("hello")

() -> {}
() -> 17

(int x, int y) -> {
System.out.println(x);
System.out.println(y);
}

Lambda的使用场合

  • 什么时候可以使用 Lambda 表达式?使用 Lambda 必须满足以下两个条件:
  1. 实现的对象是​​函数式接口​​ 的抽象方法
  2. 函数式接口的抽象方法的函数描述符和 Lambda 表达式的函数描述符一致

函数式接口

  • 函数式接口的定义开头已经说了,这里就不再赘述
  • 在 Java 8之前,常见的函数式接口有​​java.util.Comparator​​​,​​java.lang.Runnable​​ 等
  • 拿​​java.util.Runnable​​ 来说,查看其源码如下:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
  • 这个接口只有一个抽象方法,并且使用​​@FunctionalInterface​​ 注解标注
  • 接口现在还可以拥有默认方法(在类没有对方法进行实现时,其主体为方法提供默认实现的方法)
  • 哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口

 

函数描述符

  • 函数描述符其实也可以理解为方法的签名
  • 比如上述的 Runnable 的抽象方法不接受参数,并且返回 void,所以其函数描述符为​​() -> void​
  • 而​​() -> System.out.println("hello")​​​ Lambda 表达式也是不接受参数,并且返回 void,其函数描述符也是​​() -> void​
  • 所以代码​​Runnable r = () -> System.out.println("hello");​​ 是合法的
  • 特殊的 void 兼容规则
  • 如果一个 Lambda 的主体是一个语句表达式,它就和一个返回 void 的函数描述符兼容(当然需要参数列表也兼容)
  • 例如,以下 Lambda 是合法的,尽管 List 的 add 方法返回了一个 boolean,而不是 Runnable 抽象方法的函数描述符​​() -> void​​ 所要求的 void:
/**
* @author BNTang
* @program jdk8
**/
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Runnable r = () -> list.add("hello");
}
}

更简洁的Lambda

  • 编写一个类型转换的函数式接口:
@FunctionalInterface
public interface TransForm<T, R> {
R transForm(T t);
}
  • 编写一个 Lambda 表达式实现该函数式接口,用于实现 String 转换为 Integer,代码如下:
/**
* @author BNTang
* @program jdk8
**/
public class Demo {
public static void main(String[] args) {
TransForm<String, Integer> t = (String str) -> Integer.valueOf(str);
System.out.println(t.transForm("123"));
}
}
  • 上面的 Lambda 表达式可以进一步简化为如下方式:
TransForm<String, Integer> t = (str) -> Integer.valueOf(str);
System.out.println(t.transForm("123"));
  • 因为 Java 编译器会从上下文(目标类型)推断出用什么函数式接口来配合 Lambda 表达式,这意味着它也可以推断出适合 Lambda 的签名
  • 就拿这个例子来说,TransForm 的抽象方法​​transForm​​​ 在本例子中的函数描述符为​​(String) -> Integer​​,所以对应的 Lambda 的签名也是如此,即 Lambda 的参数即使不声名类型,Java 编译器可以知道其参数实际上为 String 类型
  • 其实,上面的 Labmda 表达式还不是最简洁的,其还可以更进一步地简化为如下写法:

 

TransForm<String, Integer> t = Integer::valueOf;
System.out.println(t.transForm("123"));
  • 你肯定很困惑,这还是 Lambda 表达式吗,箭头去哪里了?​​双冒号​​ 又是什么鬼?其实这种写法有一个新的名称,叫做方法的引用
  • ​方法的引用​​ 可以被看作仅仅调用特定方法的 Lambda 的一种快捷写法
  • 它的基本思想是,如果一个 Lambda 代表的只是 “直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它,这样代码可读性更好。基本写法就是​​目标引用​​​ 放在分隔符​​::​​ 前,方法的名称放在后面
  • 举几个 Lambda 及其等效方法引用的例子:

Lambda表达式

等效方法引用

(String s) -> System.out.println(s)

System.out::println

(str, i) -> str.substring(i)

String::substring

() -> Thread.currentThread().dumpStack()

Thread.currentThread()::dumpStack

 

  • 符号​​::​​ 除了出现在方法的引用外,它还常见于构造函数的引用
  • 为了演示什么是 构造函数 的引用,我们创建一个新的函数式接口:
@FunctionalInterface
public interface Generator<T, R> {
R create(T t);
}
  • 创建一个 Apple 类:
public class Apple {
public Apple(String color) {
this.color = color;
}

private String color;

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}
}
  • 现在我们可以使用如下方式来创造一个 Apple 实例:
/**
* @author BNTang
* @program jdk8
**/
public class Demo {
public static void main(String[] args) {
Generator<String, Apple> g = Apple::new;
Apple apple = g.create("red");
}
}
  • 这种通过​​ClassName::new​​​ 的写法就是​​构造函数​​ 的引用
  • 在这里 Generator 的抽象方法接收一个 String 类型参数,返回值类型为​​Apple​​,这和 Apple 类的构造函数相符合,所以这里编译可以通过
  • 它等价于下面的写法:
Generator<String, Apple> g = (color) -> new Apple(color);
Apple apple = g.create("red");

Lambda表达式访问变量

  • Lambda 表达式可以访问局部​​final​​ 变量,成员变量和静态变量
  • 这里主要说下局部​​final​​ 变量
  • 有没有​​final​​ 关键字不重要,重要的是确保该变量的值不会被改变就行了
  • 比如下面的例子可以编译通过:
/**
* @author BNTang
* @program jdk8
**/
public class Demo {
public static void main(String[] args) {
String hello = "hello lambda";
Runnable r = () -> System.out.println(hello);
r.run();
}
}

 

  • 而下面的这个就会编译出错,因为变量​​hello​​ 的值被改变了:

 

Lambda表达式(一)

Lambda表达式实战

  • 假如现在有如下需求:现有一个包含了各种颜色,不同重量的苹果的 List,编写一个方法,从中筛选出满足要求的苹果
  • 比如筛选出红色的苹果、红色并且重量大于​​1kg​​​ 的苹果、绿色重量小于​​0.5kg​​​ 的苹果或者红色大于​​0.5kg​​ 的苹果等等

不使用Lambda

  • 在没有接触 Lambda 之前,我们一般会这样做:
  • 定义一个筛选的接口
/**
* @author BNTang
*/
public interface AppleFilter {
boolean test(Apple apple);
}
  • 然后根据筛选的条件来编写各种不同的实现类:
  • 筛选出​​红色苹果​​ 的实现方法:
public class RedApple implements AppleFilter {
@Override
public boolean test(Apple apple) {
return "red".equalsIgnoreCase(apple.getColor());
}
}

 

  • 筛选出​​红色​​​ 并且​​重量大于1kg​​ 的苹果的实现方法:
  • 首先添加一个属性修改​​Apple​​ 类

 

private Double weight;

public Double getWeight() {
return weight;
}

public void setWeight(Double weight) {
this.weight = weight;
}
public class RedAndMoreThan1kgApple implements AppleFilter {
@Override
public boolean test(Apple apple) {
return "red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 1.0;
}
}
  • 筛选出​​绿色​​​​重量小于0.5kg​​​ 的苹果 或者​​红色​​​​大于0.5kg​​ 的苹果的实现方法:
public class GreenAndLessThan05OrRedAndMoreThan05Apple implements AppleFilter {
@Override
public boolean test(Apple apple) {
return ("green".equalsIgnoreCase(apple.getColor()) && apple.getWeight() < 0.5) || ("red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 0.5);
}
}

筛选苹果的方法:

public class AppleFilterMethod {
public static List<Apple> filterApple(List<Apple> list, AppleFilter filter) {
List<Apple> filterList = new ArrayList<>();
for (Apple apple : list) {
if (filter.test(apple)) {
filterList.add(apple);
}
}
return filterList;
}
}
  • 开始筛选苹果:
  • 修改​​Apple​​ 类,添加两个参数的构造函数方法
public Apple(String color, Double weight) {
this.weight = weight;
this.color = color;
}
/**
* @author BNTang
* @program jdk8
**/
public class Demo {
public static void main(String[] args) {
List<Apple> appleList = new ArrayList<>();
appleList.add(new Apple("red", 0.4));
appleList.add(new Apple("red", 0.6));
appleList.add(new Apple("red", 1.3));
appleList.add(new Apple("green", 0.2));
appleList.add(new Apple("green", 0.35));
appleList.add(new Apple("green", 1.1));

List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList, new RedApple());
for (Apple apple : appleFilterList) {
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());
}
}
}
  • 输出结果如下:
  • 剩下的略
  • 可以看到,我们为了满足各种筛选条件创造了各种筛选接口的实现类,真正起作用的只有筛选方法中 return 那一行代码,剩下的都是一些重复的模板代码
  • 使用 Java 8 中的 Lambda 可以很好的消除这些模板代码
red apple,weight:0.4
red apple,weight:0.6
red apple,weight:1.3

使用Lambda

  • AppleFilter 接口实际上就是一个函数式接口,所以它的各种实现可以用 Lambda 表达式来替代,而无需真正的去写实现方法
  • 定义筛选接口:
/**
* @author BNTang
*/
public interface AppleFilter {
boolean test(Apple apple);
}
  • 筛选苹果的方法:
public class AppleFilterMethod {
public static List<Apple> filterApple(List<Apple> list, AppleFilter filter) {
List<Apple> filterList = new ArrayList<>();
for (Apple apple : list) {
if (filter.test(apple)) {
filterList.add(apple);
}
}
return filterList;
}
}
  • 接下来便可以开始筛选了
  • 筛选红色的苹果:
/**
* @author BNTang
* @program jdk8
**/
public class Demo {
public static void main(String[] args) {
List<Apple> appleList = new ArrayList<>();
appleList.add(new Apple("red", 0.4));
appleList.add(new Apple("red", 0.6));
appleList.add(new Apple("red", 1.3));
appleList.add(new Apple("green", 0.2));
appleList.add(new Apple("green", 0.35));
appleList.add(new Apple("green", 1.1));

List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList, (apple) -> "red".equalsIgnoreCase(apple.getColor()));
for (Apple apple : appleFilterList) {
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());
}
}
}
  • 输出结果如下:
red apple,weight:0.4
red apple,weight:0.6
red apple,weight:1.3
  • 筛选出红色并且重量大于1kg的苹果:
List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList, (apple) -> "red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 1.0);
for (Apple apple : appleFilterList) {
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());
}
  • 输出结果如下:
red apple,weight:1.3
  • 筛选出绿色重量小于0.5kg的苹果或者红色大于0.5kg的苹果:
List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList,(apple) -> ("green".equalsIgnoreCase(apple.getColor()) && apple.getWeight() < 0.5) || ("red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 0.5));
for (Apple apple : appleFilterList) {
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());

  • 输出结果如下:
red apple,weight:0.6
red apple,weight:1.3
green apple,weight:0.2
green apple,weight:0.35
  • 使用 Lambda 表达式消除了大量的样板代码,并且可以灵活的构造筛选条件!
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

上一篇: EasyUI简介 下一篇: SpringData-ESCRUD
  1. 分享:
最后一次编辑于 2023年11月14日 0

暂无评论

推荐阅读
  TEZNKK3IfmPf   21天前   47   0   0 java
  TEZNKK3IfmPf   2024年05月31日   54   0   0 java
TEZNKK3IfmPf