java8函数式编程(2)

/** * @author zhangdi * @description 常用的流操作 */ public class LambdaChapter3_3 { public static void main(String[] args) { // new LambdaChapter3_3().testCollect(); // new LambdaChapter3_3().testMap_usingMap(); // new LambdaChapter3_3().testfilter_usingLambda(); // new LambdaChapter3_3().testflatMap(); new LambdaChapter3_3().testMaxAndMin(); } // 3.3.1 collect(toList()) :collect(toList()) 方法由 Stream 里的值生成一个列表, // 是一个及早求值操作。 public void testCollect() { List<String> collected = Stream.of("a", "b", "c") // <1> .collect(Collectors.toList()); // <2> assertEquals(Arrays.asList("a", "b", "c"), collected); // <3> } // 3.3.2 map 如果有一个函数可以将一种类型的值转换成另外一种类型, map 操作就可以使用该函数, 将一个流中的值转换成一个新的流 /** * 使用 for 循环将字符串转换为大写 */ public void testMap_usingFor() { List<String> collected = new ArrayList<>(); for (String string : asList("a", "b", "hello")) { String upperCase = string.toUpperCase(); collected.add(upperCase); } assertEquals(asList("A", "B", "HELLO"), collected); } /** * 使用 map 循环将字符串转换为大写 */ public void testMap_usingMap() { List<String> collected = Stream.of("a", "b", "hello").map(string -> string.toUpperCase()) .collect(Collectors.toList()); } // 3.3.3 filter ;遍历数据并检查其中的元素时, 可尝试使用 Stream 中提供的新方法 filter /** * 使用循环遍历列表, 使用条件语句做判断 */ public void testfilter_usingFor() { List<String> beginningWithNumbers = new ArrayList<>(); for (String value : asList("Demacia", "Bilgewater", "2Valoran")) { if (isDigit(value.charAt(0))) { beginningWithNumbers.add(value); } } assertEquals(asList("2Valoran"), beginningWithNumbers); } /** * 函数式风格 */ public void testfilter_usingLambda() { List<String> beginningWithNumbers = Stream.of("Demacia", "Bilgewater", "2Valoran") .filter((value) -> isDigit(value.charAt(0))).collect(toList()); assertEquals(asList("2Valoran"), beginningWithNumbers); } // 3.3.4 flatMap: flatMap 方法可用 Stream 替换值, 然后将多个 Stream 连接成一个 Stream // 前面已介绍过 map 操作, 它可用一个新的值代替 Stream 中的值。 但有时, 用户希望让 map // 操作有点变化, 生成一个新的 Stream 对象取而代之。 /** * Question:假设有一个包含多个列表的流, 现在希望得到所有数字的序列。 * 包含多个列表的 Stream */ public void testflatMap() { List<Integer> together = Stream.of(asList(1, 2), asList(3, 6)).flatMap(value -> value.stream()) .collect(toList()); assertEquals(asList(1, 2, 3, 6), together); } // 3.3.5 max和min:Stream 上常用的操作之一是求最大值和最小值 /** * 使用 Stream 查找最短曲目 */ public void testMaxAndMin() { List<Track> tracks = asList(new Track("Bakai", 524), new Track("Violets for Your Furs", 378), new Track("Time Was", 451)); Track shortestTrack = tracks.stream().min(Comparator.comparing(track -> track.getLength())).get(); /**这种new Track("Violets for Your Furs", 378)的方式会报错:Exception in thread "main" java.lang.AssertionError: expected:<testLambda.LambdaChapter3_3$Track@5b8540fd> but was:<testLambda.LambdaChapter3_3$Track@f6dff17f> */ // assertEquals(new Track("Violets for Your Furs", 378) ,shortestTrack); assertEquals(tracks.get(1), shortestTrack); } // 3.3.6 通用模式 /** * 使用 for 循环查找最短曲目 */ public void testMaxAndMin_usingFor() { List<Track> tracks = asList(new Track("Bakai", 524), new Track("Violets for Your Furs", 378), new Track("Time Was", 451)); Track shortestTrack = tracks.get(0);// 假定第一个track为最短曲目 for (Track track : tracks) { if (track.getLength() < shortestTrack.getLength()) { shortestTrack = track; } } assertEquals(tracks.get(1), shortestTrack); } // 例 3-15 中的伪代码体现了通用模式的特点:reduce 模式 例3-15 public void forkCode() { // Object accumulator = initialValue; // for (Object element : collection) { // accumulator = combine(accumulator, element); // } } // 3.3.7 reduce:reduce 操作可以实现从一组值中生成一个值。 在上述例子中用到的 count、 min 和 max 方法, // 因为常用而被纳入标准库中。 事实上, 这些方法都是 reduce 操作。 /** * 使用 reduce(reduce的类型是BinaryOperator) 求和:将两个参数相加, acc 是累加器, 保存着当前的累加结果 */ public void testSum_usingReduce() { int count = Stream.of(1, 2, 3).reduce(0, (acc, element) -> acc + element); assertEquals(6, count); } // 例 3-17 展开 reduce 操作 public void testReduce_expand_To_Sum() { BinaryOperator<Integer> accumulator = (acc, element) -> acc + element; int count = accumulator.apply(accumulator.apply(accumulator.apply(0, 1), 2), 3); } // 例 3-18 使用命令式编程方式求和 :在命令式编程方式下, 每一次循环将集合中的元素和累加器相加, 用相加后的结果更新累加器的值。 public void testLambda_To_Sum() { int acc = 0; for (Integer element : asList(1, 2, 3)) { acc = acc + element; String str = "1"; } assertEquals(6, acc); } // 3.3.8 整合操作 :如何将问题分解为简单的 Stream 操作 // 第一个要解决的问题是, 找出某张专辑上所有乐队的国籍。 /* * 问题步骤分解: 1. 找出专辑上的所有表演者。 2. 分辨出哪些表演者是乐队。 3. 找出每个乐队的国籍。 4. 将找出的国籍放入一个集合。 */ @Test public void findnNationality() { // Album album2 = SampleData.manyTrackAlbum; Album album = new Album("album_test", new ArrayList<Track>(Arrays.asList(new Track("track", 2), new Track("track2", 3))), new ArrayList<Artist>(Arrays.asList(new Artist("the tester", "china"), new Artist("haha", "USA"))) ); Set<String> set = album.getMusicians().filter((artist) -> artist.getName().startsWith("the"))// 假设所有乐队都以"the"开头. .map(artist -> artist.getNationality()).collect(Collectors.toSet()); for (String string : set) { System.out.println("set:" + string); } // System.out.println("set:"+set.toArray().toString()); } // 3.4 重构遗留代码 // 例 3-19 遗留代码: 找出长度大于 1 分钟的曲目 (假定选定一组专辑, 找出其中所有长度大于 1 分钟的曲目名称。) public Set<String> findLongTracks(List<Album> albums) { Set<String> trackNames = new HashSet<>(); for (Album album : albums) { for (Track track : album.getTrackList()) { if (track.getLength() > 60) { String name = track.getName(); trackNames.add(name); } } } return trackNames; } // 例 3-20 重构的第一步: 找出长度大于 1 分钟的曲目 public Set<String> findLongTracks_lambda_step1(List<Album> albums) { Set<String> trackNames = new HashSet<>(); // 使用 Stream 的 forEach 方法替换掉 for 循环 albums.stream().forEach(album -> { album.getTracks().forEach(track -> { if (track.getLength() > 60) { trackNames.add(track.getName()); } }); }); return trackNames; } // 例 3-21 重构的第二步 public Set<String> findLongTracks_lambda_step2(List<Album> albums) { Set<String> trackNames = new HashSet<>(); albums.stream().forEach(album -> { // 最内层的 forEach 方法有三个功用: 找出长度大于 1 分钟的曲目, 得到符合条件的曲目名称, 将曲目名称加入集合 Set album.getTracks().filter(track -> track.getLength() > 60).map(track -> track.getName()) .forEach(name -> trackNames.add(name)); }); return trackNames; } // 例 3-22 重构的第三步: 找出长度大于 1 分钟的曲目 public Set<String> findLongTrackslambda_step3(List<Album> albums) { Set<String> trackNames = new HashSet<>(); // 任何时候想转化或替代代码, 都该使用 map 操作。 这里将使用比 map 更复杂的 flatMap 操作, 把多个 // Stream 合并成一个 Stream 并返回。 albums.stream().flatMap(album -> album.getTracks()).filter(track -> track.getLength() > 60) .map(track -> track.getName()).forEach(name -> trackNames.add(name)); return trackNames; } // 例 3-23 重构的第四步: 找出长度大于 1 分钟的曲目 public Set<String> findLongTrackslambda_step4(List<Album> albums) { return albums.stream() .flatMap(album->album.getTracks()) .filter(track->track.getLength()>60) .map(track->track.getName()) .collect(Collectors.toSet()); } //3.6 高阶函数:高阶函数是指接受另外一个函数作为参数, 或返回一个函数的函数。 // 3.8 要点回顾 // Ŗ 内部迭代将更多控制权交给了集合类。 // Ŗ 和 Iterator 类似, Stream 是一种内部迭代方式。 // Ŗ 将 Lambda 表达式和 Stream 上的方法结合起来, 可以完成很多常见的集合操作。