行为参数化
- 行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不
- 同行为的能力。
- 行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。
- 传递代码,就是将新行为作为参数传递给方法。但在Java 8之前这实现起来很啰嗦。为接
- 口声明许多只用一次的实体类而造成的啰嗦代码,在Java 8之前可以用匿名类来减少。
- Java API包含很多可以用不同行为进行参数化的方法,包括排序、线程和GUI处理。
Lambda表达式
- lambda
- 参数列表——这里它采用了 Comparator 中 compare 方法的参数,两个 Apple 。
- 箭头——箭头 -> 把参数列表与Lambda主体分隔开。
- Lambda主体——比较两个 Apple 的重量。表达式就是Lambda的返回值了。

- 比较两个苹果重量的例子
先前:
Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
};
之后(用了Lambda表达式):
Comparator<Apple> byWeight =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
- lambda好处
- 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想
得多! - 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方
法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。 - 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
- 简洁——无需像匿名类那样写很多模板代码
- 示例
根据上述语法规则,以下哪个不是有效的Lambda表达式?
- (1) () -> {}
- (2) () -> "Raoul"
- (3) () -> {return "Mario";}
- (4) (Integer i) -> return "Alan" + i;
- (5) (String s) -> {"IronMan";}
答案:只有4和5是无效的Lambda。
- (1) 这个Lambda没有参数,并返回 void 。它类似于主体为空的方法: public void run() {} 。
- (2) 这个Lambda没有参数,并返回 String 作为表达式。
- (3) 这个Lambda没有参数,并返回 String (利用显式返回语句)。
- (4) return 是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:
- (Integer i) -> {return "Alan" + i;} 。
- (5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号
- 和分号,如下所示: (String s) -> "Iron Man" 。或者如果你喜欢,可以使用显式返回语
- 句,如下所示: (String s)->{return "IronMan";}
在哪里以及如何使用Lambda
-
函数式接口
函数式接口就是只定义一个抽象方法的接口。你已经知道了Java API中的一些其他函数式接口例如 Comparator 和 Runnable 。 如果你去看看新的Java API,会发现函数式接口带有@FunctionalInterface 的标注
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。你用匿名内部类也可以完成同样的事情,只不过比较笨拙
-
函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作
函数描述符。例如, Runnable 接口可以看作一个什么也不接受什么也不返回( void )的函数的
签名,因为它只有一个叫作 run 的抽象方法,这个方法什么也不接受,什么也不返回( void )。
①
我们在本章中使用了一个特殊表示法来描述Lambda和函数式接口的签名。 () -> void 代表
了参数列表为空,且返回 void 的函数。这正是 Runnable 接口所代表的。 举另一个例子, (Apple,
Apple) -> int 代表接受两个 Apple 作为参数且返回 int 的函数。我们会在3.4节和本章后面的
表3-2中提供关于函数描述符的更多信息
环绕执行模式
- 记得行为参数化
- 使用函数式接口来传递行为
- 执行一个行为
- 传递 Lambda
@FunctionalInterface
interface RowMap<T> {
T mapped(ResultSet resultSet) throws SQLException;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Teacher {
String name;
int age;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
String name;
int age;
}
class QueryUtil {
public <T> List<T> getList(String sql, RowMap<T> rm) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
List<T> temp = new ArrayList<>();
while (resultSet.next()) {
T mapped = rm.mapped(resultSet);
temp.add(mapped);
}
return temp;
}
}
public class Around1 {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
QueryUtil userDao = new QueryUtil();
List<User> list = userDao.getList("SELECT * FROM user",
resultSet ->
new User(resultSet.getString("name"), resultSet.getInt("age")
)
);
System.out.println(JSON.toJSONString(list));
List<Teacher> teachers = userDao.getList("SELECT * FROM teacher",
resultSet ->
new Teacher(resultSet.getString("name"), resultSet.getInt("age")
)
);
System.out.println(JSON.toJSONString(teachers));
}
}
使用函数式接口
-
predicate
Predicate<T> T->boolean
java.util.function.Predicate<T> 接口定义了一个名叫 test 的抽象方法,它接受泛型
T 对象,并返回一个 boolean 。 -
Consumer Consumer<T> T->void
java.util.function.Consumer<T> 定义了一个名叫 accept 的抽象方法,它接受泛型 T
的对象,没有返回( void )。 -
Function Function<T,R> T->R
java.util.function.Function<T, R> 接口定义了一个叫作 apply 的方法,它接受一个
泛型 T 的对象,并返回一个泛型 R 的对象。
//Predicate T->boolean
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 67, 8, 9);
List<Integer> collect = list.stream().filter((a -> a > 5)).collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect));
//Consumer T->void
list.forEach(a -> {
System.out.println(a);
});
//接收接收一个T类型,返回一个R类型 T->R
List<String> collect1 = list.stream().map(a -> a + 1+"").collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect1));
方法引用
Lambda及其等效方法引用的例子
Lambda | 等效的方法引用 |
---|---|
(Apple a) -> a.getWeight() | Apple::getWeight |
() -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s) | System.out::println |
- 指向静态方法的方法引用(例如 Integer 的 parseInt 方法,写作 Integer::parseInt )。
- 指 向 任 意 类 型 实 例 方 法 的 方 法 引 用 ( 例 如 String 的 length 方 法 , 写 作
String::length )。 - 指向现有对象的实例方法的方法引用(假设你有一个局部变量 expensiveTransaction
用于存放 Transaction 类型的对象,它支持实例方法 getValue ,那么你就可以写 expensive-
Transaction::getValue )。
appleList.stream().map(Apple::getColor).forEach(System.out::println);
appleList.forEach(System.out::println);
Lambda复合
什么是流

