2014年3月发布的Java 8,有可能是Java版本更新中变化最大的一次。新的Java 8为开发者带来了许多重量级的新特性,包括Lambda表达式,流式数据处理,新的Optional类,新的日期和时间API等。这些新特性给Java开发者带来了福音,特别是Lambda表达式的支持,使程序设计更加简化。本篇文章将讨论行为参数化,Lambda表达式,函数式接口等特性。

行为参数化

在软件开发的过程中,开发人员可能会遇到频繁的需求变更,使他们不断地修改程序以应对这些变化的需求,导致项目进度缓慢甚至项目延期。行为参数化就是一种可以帮助你应对频繁需求变更的开发模式,简单的说,就是预先定义一个代码块而不去执行它,把它当做参数传递给另一个方法,这样,这个方法的行为就被这段代码块参数化了。

为了方便理解,我们通过一个例子来讲解行为参数化的使用。假设我们正在开发一个图书管理系统,需求是要对图书的作者进行过滤,筛选出指定作者的书籍。比较常见的做法就是编写一个方法,把作者当成方法的参数:

public List<Book> filterByAuthor(List<Book> books, String author) {
List<Book> result = new ArrayList<>();
for (Book book : books) {
if (author.equals(book.getAuthor())) {
result.add(book);
}
}
return result;
}

现在客户需要变更需求,添加过滤条件,按照出版社过滤,于是我们不得不再次编写一个方法:

public List<Book> filterByPublisher(List<Book> books, String publisher) {
List<Book> result = new ArrayList<>();
for (Book book : books) {
if (publisher.equals(book.getPublisher())) {
result.add(book);
}
}
return result;
}

两个方法除了名称之外,内部的实现逻辑几乎一模一样,唯一的区别就是if判断条件,前者判断的是作者,后者判断的是出版社。如果现在客户又要增加需求,需要按照图书的售价过滤,是不是需要再次将上面的方法复制一遍,将if判断条件改为售价? No! 这种做法违背了DRY(Don’t Repeat Yourself,不要重复自己)原则,而且不利于后期维护,如果需要改变方法内部遍历方式来提高性能,意味着每个filterByXxx()方法都需要修改,工作量太大。

一种可行的办法是对过滤的条件做更高层的抽象,过滤的条件无非就是图书的某些属性(比如价格、出版社、出版日期、作者等),可以声明一个接口用于对过滤条件建模:

public interface BookPredicate {
public boolean test(Book book);
}

BookPredicate接口只有一个抽象方法test(),该方法接受一个Book类型参数,返回一个boolean值,可以用它来表示图书的不同过滤条件。

接下来我们对之前的过滤方法进行重构,将filterByXxx()方法的第二个参数换成上面定义的接口:

public List<Book> filter(List<Book> books, BookPredicate bookPredicate) {
List<Book> result = new ArrayList<>();
for (Book book : books) {
if (bookPredicate.test(book)) {
result.add(book);
}
}
return result;
}

将过滤的条件换成BookPredicate的实现类,这里采用了内部类:

// 根据作者过滤
final String author = "张三";
List<Book> result = filter(books, new BookPredicate() {
@Override
public boolean test(Book book) {
return author.equals(book.getAuthor());
}
}); // 根据图书价格过滤
final double price = 100.00D;
List<Book> result = filter(books, new BookPredicate() {
@Override
public boolean test(Book book) {
return price > book.getPrice();
}
});

重构前后有什么区别?我们将方法中的if判断条件换成了BookPredicate接口定义的test()方法,用于判断是否满足过滤条件,将图书过滤的逻辑交给了BookPredicate接口的实现类,而不是在filter()方法内部实现过滤,而BookPredicate接口又是filter()方法的参数。以上的步骤,就是将行为参数化,也就是将图书过滤的行为(BookPredicate接口的实现类)当做filter()方法的参数。现在,可以删掉所有filterByXxx()的方法,只保留filter()方法,就算后期数据规模很庞大,需要改变集合的遍历方式来提高性能,只需要在filter()方法内部做出相应的修改,而不用去修改其他业务代码。

不过,BookPredicate接口只是针对图书的过滤,如果需要对其他对象集合排序(如:用户),又得重新申明一个接口。有一个办法就是可以用Java的泛型对它做进一步的抽象:

public interface Predicate<T> {
public boolean test(T t);
}

现在你可以把filter()方法用在任何对象的过滤中。

Lambda表达式

虽然我们对filter()方法进行重构,并抽象了Predicate接口作为过滤的条件,但实际上还需要编写很多内部类来实现Predicate接口。使用内部类的方式实现Predicate接口有很多缺点:首先是代码显得臃肿不堪,可读性差;其次,如果某个局部变量被内部类使用,这个变量必须使用final关键字修饰。在Java 8中,使用Lambda表达式可以对内部类进一步简化:

// 根据作者过滤
List<Book> result = filter(books, book -> "张三".equals(book.getAuthor())); // 根据图书价格过滤
List<Book> result = filter(books, book -> 100 > book.getPrice());

