基本定义
Java中重要的函数接口
| 接口 | 参数类型 | 返回类型 | 示例 |
|---|---|---|---|
| Predicate | T | boolean | 接受一个对象, 返回一个布尔值s -> s.isEmpty() |
| Consumer | T | void | s -> System.out.pring(s) |
| Function<T,R> | T | U | s -> new Integer(s) |
| Supplier | None | T | () -> new String(); |
| UnaryOperator | T | T | |
| BinaryOperator | (T, T) | T |
Java中的lambda表达式包含一个参数列表和一个lambda体,二者之间通过一个函数箭头“->”分隔。
单个参数:
p ->p.translate(1, 1)
i -> new Point(i, i + 1)
不过与方法声明类似,lambda 可以接收任意数量的参数。除了像之前那样接收单个参数的lambda外,参数列表必须使用圆括号包围起来:
(x, y) ->x+y
()->23
到目前为止,我们在声明参数时并没有显式指定类型,因为不指定类型时lambda的可读性通常会更好一些。 不过,我们总是可以提供参数类型,有时这也是必要的,因为编译器可能无法从上下文中推断出其类型。如果显式提供了类型,那就必须为所有参数都提供类型,而且参数列表必须包围在圆括号中:
(int x,int y) ->x+y
可以像方法参数那样修改这种显式类型的参数,例如,可以将其声明为final, 也可以添加注解。
函数箭头右侧的lambda体可以是表达式,到目前为止所有示例都是这样的(注意,方法调用是表达式,包括那些返回void 的方法)。诸如此类的lambda有时也称为“表达式lambda"。更为一般的形式则是“语句lambda",其中的lambda体是-一个块,也就是说,是由花括号包围的一系列语句:
(Thread t) ->{t.start();}
() ->{System.gc(); return 0;}
表达式lambda:
args -> expr
可以看成相应的语句lambda的简写形式:
args -> { return expr; }
在块体中到底使用还是省略return 关键字的原则与普通的方法体是一致的,也就是说,如果lambda体中的表达式有返回值,那就需要使用return, 也可以后面跟一个参数来立刻终止lambda 体的执行。如果lambda返回void,那就可以省略returm,也可以使用它,但后面不带参数。
方法引用类型:
方法引用
artist -> artist.getName() 等价于 Artist::getName
lambda与匿名内部类
内部类的声明会创建出一个新的命名作用域,在这个作用域中,this 与super指的是内部类本身的当前实例:相反,lambda表达式并不会引入任何新的命名环境。这样就避免了内部类名称查找的复杂性,名称查找会导致很多小错误,例如想要调用外围实例的方法时却错误地调用了内部类实例的Object方法。
由于lambda声明就像简单的块一样,因此关键字this与super与外围环境的含义一样:也就是说,它们分别指的是外围对象及其父类对象。
惰性求值与及早求值
像filter 这样只描述 Stream, 最终不产生新集合的方法叫作惰性求值方法; 而像 count 这样最终会从 Stream 产生值的方法叫作及早求值方法。
判断一个操作是惰性求值还是及早求值很简单: 只需看它的返回值。 如果返回值是 Stream, 那么是惰性求值; 如果返回值是另一个值或为空, 那么就是及早求值。
Tip:高阶函数是指接受另外一个函数作为参数, 或返回一个函数的函数。 高阶函数不难辨认: 看函数签名就够了。 如果函数的参数列表里包含函数接口, 或该函数返回一个函数接口, 那么该函数就是高阶函数。
常用的流操作
Stream API
Stream 的 of 方法使用一组初始值生成新的 Stream。
List collected = Stream.of("a", "b", "c").collect(Collectors.toList());
assertEquals(Arrays.asList("a", "b", "c"), collected);
map 映射
可以将一种类型的值转换成另外一种类型,方法map会通过提供的Function<T, R>转换每个流元素。
其输出是一个流,包含了对输入流中每个元素应用了Function后的结果。
List collected = Stream.of("a", "b", "hello") .map(string -> string.toUpperCase()) .collect(toList());
assertEquals(asList("A", "B", "HELLO"), collected);
传给 map 的 Lambda 表达式只接受一个 String 类型的参数, 返回一个新的 String。 参数 和返回值不必属于同一种类型, 但是 Lambda 表达式必须是 Function 接口的一个实例( 如 图 3-4 所示), Function 接口是只包含一个参数的普通函数接口。
filter 过滤
经过过滤,输入流中符合提供的Predicate的那些元素, 即 Lambda 表达式值为 true 的元素被保留下来。
flatMap
flatMap 方法可用 Stream 替换值, 然后将多个 Stream 连接成一个 Stream。
假定选定一组专辑(album), 找出其中所有长度大于 1 分钟的曲目(track)名称
Set trackNames = albums.stream().flatMap(album -> album.getTracks()).filter(track -> track.getLength() > 60).map(track -> track.getName()).collect(toSet());
排序与去重
如果集合本身就是无序的, 由此生成的流也是无序的。sorted使得输出流包含输入流中的元素,并且是有序的。
- 第1个sorted重载方法会使用自然顺序对对象进行排序。
Stream sortedTitles = library.stream().map(Book: :getTitle).sorted();
- 第2个重载的sorted会接受一个Comparator;例如,静态方法Comparator.comparing会根据一个键抽取器创建一个Comparator:
Stream booksSortedByTitle = library.stream().sorted(Comparator.comparing(Book: :getTitle));
通过从一个键创建一个Comparator,它提供了除了对流元素
搜索操作
可以划分为“搜索"操作的Stream方法分为两组:
第1组:包含了匹配操作,它们会测试是否有某个流元素或全部流元素满足给定的Predicate:
anyMatch在找到与predicate匹配的元素时会返回true;allMatch在找到不满足predicate的任意一个元素时会返回false,否则返回true;
noneMatch与之类似,如果找到任意一个满足predicate的元素时会返回false,否则返回true。
boolean withinShelfHeight = library.stream().filter(b ->b.getTopic() == HISTORY).allMatch(b ->b.getHeight() < 19) ;
第2组:搜索操作由两个“find"方法构成: findFirst与findAny:
findFirst在有序流中找到第1个匹配的元素并返回。而如果有序流中的任何一个匹配的元素都可以接受,那么你就应该使用findAny;
optional anyBook = library.stream().filter(b ->b.getAuthors().contains ("Herman Melville")).findAny();
BufferedReader br = new BufferedReader (new FileReader ("Mastering.tex")) ;
Optional line = br.lines().filter(s ->s.contains ("findFirst") ).findFirst() ;
类Optional
- get: 如果存在则返回一个值;否则,该方法会抛出NoSuchElementException异常。这是个“不安全的”访问一个Optional内容的操作,通常情况下应该避免使用,转而使用如下安全的方法。
- ifPresent:如果值存在,那么将其提供给Consumer; 否则,什么都不做。
- isPresent: 如果值存在,那么返回true;否则返回false。
- orElse: 如果值存在,那么将其返回,否则返回参数。它与orElseGet是访问内容的安全操作。对于Optional的一般使用场景来说,即便存在空值的可能,这些操作也要比get用处更大。
- orElseGet: 如果值存在,那么将其返回;否则,调用Supplier并返回其结果。
reduce
reduce 操作可以实现从一组值中生成一个值。
Comparator titleComparator = Comparator.comparing(Book::getTitle) ;
Optional first = library.stream().reduce(BinaryOperator.minBy(titleComparator));
Stream biStream = LongStream.of(1,2,3).mapTo0bj(BigInteger: :value0f) ;
Optional bigIntegerSum = biStream.reduce(BigInteger: :add) ;
流处理的示例
- 只包含计算机图书的流:
Stream computingBooks = library.stream ().filter(b ->b.getTopic() == COMPUTING);
- 图书标题的流:
Stream bookTitles = library.stream().map(Book: :getTitle);
- 根据标题排序:
Stream booksSortedByTitle = library.stream().sorted(Comparator.comparing(Book: :getTitle));
- 使用这个排序流创建一个作者流,根据图书标题排序,并且去除重复的:
Stream authorsInBookTitleorder = library.stream()
.sorted(Comparator.comparing(Book: :getTitle))
.flatMap(book ->book.getAuthors().stream())
.distinct();
- 以标题的字母顺序生成前100个图书的流:
Stream readingList = library.stream().sorted(Comparator.comparing(Book: :getTitle)).limit (100);
- 除去前100个图书的流:
Stream remainderList = library.stream().sorted(Comparator.comparing(Book: :getTitle)).skip(100);
- 图书馆中最早出版的图书:
Optional oldest = library.stream().min(Comparator.comparing(Book: :getPubDate)) ;
- 图书馆中图书的标题集合:
Set titles = library.stream().map(Book: :getTitle).collect(Collectors.toSet()) ;
- Accumulate names into a List
List list = people.stream().map(Person::getName).collect(Collectors.toList());
- Accumulate names into a TreeSet
Set set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));
- Convert elements to strings and concatenate them, separated by commas
String joined = things.stream().map(Object::toString).collect(Collectors.joining(", "));
- Compute sum of salaries of employee
int total = employees.stream().collect(Collectors.summingInt(Employee::getSalary)));
- Group employees by department
Map<Department, List> byDept = employees.stream().collect(Collectors.groupingBy(Employee::getDepartment));
- Compute sum of salaries by department
Map<Department, Integer> totalByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,Collectors.summingInt(Employee::getSalary)));
- Partition students into passing and failing
Map<Boolean, List> passingFailing = students.stream().collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
使用收集器
转换成其他集合
Set titles = library.stream().map(Book: :getTitle).collect(Collectors.toSet());
例如,可以通过toMap的第1个重载收集器将集合中的每本图书的标题映射到其出版日期上:
Map<String,Year> titleToPubDate = library.stream().collect(toMap(Book::getTitle, Book::getPubDate));
如果需要重复键,那么就可以使用第2个重载方法,这样就可以指定具体行为了,这是通过类型为BinaryOperator的merge函数形式指定的,它会从两个已有值(一个位于Map中,另一个是要添加到Map中的)生成一个新值。 有多种方式可以通过两个值来生成相同类型的第3个值;例如,两个String值可以拼接。这里,我决定只在Map中包含每本书的最新版本:
Map<String, Year> titleToPubDate = library.stream().collect(toMap(Book::getTitle,Book::get PubDate,(x,y) -> x.isAfter(y) ? x : y));
数据分块
partitioningBy, 它接受一个流, 并将其分成两部分。它使用 Predicate 对象判断一个元素应该属于哪个部分,并根据布尔值返回一个 Map到列表。因此,对于 true List 中的元素, Predicate 返回 true; 对其他 List 中的元素, Predicate 返回 false。
数据分组
数据分组是一种更自然的分割数据操作, 与数据分块将数据分成 ture 和 false 两部分不同, 可以使用任意值对数据分组。groupingBy 收集器接受一个分类函数, 用来对数据分组, 使用的分类器是一个 Function 对象,和 map 操作用到的一样。
- 根据主题对图书进行分类的Map:
Map<Topic,List> booksByTopic = library.stream().collect (groupingBy (Book: :getTopic));
- 从图书标题映射到最新版发布日期的有序Map:
Map<String, Year> titleToPubDate = library.stream () .collect (toMap (Book: :getTitle,Book: :getPubDate, BinaryOperator.maxBy (natural0rder()),TreeMap: :new));
- 将图书划分为小说(对应true)-与非小说(对应false)的Map:
Map<Boolean, List> fictionOrNon = library. stream().collect(partitioningBy(b -> b.getTopic() == FICTION));
- 将每个主题关联到该主题下拥有最多作者的图书上:
Map<Topic, optional > mostAuthorsByTopic =library.stream () .collect (groupingBy (Book: :getTopic, maxBy (comparing(b -> b.getAuthors() .size()))));
- 将每个主题关联到该主题总的卷数上:
Map<Topic, Integer> volumeCountByTopic = library.stream() .collect (groupingBy(Book::getTopic, summingInt(b -> b.getPageCounts().length)));
- 拥有最多图书的主题:
Optional mostPopularTopic = library.stream()
.collect(groupingBy (Book: :getTopic, counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey);
- 将每个主题关联到该主题下所有图书标题拼接成的字符串上:
Map<Topic, String> concatenatedTitlesByTopic = library.stream().collect(groupingBy(Book: :getTopic,mapping(Book: :getTitle, joining(";"))));
- given a stream of Person, to accumulate the set of last names in each city:
Map<City, Set> lastNamesByCity = people.stream().collect(groupingBy(Person::getCity, mapping(Person::getLastName, toSet())));
Map<String, Year> titleToPubDate = library.stream() .collect (toMap(Book::getTitle, Book: :getPubDate, (x,y) -> x.isAfter(y) ? x : y,TreeMap: :new)) ;
NavigableSet sortedTitles = library.stream().map (Book::getTitle).collect(toCollection(TreeSet::new)) ;