文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

For循环和While循环之流的终结

2024-12-03 08:41

关注

循环语句是编程的基本组成部分。列表中的每一项都有用处,读取输入,直到输入结束,在屏幕上放置n个输入框。每当看到PR中的代码添加了循环语句,我都怒不可遏。现在我必定仔细检查代码,确保循环可以终止。

[[389350]]

我希望所有运行良好的语句库中都看不到循环语句的踪影,但仍然有一些悄悄混进来,所以我想告诉大家如何消除循环语句。

让循环语句终结的关键是函数式编程。只需提供要在循环中执行的代码以及循环的参数(需要循环的内容)即可。我用Java作示范语言,但其实许多语言都支持这种类型的函数式编程,这种编程可以消除代码中的循环。

最简单的情况是对列表中的每个元素执行操作。

  1. List<Integer> list = List.of(1, 2, 3); 
  2. // bare for loop.  
  3. for(int i : list) { 
  4.    System.out.println("int = "+ i); 
  5. }// controlled for each 
  6. list.forEach(i -> System.out.println("int = " + i)); 

在这种最简单的情况下,无论哪种方法都没有太大优势。但第二种方法可以不使用bare for循环,而且语法更简洁。

我觉得forEach语句也有问题,应该只应用于副作用安全的方法。我所说的安全副作用是指不改变程序状态。上例只是记录日志,因此使用无碍。其他有关安全副作用的示例是写入文件、数据库或消息队列。

不安全的副作用会更改程序状态。下面为示例及其解决方法:

  1. // bad side-effect, the loop alters sum 
  2. int sum = 0; 
  3. for(int i : list) { 
  4.     sum += i; 
  5. System.out.println("sum = " + sum);// no side-effect, sum iscalculated by loop 
  6. sum = list 
  7.        .stream() 
  8.        .mapToInt(i -> i) 
  9.        .sum(); 
  10. System.out.println("sum = " + sum); 

另一个常见的例子:

  1. // bad side-effect, the loop alters list2 
  2. List<Integer> list2 = new ArrayList<>(); 
  3. for(int i : list) { 
  4.    list2.add(i); 
  5. list2.forEach(i -> System.out.println("int = " + i));// no sideeffect, the second list is built by the loop 
  6. list2 = list 
  7.          .stream() 
  8.          .collect(Collectors.toList()); 
  9. list2.forEach(i -> System.out.println("int = " + i)); 

当你需要处理列表项方法中的索引时就会出现问题,但可以解决,如下:

  1. // bare for loop with index
  2. for(int i = 0; i < list.size(); i++) { 
  3.     System.out.println("item atindex "  
  4.       + i  
  5.       + " = "  
  6.       + list.get(i)); 
  7. }// controlled loop with index
  8. IntStream.range(0, list.size()) 
  9.    .forEach(i ->System.out.println("item at index " 
  10.     + i 
  11.     + " = " 
  12.     + list.get(i))); 

老生常谈的问题:读取文件中的每一行直到文件读取完毕如何解决?

  1. BufferedReader reader = new BufferedReader( 
  2.        new InputStreamReader( 
  3.       LoopElimination.class.getResourceAsStream("/testfile.txt"))); 
  4. // while loop with clumsy looking syntax 
  5. String line; 
  6. while((line = reader.readLine()) != null) { 
  7.    System.out.println(line); 
  8. }reader = new BufferedReader( 
  9.        new InputStreamReader( 
  10.       LoopElimination.class.getResourceAsStream("/testfile.txt"))); 
  11. // less clumsy syntax 
  12. reader.lines() 
  13.    .forEach(l ->System.out.println(l)); 

应对上述情况有一个非常简便的lines方法,可以返回Stream类型。但是如果一个字符一个字符地读取呢?InputStream类没有返回Stream的方法。我们必须创建自己的Stream:

  1. InputStream is = 
  2.    LoopElimination.class.getResourceAsStream("/testfile.txt"); 
  3. // while loop with clumsy looking syntax 
  4. int c; 
  5. while((c = is.read()) != -1) { 
  6.   System.out.print((char)c); 
  7. // But this is even uglier 
  8. InputStream nis = 
  9.    LoopElimination.class.getResourceAsStream("/testfile.txt"); 
  10. // Exception handling makes functional programming ugly 
  11. Stream.generate(() -> { 
  12.    try { 
  13.       return nis.read(); 
  14.    } catch (IOException ex) { 
  15.       throw new RuntimeException("Errorreading from file", ex); 
  16.    } 
  17. }) 
  18.  .takeWhile(ch -> ch != -1) 
  19.  .forEach(ch ->System.out.print((char)(int)ch)); 

这种情况下while循环看起来更好。此外,Stream版本还使用了可以返回无限项目流的 generate函数,因此必须进一步检查以确保生成过程终止,这是由于takeWhile方法的存在。

InputStream类存在问题,因为缺少peek 方法来创建可轻松转换为Stream的Iterator。它还会抛出一个检查过的异常,这样函数式编程就会杂乱无章。在这种情况下可以使用while语句让PR通过。

为了使上述问题更简洁,可以创建一个新的IterableInputStream类型,如下:

  1. static class InputStreamIterable implements Iterable<Character> { 
  2.   private final InputStream is
  3.   public InputStreamIterable(InputStreamis) { 
  4.     this.is = is
  5.   } 
  6.   public Iterator<Character>iterator() { 
  7.      return newIterator<Character>() {               
  8.         public boolean hasNext() { 
  9.            try { 
  10.              // poor man's peek: 
  11.              is.mark(1); 
  12.              boolean ret = is.read() !=-1; 
  13.              is.reset(); 
  14.              return ret; 
  15.            } catch (IOException ex) { 
  16.              throw new RuntimeException( 
  17.                     "Error readinginput stream", ex); 
  18.            } 
  19.         } 
  20.         public Character next() { 
  21.            try { 
  22.              return (char)is.read(); 
  23.            } catch (IOException ex) { 
  24.              throw new RuntimeException( 
  25.                    "Error readinginput stream", ex); 
  26.            } 
  27.         } 
  28.      }; 
  29.   } 

这样就大大简化了循环问题:

  1. // use a predefined inputstream iterator: 
  2. InputStreamIterable it = new InputStreamIterable( 
  3.     LoopElimination.class.getResourceAsStream("/testfile.txt")); 
  4. StreamSupport.stream(it.spliterator(), false
  5.    .forEach(ch -> System.out.print(ch)); 

如果你经常遇到此类while循环,那么你可以创建并使用一个专门的Iterable类。但如果只用一次,就不必大费周章,这只是新旧Java不兼容的一个例子。

所以,下次你在代码中写for 语句或 while语句的时候,可以停下来思考一下如何用forEach 或 Stream更好地完成你的代码。

 

来源:读芯术内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