For循環(huán)和While循環(huán)之流的終結(jié)
本文轉(zhuǎn)載自公眾號(hào)“讀芯術(shù)”(ID:AI_Discovery)
循環(huán)語句是編程的基本組成部分。列表中的每一項(xiàng)都有用處,讀取輸入,直到輸入結(jié)束,在屏幕上放置n個(gè)輸入框。每當(dāng)看到PR中的代碼添加了循環(huán)語句,我都怒不可遏?,F(xiàn)在我必定仔細(xì)檢查代碼,確保循環(huán)可以終止。
我希望所有運(yùn)行良好的語句庫中都看不到循環(huán)語句的蹤影,但仍然有一些悄悄混進(jìn)來,所以我想告訴大家如何消除循環(huán)語句。
讓循環(huán)語句終結(jié)的關(guān)鍵是函數(shù)式編程。只需提供要在循環(huán)中執(zhí)行的代碼以及循環(huán)的參數(shù)(需要循環(huán)的內(nèi)容)即可。我用Java作示范語言,但其實(shí)許多語言都支持這種類型的函數(shù)式編程,這種編程可以消除代碼中的循環(huán)。
最簡(jiǎn)單的情況是對(duì)列表中的每個(gè)元素執(zhí)行操作。
- List<Integer> list = List.of(1, 2, 3);
- // bare for loop.
- for(int i : list) {
- System.out.println("int = "+ i);
- }// controlled for each
- list.forEach(i -> System.out.println("int = " + i));
在這種最簡(jiǎn)單的情況下,無論哪種方法都沒有太大優(yōu)勢(shì)。但第二種方法可以不使用bare for循環(huán),而且語法更簡(jiǎn)潔。
我覺得forEach語句也有問題,應(yīng)該只應(yīng)用于副作用安全的方法。我所說的安全副作用是指不改變程序狀態(tài)。上例只是記錄日志,因此使用無礙。其他有關(guān)安全副作用的示例是寫入文件、數(shù)據(jù)庫或消息隊(duì)列。
不安全的副作用會(huì)更改程序狀態(tài)。下面為示例及其解決方法:
- // bad side-effect, the loop alters sum
- int sum = 0;
- for(int i : list) {
- sum += i;
- }
- System.out.println("sum = " + sum);// no side-effect, sum iscalculated by loop
- sum = list
- .stream()
- .mapToInt(i -> i)
- .sum();
- System.out.println("sum = " + sum);
另一個(gè)常見的例子:
- // bad side-effect, the loop alters list2
- List<Integer> list2 = new ArrayList<>();
- for(int i : list) {
- list2.add(i);
- }
- list2.forEach(i -> System.out.println("int = " + i));// no sideeffect, the second list is built by the loop
- list2 = list
- .stream()
- .collect(Collectors.toList());
- list2.forEach(i -> System.out.println("int = " + i));
當(dāng)你需要處理列表項(xiàng)方法中的索引時(shí)就會(huì)出現(xiàn)問題,但可以解決,如下:
- // bare for loop with index:
- for(int i = 0; i < list.size(); i++) {
- System.out.println("item atindex "
- + i
- + " = "
- + list.get(i));
- }// controlled loop with index:
- IntStream.range(0, list.size())
- .forEach(i ->System.out.println("item at index "
- + i
- + " = "
- + list.get(i)));
老生常談的問題:讀取文件中的每一行直到文件讀取完畢如何解決?
- BufferedReader reader = new BufferedReader(
- new InputStreamReader(
- LoopElimination.class.getResourceAsStream("/testfile.txt")));
- // while loop with clumsy looking syntax
- String line;
- while((line = reader.readLine()) != null) {
- System.out.println(line);
- }reader = new BufferedReader(
- new InputStreamReader(
- LoopElimination.class.getResourceAsStream("/testfile.txt")));
- // less clumsy syntax
- reader.lines()
- .forEach(l ->System.out.println(l));
應(yīng)對(duì)上述情況有一個(gè)非常簡(jiǎn)便的lines方法,可以返回Stream類型。但是如果一個(gè)字符一個(gè)字符地讀取呢?InputStream類沒有返回Stream
- InputStream is =
- LoopElimination.class.getResourceAsStream("/testfile.txt");
- // while loop with clumsy looking syntax
- int c;
- while((c = is.read()) != -1) {
- System.out.print((char)c);
- }
- // But this is even uglier
- InputStream nis =
- LoopElimination.class.getResourceAsStream("/testfile.txt");
- // Exception handling makes functional programming ugly
- Stream.generate(() -> {
- try {
- return nis.read();
- } catch (IOException ex) {
- throw new RuntimeException("Errorreading from file", ex);
- }
- })
- .takeWhile(ch -> ch != -1)
- .forEach(ch ->System.out.print((char)(int)ch));
這種情況下while循環(huán)看起來更好。此外,Stream版本還使用了可以返回?zé)o限項(xiàng)目流的 generate函數(shù),因此必須進(jìn)一步檢查以確保生成過程終止,這是由于takeWhile方法的存在。
InputStream類存在問題,因?yàn)槿鄙賞eek 方法來創(chuàng)建可輕松轉(zhuǎn)換為Stream的Iterator。它還會(huì)拋出一個(gè)檢查過的異常,這樣函數(shù)式編程就會(huì)雜亂無章。在這種情況下可以使用while語句讓PR通過。
為了使上述問題更簡(jiǎn)潔,可以創(chuàng)建一個(gè)新的IterableInputStream類型,如下:
- static class InputStreamIterable implements Iterable<Character> {
- private final InputStream is;
- public InputStreamIterable(InputStreamis) {
- this.is = is;
- }
- public Iterator<Character>iterator() {
- return newIterator<Character>() {
- public boolean hasNext() {
- try {
- // poor man's peek:
- is.mark(1);
- boolean ret = is.read() !=-1;
- is.reset();
- return ret;
- } catch (IOException ex) {
- throw new RuntimeException(
- "Error readinginput stream", ex);
- }
- }
- public Character next() {
- try {
- return (char)is.read();
- } catch (IOException ex) {
- throw new RuntimeException(
- "Error readinginput stream", ex);
- }
- }
- };
- }
- }
這樣就大大簡(jiǎn)化了循環(huán)問題:
- // use a predefined inputstream iterator:
- InputStreamIterable it = new InputStreamIterable(
- LoopElimination.class.getResourceAsStream("/testfile.txt"));
- StreamSupport.stream(it.spliterator(), false)
- .forEach(ch -> System.out.print(ch));
如果你經(jīng)常遇到此類while循環(huán),那么你可以創(chuàng)建并使用一個(gè)專門的Iterable類。但如果只用一次,就不必大費(fèi)周章,這只是新舊Java不兼容的一個(gè)例子。
所以,下次你在代碼中寫for 語句或 while語句的時(shí)候,可以停下來思考一下如何用forEach 或 Stream更好地完成你的代碼。