From 58d8e1b259fd328305da9f3945d833c63d7a9759 Mon Sep 17 00:00:00 2001 From: "venkats@agiledeveloper.com" Date: Mon, 25 Mar 2024 19:04:22 -0600 Subject: [PATCH 1/8] fifth article in the series --- .../00_refactoring.md | 1 + .../05_converting_to_streams.md | 103 ++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/00_refactoring.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/00_refactoring.md index 86a163e..c6e4be4 100644 --- a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/00_refactoring.md +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/00_refactoring.md @@ -23,4 +23,5 @@ In this series we cover the following conversions from the imperative to the fun | [Converting Loops with Steps](id:refactoring.loops.withsteps) | `for(...i = i + ...)` | `iterate()` with `takeWhile()` | | [Converting foreach with if](id:refactoring.foreach.withif) | `foreach(...) { if... }` | `stream()` with `filter()` | | [Converting Iteration with transformation](id:refactoring.iteration.withtransformation) | `foreach(...) { ...transformation... }` | `stream()` with `map()` | +| [Converting to Streams](id:refactoring.to.streams) | `File read operation` | `Files.lines()` | diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md new file mode 100644 index 0000000..77b5981 --- /dev/null +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md @@ -0,0 +1,103 @@ +--- +id: refactoring.to.streams +title: Converting to Streams +slug: learn/refactoring-to-functional-style/convertingtostreams +type: tutorial-group +group: refactoring-to-functional-style +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- Thinking in Streams {streams} +- From Imperative to Functional Style {imperativetofunctional} +- Mappings {mappings} +description: "Converting to use the Streams API." +last_update: 2024-03-25 +author: ["VenkatSubramaniam"] +--- + +  +## Thinking in Streams + +In the previous articles in this [tutorial series](id:refactoring) we looked at converting loops written in the imperative style to the functional style. In this article we'll look at viewing the source of data, through the functional eyes, as a stream of data and convert the iteration to use the Streams API. + +We saw how we can use `filter()` and `map()` functions to select and transform data, respectively. We can perform these operations in the middle of the functional pipeline. In the examples in the previous articles we used functions like `range()` and `rangeClosed()` to create a stream of values in a range of numbers. That worked nicely when we want to iterate over a known range of values, but, often we may want to work with data that comes from external resources, like from a file, for example. If we are able to work with the external resource as a stream, then we can readily apply the operations of the functional pipeline. In this article we'll take a look at an example that illustrates that idea. + +  +## From Imperative to Functional Style + +Suppose we want to iterate over a file and count the number of lines with one or more occurrences of a word. Here's an all too familiear imperative style code to accomplish that task: + +```java +//Sample.java +import java.util.*; +import java.io.*; + +public class Sample { + public static void main(String[] args) { + try { + final var filePath = "./Sample.java"; + final var wordOfInterest = "public"; + + try (var reader = new BufferedReader(new FileReader(filePath))) { + String line = ""; + long count = 0; + + while((line = reader.readLine()) != null) { + if(line.contains(wordOfInterest)) { + count++; + } + } + + System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest)); + } + } catch(Exception ex) { + System.out.println("ERROR: " + ex.getMessage()); + } + } +} + +``` + +In order to make it easy to work with this example, we look for the number of lines with the word "public" in the same source file as the code resides. You may change the value of the `filePath` to refer to a different file and/or the value of the `wordOfInterest` to something else if you like. + + +There are two major parts in this example. We use the `BufferedReader` to access the contents of the file we're interested in looking into. Then, in the `while` loop we check each line to see if it contains the desired word and, if so, increment the `count` to indicate we found another line with the word. Let's examine the two parts, with the second one first. + +Looking closely at the loop, from our discussions in the previous articles, we can recognize that the presence of `if` is a sign that we may use the `filter()` operation if we can write the code as a functional pipeline. Once we filter out or select the lines with the desired word, we can count the number of lines, using the `count()` method of stream. You're most likely curious and bursting to ask, "but, where's the stream?" To answer that question, let's take a look at the first part of the code. + +The data, that is the lines of text, is coming from the file whose path is provided in the variable `filePath`. We're reading the data using the `BufferedReader`'s `readLine()` method and the imperative style to iterate over each line of text. In order to use the functional pipeline, with the operations like `filter()` we need a `Stream` of data. Hence the question, "is it possible to get a stream of data for the contents in a file?" + +The answer, thankfully, is a resounding yes. The developers behind the JDK and the Java language did not merely introduce the capability to do functional programming and say "good luck." They took the pains to enhance the JDK to add functions so we, as programmers, can make good use of the functional capabilities of Java for our routine tasks. + +An easy way to turn the contents of a file into a stream of data is using the `lines()` method of the `Files` class that is part of the `java.nio.file` package. Let's refactor the previous imperative style code to the functional style, with the help of the `lines()` method that gives us the `Stream` over the contents of a file, like so: + +```java +//Sample.java +import java.util.*; +import java.nio.file.*; + +public class Sample { + public static void main(String[] args) { + try { + final var filePath = "./Sample.java"; + final var wordOfInterest = "public"; + + long count = Files.lines(Paths.get(filePath)) + .filter(line -> line.contains(wordOfInterest)) + .count(); + + System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest)); + } catch(Exception ex) { + System.out.println("ERROR: " + ex.getMessage()); + } + } +} +``` + +Not only does the `lines()` method provide a stream of data over the contents of a file, it remove so much of the cruft around reading the lines. Instead of the external iterator where we fetched one line at a time, the stream makes it possible to use the internal iterator where we can focus on what to do for each line of text as it emerges in the stream's pipeline. + +  +## Mappings + +Any where you're working with a collection of data from an external resource, ask if there is a way to get a stream of data over the contents of that resource. The chances are that you may find a function for that within the JDK or a third-party library. Once we get a stream, we can use the highly effective functional operators like `filter()`, `map()`, etc. to fluently iterate over the collection of data that's part of the resource. From ebcb95f3503372ec48272a0f6a9b343b5afaa682 Mon Sep 17 00:00:00 2001 From: "venkats@agiledeveloper.com" Date: Mon, 22 Apr 2024 16:08:55 +0530 Subject: [PATCH 2/8] fixed the indentation --- .../05_converting_to_streams.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md index 77b5981..2b68e81 100644 --- a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md @@ -45,8 +45,8 @@ public class Sample { while((line = reader.readLine()) != null) { if(line.contains(wordOfInterest)) { - count++; - } + count++; + } } System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest)); From 32c8deb5a2292d69f982fb85968b7d11b9821098 Mon Sep 17 00:00:00 2001 From: "venkats@agiledeveloper.com" Date: Mon, 22 Apr 2024 16:16:27 +0530 Subject: [PATCH 3/8] using Files.newBufferedReader --- .../05_converting_to_streams.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md index 2b68e81..396b0f1 100644 --- a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md @@ -30,23 +30,22 @@ Suppose we want to iterate over a file and count the number of lines with one or ```java //Sample.java -import java.util.*; -import java.io.*; +import java.nio.file.*; public class Sample { public static void main(String[] args) { try { - final var filePath = "./Sample.java"; + final var filePath = "./Sample.java"; final var wordOfInterest = "public"; - try (var reader = new BufferedReader(new FileReader(filePath))) { + try (var reader = Files.newBufferedReader(Paths.get(filePath))) { String line = ""; long count = 0; while((line = reader.readLine()) != null) { if(line.contains(wordOfInterest)) { - count++; - } + count++; + } } System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest)); From d77910b253b2998883d8cd13eebea8c0a15fd9d2 Mon Sep 17 00:00:00 2001 From: "venkats@agiledeveloper.com" Date: Mon, 22 Apr 2024 16:27:21 +0530 Subject: [PATCH 4/8] using Files.newBufferedReader --- .../05_converting_to_streams.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md index 396b0f1..cfdafbc 100644 --- a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md @@ -44,8 +44,8 @@ public class Sample { while((line = reader.readLine()) != null) { if(line.contains(wordOfInterest)) { - count++; - } + count++; + } } System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest)); @@ -61,9 +61,9 @@ public class Sample { In order to make it easy to work with this example, we look for the number of lines with the word "public" in the same source file as the code resides. You may change the value of the `filePath` to refer to a different file and/or the value of the `wordOfInterest` to something else if you like. -There are two major parts in this example. We use the `BufferedReader` to access the contents of the file we're interested in looking into. Then, in the `while` loop we check each line to see if it contains the desired word and, if so, increment the `count` to indicate we found another line with the word. Let's examine the two parts, with the second one first. +There are two major parts in this example. We use the `BufferedReader` returned by the `newBufferedReader()` method to access the contents of the file we're interested in looking into. Then, in the `while` loop we check each line to see if it contains the desired word and, if so, increment the `count` to indicate we found another line with the word. Let's examine the two parts, with the second one first. -Looking closely at the loop, from our discussions in the previous articles, we can recognize that the presence of `if` is a sign that we may use the `filter()` operation if we can write the code as a functional pipeline. Once we filter out or select the lines with the desired word, we can count the number of lines, using the `count()` method of stream. You're most likely curious and bursting to ask, "but, where's the stream?" To answer that question, let's take a look at the first part of the code. +Looking closely at the loop, from our discussions in the previous articles, we can recognize that the presence of `if` is a sign that we may use the `filter()` operation if we can write the code as a functional pipeline. Once we filter out or select the lines with the desired word, we can count the number of lines, using the `count()` method of stream. You're most likely curious and bursting to ask, "but, where's the Stream?" To answer that question, let's take a look at the first part of the code. The data, that is the lines of text, is coming from the file whose path is provided in the variable `filePath`. We're reading the data using the `BufferedReader`'s `readLine()` method and the imperative style to iterate over each line of text. In order to use the functional pipeline, with the operations like `filter()` we need a `Stream` of data. Hence the question, "is it possible to get a stream of data for the contents in a file?" From ae9765a98bbce92c6cf3ec2a2c00cc3af21e7b7b Mon Sep 17 00:00:00 2001 From: "venkats@agiledeveloper.com" Date: Mon, 22 Apr 2024 16:28:11 +0530 Subject: [PATCH 5/8] stream to uppercase --- .../05_converting_to_streams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md index cfdafbc..e9ab3dd 100644 --- a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md @@ -65,7 +65,7 @@ There are two major parts in this example. We use the `BufferedReader` returned Looking closely at the loop, from our discussions in the previous articles, we can recognize that the presence of `if` is a sign that we may use the `filter()` operation if we can write the code as a functional pipeline. Once we filter out or select the lines with the desired word, we can count the number of lines, using the `count()` method of stream. You're most likely curious and bursting to ask, "but, where's the Stream?" To answer that question, let's take a look at the first part of the code. -The data, that is the lines of text, is coming from the file whose path is provided in the variable `filePath`. We're reading the data using the `BufferedReader`'s `readLine()` method and the imperative style to iterate over each line of text. In order to use the functional pipeline, with the operations like `filter()` we need a `Stream` of data. Hence the question, "is it possible to get a stream of data for the contents in a file?" +The data, that is the lines of text, is coming from the file whose path is provided in the variable `filePath`. We're reading the data using the `BufferedReader`'s `readLine()` method and the imperative style to iterate over each line of text. In order to use the functional pipeline, with the operations like `filter()` we need a `Stream` of data. Hence the question, "is it possible to get a Stream of data for the contents in a file?" The answer, thankfully, is a resounding yes. The developers behind the JDK and the Java language did not merely introduce the capability to do functional programming and say "good luck." They took the pains to enhance the JDK to add functions so we, as programmers, can make good use of the functional capabilities of Java for our routine tasks. From d1b048356573d93e70813d6e625a0df9ae8a67c5 Mon Sep 17 00:00:00 2001 From: "venkats@agiledeveloper.com" Date: Mon, 22 Apr 2024 16:32:53 +0530 Subject: [PATCH 6/8] using Path.of and closing the stream --- .../05_converting_to_streams.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md index e9ab3dd..9915367 100644 --- a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md @@ -38,7 +38,7 @@ public class Sample { final var filePath = "./Sample.java"; final var wordOfInterest = "public"; - try (var reader = Files.newBufferedReader(Paths.get(filePath))) { + try (var reader = Files.newBufferedReader(Path.of(filePath))) { String line = ""; long count = 0; @@ -73,25 +73,26 @@ An easy way to turn the contents of a file into a stream of data is using the `l ```java //Sample.java -import java.util.*; import java.nio.file.*; public class Sample { public static void main(String[] args) { try { - final var filePath = "./Sample.java"; + final var filePath = "./Sample.java"; final var wordOfInterest = "public"; - long count = Files.lines(Paths.get(filePath)) - .filter(line -> line.contains(wordOfInterest)) - .count(); + try(var stream = Files.lines(Path.of(filePath))) { + long count = stream.filter(line -> line.contains(wordOfInterest)) + .count(); - System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest)); + System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest)); + } } catch(Exception ex) { System.out.println("ERROR: " + ex.getMessage()); } } } + ``` Not only does the `lines()` method provide a stream of data over the contents of a file, it remove so much of the cruft around reading the lines. Instead of the external iterator where we fetched one line at a time, the stream makes it possible to use the internal iterator where we can focus on what to do for each line of text as it emerges in the stream's pipeline. From f0acc59f77d6536fe304b0c50ea1ad8270f7c2d4 Mon Sep 17 00:00:00 2001 From: "venkats@agiledeveloper.com" Date: Mon, 22 Apr 2024 16:35:42 +0530 Subject: [PATCH 7/8] changed the wording --- .../05_converting_to_streams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md index 9915367..d09a5f6 100644 --- a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md @@ -100,4 +100,4 @@ Not only does the `lines()` method provide a stream of data over the contents of   ## Mappings -Any where you're working with a collection of data from an external resource, ask if there is a way to get a stream of data over the contents of that resource. The chances are that you may find a function for that within the JDK or a third-party library. Once we get a stream, we can use the highly effective functional operators like `filter()`, `map()`, etc. to fluently iterate over the collection of data that's part of the resource. +Whenever you're working with a collection of data from an external resource, ask if there is a way to get a stream of data over the contents of that resource. The chances are that you may find a function for that within the JDK or a third-party library. Once we get a stream, we can use the highly effective functional operators like `filter()`, `map()`, etc. to fluently iterate over the collection of data that's part of the resource. From d77b2df5ce8496fd31998a4da46e1e97daa11c47 Mon Sep 17 00:00:00 2001 From: "venkats@agiledeveloper.com" Date: Mon, 22 Apr 2024 16:37:03 +0530 Subject: [PATCH 8/8] changed the wording --- .../05_converting_to_streams.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md index d09a5f6..6cf4944 100644 --- a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md @@ -1,6 +1,6 @@ --- id: refactoring.to.streams -title: Converting to Streams +title: Converting Data Sources to Streams slug: learn/refactoring-to-functional-style/convertingtostreams type: tutorial-group group: refactoring-to-functional-style @@ -11,7 +11,7 @@ toc: - Thinking in Streams {streams} - From Imperative to Functional Style {imperativetofunctional} - Mappings {mappings} -description: "Converting to use the Streams API." +description: "Converting Data Sources to Streams." last_update: 2024-03-25 author: ["VenkatSubramaniam"] ---