流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不
是临时编写一个实现),就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了!
其他库:Guava、Apache和lambdaj
为了给Java程序员提供更好的库操作集合,前人已经做过了很多尝试。比如,Guava就是
谷歌创建的一个很流行的库。它提供了 multimaps 和 multisets 等额外的容器类。Apache
Commons Collections库也提供了类似的功能。最后,本书作者Mario Fusco编写的lambdaj受到
函数式编程的启发,也提供了很多声明性操作集合的工具。
如今Java 8自带了官方库,可以以更加声明性的方式操作集合了。
“从支持数据处理操作的源生成的元素序列”。让
我们一步步剖析这个定义。
- 元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序
值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元
素(如 ArrayList 与 LinkedList )。但流的目的在于表达计算,比如你前面见到的
filter 、 sorted 和 map 。集合讲的是数据,流讲的是计算。我们会在后面几节中详细解
释这个思想。 - 源——流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集
合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。 - 数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中
的常用操作,如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。流操作可以顺序执
行,也可并行执行。
此外,流操作有两个重要的特点。 - 流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大
的流水线。这让我们下一章中的一些优化成为可能,如延迟和短路。流水线的操作可以
看作对数据源进行数据库式查询。 - 内部迭代——与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

集合与流
请注意,和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了
Stream<Apple> stream = appleList.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println);
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at cn.wyj.jdk7.TestFunction.main(TestFunction.java:59)
内部迭代与外部迭代

中间操作与终端操作
中间操作
诸如 filter 或 sorted 等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查
询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理——它们很懒。
这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。

终端操作
端操作会从流的流水线生成结果。其结果是任何不是流的值,比如 List 、 Integer ,甚
至 void 。例如,在下面的流水线中, forEach 是一个返回 void 的终端操作,它会对源中的每道
菜应用一个Lambda。把 System.out.println 传递给 forEach ,并要求它打印出由 menu 生成的
流中的每一个 Dish :
menu.stream().forEach(System.out::println);
使用流
筛选、切片和匹配
//筛选
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
//去重
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
//截短流
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
// 跳过
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
映射
对流中每一个元素应用函数
//如果你要找出每道菜的名称有多长
List<Integer> dishNameLengths = menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(toList());
流的扁平化
使用 flatMap 方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所
有使用 map(Arrays::stream) 时生成的单个流都被合并起来,即扁平化为一个流。
List<String> uniqueCharacters =
words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
网友评论