使用Lambda仅仅用一行代码就对内部类进行了转化,而且代码变得更加清晰可读。其中book -> "张三".equals(book.getAuthor())和book -> 100 > book.getPrice()就是我们接下来要研究的Lambda表达式。

Lambda表达式是什么

Lambda表达式(lambda expression)是一个匿名函数,由数学中的λ演算而得名。在Java 8中可以把Lambda表达式理解为匿名函数,它没有名称,但是有参数列表、函数主体、返回类型等。

Lambda表达式的语法如下:

(parameters) -> { statements; }

为什么要使用Lambda表达式?前面你也看到了,在Java中使用内部类显得十分冗长,要编写很多样板代码,Lambda表达式正是为了简化这些步骤出现的,它使代码变得清晰易懂。

如何使用Lambda表达式

Lambda表达式是为了简化内部类的,你可以把它当成是内部类的一种简写方式,只要是有内部类的代码块,都可以转化成Lambda表达式:

// Comparator排序
List<Integer> list = Arrays.asList(3, 1, 4, 5, 2);
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
// 使用Lambda表达式简化
list.sort((o1, o2) -> o1.compareTo(o2));
// Runnable代码块
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello Man!");
}
}); // 使用Lambda表达式简化
Thread thread = new Thread(() -> System.out.println("Hello Man!"));

可以看出,只要是内部类的代码块,就可以使用Lambda表达式简化,并且简化后的代码清晰易懂。甚至,Comparator排序的Lambda表达式还可以进一步简化:

list.sort(Integer::compareTo);

这种写法被称为 方法引用,方法引用是Lambda表达式的简便写法。如果你的Lambda表达式只是调用这个方法,最好使用名称调用,而不是描述如何调用,这样可以提高代码的可读性。

方法引用使用::分隔符,分隔符的前半部分表示引用类型,后面半部分表示引用的方法名称。例如:Integer::compareTo表示引用类型为Integer,引用名称为compareTo的方法。

类似使用方法引用的例子还有打印集合中的元素到控制台中:

list.forEach(System.out::println);

函数式接口

如果你的好奇心使你翻看Runnable接口源代码,你会发现该接口被一个@FunctionalInterface的注解修饰,这是Java 8中添加的新注解,用于表示 函数式接口。

函数式接口又是什么鬼?在Java 8中,把那些仅有一个抽象方法的接口称为函数式接口。如果一个接口被@FunctionalInterface注解标注,表示这个接口被设计成函数式接口,只能有一个抽象方法,如果你添加多个抽象方法,编译时会提示“Multiple non-overriding abstract methods found in interface XXX”之类的错误。

函数式方法又能做什么?Java8允许你以Lambda表达式的方式为函数式接口提供实现,通俗的说,你可以将整个Lambda表达式作为接口的实现类。

除了Runnable之外,Java 8中内置了许多函数式接口供开发者使用,这些接口位于java.util.function包中,我们之前使用的Predicate接口,已经被包含在这个包内,他们分别为Predicate、Consumer和Function,由于我们已经在之前的图书过滤的例子中介绍了Predicate的用法,所以接下来主要介绍Consumer和Function的用法。

Consumer

java.util.function.Consumer定义了一个名叫accept()的抽象方法,它接受泛型T的对象,没有返回(void)。如果你需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。比如,你可以用它来创建一个forEach()方法,接受一个集合,并对集合中每个元素执行操作:

@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
} public static <T> void forEach(List<T> list, Consumer<T> consumer) {
for(T t: list){
consumer.accept(t);
}
} public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B", "C", "D");
forEach(list, str -> System.out.println(str));
// 也可以写成
forEach(list, System.out::println);
}

Function

java.util.function.Function<T, R>接口定义了一个叫作apply()的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口。比如,我们需要计算一个图书集合中每本书的作者名称有几个汉字(假设这些书的作者都是中国人):

@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
} public static <T, R> List<R> map(List<T> list, Function<T, R> f) {
List<R> result = new ArrayList<>();
for(T s: list){
result.add(f.apply(s));
}
return result;
} public static void main(String[] args) {
List<Book> books = Arrays.asList(
new Book("张三", 99.00D),
new Book("李四", 59.00D),
new Book("王老五", 59.00D)
);
List<Integer> results = map(books, book -> book.getAuthor().length());
}

现在,对Lambda表达式有一个初步的了解了,你可以尝试使用Lambda表达式来重构你的代码,提高代码可读性;使用行为参数化来设计你的程序,让程序更灵活。

