From 7848d43ed5abba053ddef37db3b5d8a0985a2e1d Mon Sep 17 00:00:00 2001 From: sjsdfg <736777445@qq.com> Date: Sat, 5 Jan 2019 15:58:28 +0800 Subject: [PATCH] =?UTF-8?q?47.=20=E4=BC=98=E5=85=88=E4=BD=BF=E7=94=A8=20Co?= =?UTF-8?q?llection=20=E8=80=8C=E4=B8=8D=E6=98=AF=20Stream=20=E6=9D=A5?= =?UTF-8?q?=E4=BD=9C=E4=B8=BA=E6=96=B9=E6=B3=95=E7=9A=84=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...¨Collection而ä¸æ˜¯Streamæ¥ä½œä¸ºæ–¹æ³•çš„返回类型.md | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 47. 优先使用Collection而ä¸æ˜¯Streamæ¥ä½œä¸ºæ–¹æ³•çš„返回类型.md diff --git a/47. 优先使用Collection而ä¸æ˜¯Streamæ¥ä½œä¸ºæ–¹æ³•çš„返回类型.md b/47. 优先使用Collection而ä¸æ˜¯Streamæ¥ä½œä¸ºæ–¹æ³•çš„返回类型.md new file mode 100644 index 0000000..63ad675 --- /dev/null +++ b/47. 优先使用Collection而ä¸æ˜¯Streamæ¥ä½œä¸ºæ–¹æ³•çš„返回类型.md @@ -0,0 +1,153 @@ +# 47. 优先使用 Collection 而ä¸æ˜¯ Stream æ¥ä½œä¸ºæ–¹æ³•çš„返回类型 + +  许多方法返回元素åºåˆ—(sequence)。在 Java 8 之å‰ï¼Œé€šå¸¸æ–¹æ³•çš„返回类型是 `Collection`,`Set` å’Œ `List` 这些接å£ï¼›è¿˜åŒ…括 `Iterable` 和数组类型。通常,很容易决定返回哪一ç§ç±»åž‹ã€‚规范(norm)是集åˆæŽ¥å£ã€‚如果该方法仅用于å¯ç”¨ for-each 循环,或者返回的åºåˆ—ä¸èƒ½å®žçŽ°æŸäº› `Collection` 方法 (通常是 `contains(Object)`),则使用迭代(`Iterable`)接å£ã€‚如果返回的元素是基本类型或有严格的性能è¦æ±‚,则使用数组。在 Java 8 中,将æµï¼ˆStream)添加到平å°ä¸­ï¼Œè¿™ä½¿å¾—为åºåˆ—返回方法选择适当的返回类型的任务å˜å¾—éžå¸¸å¤æ‚。 + +  你å¯èƒ½å¬è¯´è¿‡ï¼ŒæµçŽ°åœ¨æ˜¯è¿”回元素åºåˆ—的明显的选择,但是正如æ¡ç›® 45 所讨论的,æµä¸ä¼šä½¿è¿­ä»£è¿‡æ—¶ï¼šç¼–写好的代ç éœ€è¦æ˜Žæ™ºåœ°ç»“åˆæµå’Œè¿­ä»£ã€‚如果一个 API åªè¿”回一个æµï¼Œå¹¶ä¸”一些用户想用 for-each 循环é历返回的åºåˆ—,那么这些用户肯定会感到ä¸å®‰ã€‚这尤其令人沮丧,因为 `Stream` 接å£åœ¨ `Iterable` 接å£ä¸­åŒ…å«å”¯ä¸€çš„抽象方法,`Stream` 的方法规范与 `Iterable` 兼容。阻止程åºå‘˜ä½¿ç”¨ for-each 循环在æµä¸Šè¿­ä»£çš„唯一原因是 `Stream` 无法继承 `Iterable`。 + +  é—憾的是,这个问题没有好的解决方法。 ä¹ä¸€çœ‹ï¼Œä¼¼ä¹Žå¯ä»¥å°†æ–¹æ³•å¼•ç”¨ä¼ é€’ç»™ `Stream` çš„ iterator 方法。 结果代ç å¯èƒ½æœ‰ç‚¹å˜ˆæ‚å’Œä¸é€æ˜Žï¼Œä½†å¹¶éžä¸åˆç†ï¼š + +```Java +// Won't compile, due to limitations on Java's type inference +for (ProcessHandle ph : ProcessHandle.allProcesses()::iterator) { + // Process the process +} +``` + +  ä¸å¹¸çš„是,如果你试图编译这段代ç ï¼Œä¼šå¾—到一个错误信æ¯: + +```Java +Test.java:6: error: method reference not expected here +for (ProcessHandle ph : ProcessHandle.allProcesses()::iterator) { +``` + +  为了使代ç ç¼–译,必须将方法引用强制转æ¢ä¸ºé€‚当å‚数化的 `Iterable` 类型: + +```Java`此处输入代ç ` +// Hideous workaround to iterate over a stream +for (ProcessHandle ph : (Iterable)ProcessHandle.allProcesses()::iterator) +``` + +  此代ç æœ‰æ•ˆï¼Œä½†åœ¨å®žè·µä¸­ä½¿ç”¨å®ƒå¤ªå˜ˆæ‚å’Œä¸é€æ˜Žã€‚ 更好的解决方法是使用适é…器方法。 JDK 没有æ供这样的方法,但是使用上é¢çš„代ç ç‰‡æ®µä¸­ä½¿ç”¨çš„相åŒæŠ€æœ¯ï¼Œå¾ˆå®¹æ˜“编写一个方法。 请注æ„,在适é…器方法中ä¸éœ€è¦å¼ºåˆ¶è½¬æ¢ï¼Œå› ä¸º Java 的类型推断在此上下文中能够正常工作: + +```Java +// Adapter from Stream to Iterable +public static Iterable iterableOf(Stream stream) { + return stream::iterator; +} +``` + +  使用此适é…器,å¯ä»¥ä½¿ç”¨ for-each 语å¥è¿­ä»£ä»»ä½•æµï¼š + +```Java +for (ProcessHandle p : iterableOf(ProcessHandle.allProcesses())) { + // Process the process +} +``` + +  注æ„,æ¡ç›® 34 中的 `Anagrams` 程åºçš„æµç‰ˆæœ¬ä½¿ç”¨ `Files.lines` 方法读å–字典,而迭代版本使用了 scanner。`Files.lines` 方法优于 scanner,scanner 在读å–文件时无声地åžå™¬æ‰€æœ‰å¼‚常。ç†æƒ³æƒ…况下,我们也会在迭代版本中使用 `Files.lines`。如果 API åªæ供对åºåˆ—çš„æµè®¿é—®ï¼Œè€Œç¨‹åºå‘˜å¸Œæœ›ä½¿ç”¨ for-each 语å¥é历åºåˆ—,那么他们就è¦åšå‡ºè¿™ç§å¦¥å。 + +  相å,如果一个程åºå‘˜æƒ³è¦ä½¿ç”¨æµç®¡é“æ¥å¤„ç†ä¸€ä¸ªåºåˆ—,那么一个åªæä¾› `Iterable` çš„ API 会让他感到ä¸å®‰ã€‚JDK åŒæ ·æ²¡æœ‰æ供适é…器,但是编写这个适é…器éžå¸¸ç®€å•: + +```Java +// Adapter from Iterable to Stream +public static Stream streamOf(Iterable iterable) { + return StreamSupport.stream(iterable.spliterator(), false); +} +``` + +  如果你正在编写一个返回对象åºåˆ—的方法,并且它åªä¼šåœ¨æµç®¡é“中使用,那么当然å¯ä»¥è‡ªç”±åœ°è¿”回æµã€‚类似地,返回仅用于迭代的åºåˆ—的方法应该返回一个 `Iterable`。但是如果你写一个公共 API,它返回一个åºåˆ—,你应该为用户æ供哪些想写æµç®¡é“,哪些想写 for-each 语å¥ï¼Œé™¤éžä½ æœ‰å……分的ç†ç”±ç›¸ä¿¡å¤§å¤šæ•°ç”¨æˆ·æƒ³è¦ä½¿ç”¨ç›¸åŒçš„机制。 + +  `Collection` 接å£æ˜¯ `Iterable` çš„å­ç±»åž‹ï¼Œå¹¶ä¸”具有 `stream` 方法,因此它æ供迭代和æµè®¿é—®ã€‚ 因此,**`Collection` 或适当的å­ç±»åž‹é€šå¸¸æ˜¯å…¬å…±åºåˆ—返回方法的最佳返回类型。** 数组还使用 `Arrays.asList` å’Œ `Stream.of` 方法æ供简å•çš„迭代和æµè®¿é—®ã€‚ 如果返回的åºåˆ—å°åˆ°è¶³ä»¥å®¹æ˜“地放入内存中,那么最好返回一个标准集åˆå®žçŽ°ï¼Œä¾‹å¦‚ `ArrayList` 或 `HashSet`。 **但是ä¸è¦åœ¨å†…存中存储大的åºåˆ—,åªæ˜¯ä¸ºäº†å°†å®ƒä½œä¸ºé›†åˆè¿”回。** + +  如果返回的åºåˆ—很大但å¯ä»¥ç®€æ´åœ°è¡¨ç¤ºï¼Œè¯·è€ƒè™‘实现一个专用集åˆã€‚ 例如,å‡è®¾è¿”回给定集åˆçš„幂集(power set:就是原集åˆä¸­æ‰€æœ‰çš„å­é›†ï¼ˆåŒ…括全集和空集)构æˆçš„集æ—),该集包å«å…¶æ‰€æœ‰å­é›†ã€‚ {a,b,c} 的幂集为 {{},{a},{b},{c},{a,b},{a,c},{b,c},{a,b, c}}。 如果一个集åˆå…·æœ‰ n 个元素,则幂集具有 2n 个。 因此,你甚至ä¸åº”考虑将幂集存储在标准集åˆå®žçŽ°ä¸­ã€‚ 但是,在 `AbstractList` 的帮助下,很容易为此实现自定义集åˆã€‚ + +  诀çªæ˜¯ä½¿ç”¨å¹‚集中æ¯ä¸ªå…ƒç´ çš„索引作为ä½å‘é‡ï¼ˆbit vector),其中索引中的第 n ä½æŒ‡ç¤ºæºé›†åˆä¸­æ˜¯å¦å­˜åœ¨ç¬¬ n 个元素。 本质上,从 0 到 2n-1 的二进制数和 n 个元素集和的幂集之间存在自然映射。 这是代ç ï¼š + +```Java +// Returns the power set of an input set as custom collection +public class PowerSet { + public static final Collection> of(Set s) { + List src = new ArrayList<>(s); + + if (src.size() > 30) + throw new IllegalArgumentException("Set too big " + s); + + return new AbstractList>() { + @Override + public int size() { + return 1 << src.size(); // 2 to the power srcSize + } + + @Override + public boolean contains(Object o) { + return o instanceof Set && src.containsAll((Set)o); + } + + @Override + public Set get(int index) { + Set result = new HashSet<>(); + for (int i = 0; index != 0; i++, index >>= 1) + if ((index & 1) == 1) + result.add(src.get(i)); + return result; + } + }; + } +} +``` + +  请注æ„,如果输入集åˆè¶…过 30 个元素,则 `PowerSet.of` 方法会引å‘异常。 è¿™çªå‡ºäº†ä½¿ç”¨ `Collection` 作为返回类型而ä¸æ˜¯ `Stream` 或 `Iterable` 的缺点:`Collection` 有 int 返回类型的 size 的方法,该方法将返回åºåˆ—的长度é™åˆ¶ä¸º `Integer.MAX_VALUE` 或 231-1。`Collection` 规范å…许 size 方法返回 231 - 1,如果集åˆæ›´å¤§ï¼Œç”šè‡³æ— é™ï¼Œä½†è¿™ä¸æ˜¯ä¸€ä¸ªå®Œå…¨ä»¤äººæ»¡æ„的解决方案。 + +  为了在 `AbstractCollection` 上编写 `Collection` 实现,除了 `Iterable` 所需的方法之外,åªéœ€è¦å®žçŽ°ä¸¤ç§æ–¹æ³•ï¼š`contains` å’Œ `size`。 通常,编写这些方法的有效实现很容易。 如果ä¸å¯è¡Œï¼Œå¯èƒ½æ˜¯å› ä¸ºåœ¨è¿­ä»£å‘生之å‰æœªé¢„先确定åºåˆ—的内容,返回 `Stream` 还是 `Iterable` 的,无论哪ç§æ„Ÿè§‰æ›´è‡ªç„¶ã€‚ 如果选择,å¯ä»¥ä½¿ç”¨ä¸¤ç§ä¸åŒçš„方法分别返回。 + +  有时,你会仅根æ®å®žçŽ°çš„易用性选择返回类型。例如,å‡è®¾å¸Œæœ›ç¼–写一个方法,该方法返回输入列表的所有 (è¿žç»­çš„) å­åˆ—表。生æˆè¿™äº›å­åˆ—表并将它们放到标准集åˆä¸­åªéœ€è¦ä¸‰è¡Œä»£ç ï¼Œä½†æ˜¯ä¿å­˜è¿™ä¸ªé›†åˆæ‰€éœ€çš„内存是æºåˆ—表大å°çš„二次方。虽然这没有指数幂集那么糟糕,但显然是ä¸å¯æŽ¥å—çš„ã€‚å®žçŽ°è‡ªå®šä¹‰é›†åˆ (å°±åƒæˆ‘们对幂集所åšçš„那样) 会很ä¹å‘³ï¼Œå› ä¸º JDK 缺少一个框架 `Iterator` 实现æ¥å¸®åŠ©æˆ‘们。 + +  然而,实现输入列表的所有å­åˆ—表的æµæ˜¯ç›´æˆªäº†å½“的,尽管它确实需è¦ä¸€ç‚¹çš„洞察力(insight)。 让我们调用一个å­åˆ—表,该å­åˆ—表包å«åˆ—表的第一个元素和列表的å‰ç¼€ã€‚ 例如,(a,b,c)的å‰ç¼€æ˜¯ï¼ˆa),(a,b)和(a,b,c)。 类似地,让我们调用包å«åŽç¼€çš„最åŽä¸€ä¸ªå…ƒç´ çš„å­åˆ—表,因此(a,b,c)的åŽç¼€æ˜¯ï¼ˆa,b,c),(b,c)和(c)。 洞察力是列表的å­åˆ—表åªæ˜¯å‰ç¼€çš„åŽç¼€ï¼ˆæˆ–相åŒçš„åŽç¼€çš„å‰ç¼€ï¼‰å’Œç©ºåˆ—表。 这一观察直接展现了一个清晰,åˆç†ç®€æ´çš„实现: + +```Java +// Returns a stream of all the sublists of its input list +public class SubLists { + + public static Stream> of(List list) { + return Stream.concat(Stream.of(Collections.emptyList()), + prefixes(list).flatMap(SubLists::suffixes)); + } + + private static Stream> prefixes(List list) { + return IntStream.rangeClosed(1, list.size()) + .mapToObj(end -> list.subList(0, end)); + } + + private static Stream> suffixes(List list) { + return IntStream.range(0, list.size()) + .mapToObj(start -> list.subList(start, list.size())); + } +} +``` + +  请注æ„,`Stream.concat` 方法用于将空列表添加到返回的æµä¸­ã€‚ 还有,`flatMap` 方法(æ¡ç›® 45)用于生æˆç”±æ‰€æœ‰å‰ç¼€çš„所有åŽç¼€ç»„æˆçš„å•ä¸ªæµã€‚ 最åŽï¼Œé€šè¿‡æ˜ å°„ `IntStream.range` å’Œ `IntStream.rangeClosed` 返回的连续 int 值æµæ¥ç”Ÿæˆå‰ç¼€å’ŒåŽç¼€ã€‚这个习惯用法,粗略地说,æµç­‰ä»·äºŽæ•´æ•°ç´¢å¼•ä¸Šçš„标准 for 循环。因此,我们的å­åˆ—表实现似于明显的嵌套 for 循环: + +```Java +for (int start = 0; start < src.size(); start++) + for (int end = start + 1; end <= src.size(); end++) + System.out.println(src.subList(start, end)); +``` + +  å¯ä»¥å°†è¿™ä¸ª for 循环直接转æ¢ä¸ºæµã€‚结果比我们以å‰çš„实现更简æ´ï¼Œä½†å¯èƒ½å¯è¯»æ€§ç¨å·®ã€‚它类似于æ¡ç›® 45 中的笛å¡å°”积的使用æµçš„代ç : + +```Java +// Returns a stream of all the sublists of its input list +public static Stream> of(List list) { + return IntStream.range(0, list.size()) + .mapToObj(start -> + IntStream.rangeClosed(start + 1, list.size()) + .mapToObj(end -> list.subList(start, end))) + .flatMap(x -> x); +} +``` +  与之å‰çš„ for 循环一样,此代ç ä¸ä¼šåŒ…æ¢ç©ºåˆ—表。 为了解决这个问题,å¯ä»¥ä½¿ç”¨ `concat` 方法,就åƒæˆ‘们在之å‰ç‰ˆæœ¬ä¸­æ‰€åšçš„那样,或者在 `rangeClosed` 调用中用 `(int) Math.signum(start)` æ›¿æ¢ 1。 + +  这两ç§å­åˆ—表的æµå®žçŽ°éƒ½å¯ä»¥ï¼Œä½†éƒ½éœ€è¦ä¸€äº›ç”¨æˆ·ä½¿ç”¨æµ-迭代适é…器 ( Stream-to-Iterable adapte),或者在更自然的地方使用æµã€‚æµ-迭代适é…器ä¸ä»…打乱了客户端代ç ï¼Œè€Œä¸”在我的机器上使循环速度é™ä½Žäº† 2.3 å€ã€‚一个专门构建的 Collection 实现 (此处未显示) è¦å†—长,但è¿è¡Œé€Ÿåº¦å¤§çº¦æ˜¯æˆ‘的机器上基于æµçš„实现的 1.4 å€ã€‚ + +  总之,在编写返回元素åºåˆ—的方法时,请记ä½ï¼ŒæŸäº›ç”¨æˆ·å¯èƒ½å¸Œæœ›å°†å®ƒä»¬ä½œä¸ºæµå¤„ç†ï¼Œè€Œå…¶ä»–用户å¯èƒ½å¸Œæœ›è¿­ä»£æ–¹å¼æ¥å¤„ç†å®ƒä»¬ã€‚ å°½é‡é€‚应两个群体。 如果返回集åˆæ˜¯å¯è¡Œçš„,请执行此æ“作。 如果已ç»æ‹¥æœ‰é›†åˆä¸­çš„元素,或者åºåˆ—中的元素数é‡è¶³å¤Ÿå°ï¼Œå¯ä»¥åˆ›å»ºä¸€ä¸ªæ–°çš„元素,那么返回一个标准集åˆï¼Œæ¯”如 `ArrayList`。 å¦åˆ™ï¼Œè¯·è€ƒè™‘实现自定义集åˆï¼Œå°±åƒæˆ‘们为幂集程åºé‡Œæ‰€åšçš„那样。 如果返回集åˆæ˜¯ä¸å¯è¡Œçš„,则返回æµæˆ–å¯è¿­ä»£çš„,无论哪个看起æ¥æ›´è‡ªç„¶ã€‚ 如果在将æ¥çš„ Java 版本中,`Stream` 接å£å£°æ˜Žè¢«ä¿®æ”¹ä¸ºç»§æ‰¿ `Iterable`,那么应该éšæ„返回æµï¼Œå› ä¸ºå®ƒä»¬å°†å…许æµå’Œè¿­ä»£å¤„ç†ã€‚ +