Java 8新特性(一):Lambda表达式的更多相关文章

  1. Java 8新特性-3 Lambda 表达式

    在 Java 8 之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差. 在Java 8 之前使用匿名内部类: 例如 interface ITestPrint{ public voi ...

  2. Java 8 新特性之 Lambda表达式

    Lambda的出现就是为了增强Java面向过程编程的深度和灵活性.今天就来分享一下在Java中经常使用到的几个示例,通过对比分析,效果应该会更好. – 1.实现Runnable线程案例 其存在的意义就 ...

  3. java8新特性: lambda表达式:直接获得某个list/array/对象里面的字段集合

    java8新特性: lambda表达式:直接获得某个list/array/对象里面的字段集合 比如,我有一张表: entity Category.java service CategoryServic ...

  4. Java8 新特性学习 Lambda表达式 和 Stream 用法案例

    Java8 新特性学习 Lambda表达式 和 Stream 用法案例 学习参考文章: https://www.cnblogs.com/coprince/p/8692972.html 1.使用lamb ...

  5. JDK1.8新特性(一) ----Lambda表达式、Stream API、函数式接口、方法引用

    jdk1.8新特性知识点: Lambda表达式 Stream API 函数式接口 方法引用和构造器调用 接口中的默认方法和静态方法 新时间日期API default   Lambda表达式     L ...

  6. Java 8 新特性:Lambda、Stream和日期处理

    1. Lambda 简介   Lambda表达式(Lambda Expression)是匿名函数,Lambda表达式基于数学中的λ演算得名,对应于其中的Lambda抽象(Lambda Abstract ...

  7. Java 8新特性(Lambda,Stream API)

    由于最近总监要求学习Java 8的一些知识,就去网上找了 一套教程来学习学习,将学习结果做一个小的总结记录,方便以后使用: 1.Java 8的优点 2.Lambda表达式优点 2.1Lambda实例 ...

  8. 夯实Java基础(二十二)——Java8新特性之Lambda表达式

    1.前言 Java 8于14年发布到现在已经有5年时间了,经过时间的磨练,毫无疑问,Java 8是继Java 5(发布于2004年)之后的又一个非常最重要的版本.因为Java 8里面出现了非常多新的特 ...

  9. 【Java新特性】Lambda表达式典型案例,你想要的的都在这儿了!!

    写在前面 不得不说,有些小伙伴的学习热情真高,学完了Lambda表达式的语法,想来几个典型案例再强化下.于是问冰河能否给几个Lambda表达式的典型使用示例.于是乎,便有了这篇文章. 案例一 需求 调 ...

  10. Java 8新特性之lambda(八恶人-2)

    Major Marquis Warren 沃伦·马奎斯少校 “Tring to get a couple of bounties in to Red Rock.”我想带几个通缉犯去红石镇 一.基本介绍 ...

随机推荐

  1. SQL Server跟踪工具Profiler的使用

    一.什么是SQL Profiler SQL Server Profiler 是一个功能丰富的界面,用于创建和管理跟踪并分析和重播跟踪结果. 事件保存在一个跟踪文件中,稍后试图诊断问题时,可以对该文件进 ...

  2. SQL数据库优化总结

    1.在表中建立索引优先考虑 where.group by使用到的数据. 2.查询的sql语句中不要使用select * ,因为会返回许多无用的字段降低查询的效率,应该使用具体的字段代替*,只返回使用到 ...

  3. 判断js中数组是否包含某值

    可以用数组的includes函数判断数组中是否存在某个值.

  4. 爬取图虫网 示例网址 https://wangxu.tuchong.com/23892889/

    #coding=gbk import requests from fake_useragent import UserAgent from lxml import etree import urlli ...

  5. PHP fread() 函数

    定义和用法 fread() 函数读取打开的文件. 函数会在到达指定长度或读到文件末尾(EOF)时(以先到者为准),停止运行. 该函数返回读取的字符串,如果失败则返回 FALSE. 语法 string ...

  6. 类加载Class Loading

    JVM 何时.如何把 Class 文件加载到内存,形成可以直接使用的 Java 类型,并开始执行代码? ​ 类的生命周期 加载 - 连接(验证.准备.解析)- 初始化 - 使用 - 卸载. 注意,加载 ...

  7. 利用WxJava实现PC网站集成微信登录功能

    原文地址:https://mp.weixin.qq.com/s/rT0xL9uAdHdZck_F8nyncg 来源:微信公众号:java碎碎念 1. 微信开放平台操作步骤 微信开放平台地址:https ...

  8. 解决 IntelliJ IDEA占用C盘过大空间问题

    原文地址:https://blog.csdn.net/weixin_44449518/article/details/103334235 问题描述: 在保证其他软件缓存不影响C盘可用空间的基础上,当我 ...

  9. Go之Gorm和BeegoORM简介及配置使用

    简介 ORM Object-Relationl Mapping, 它的作用是映射数据库和对象之间的关系,方便我们在实现数据库操作的时候不用去写复杂的sql语句,把对数据库的操作上升到对于对象的操作 G ...

  10. C#LeetCode刷题-位运算

    位运算篇 # 题名 刷题 通过率 难度 78 子集   67.2% 中等 136 只出现一次的数字 C#LeetCode刷题之#136-只出现一次的数字(Single Number) 53.5% 简单 ...