Java code style stream

Java 8 tutorial: Master stream API and beyond

Java 8 was released in 2014, bringing with it a heap of new features now praised as essential by modern developers, such as the lambda expression, concurrency API improvements, Functional Interfaces, and improvements to bulk data handling. While many years have passed since then, Java 8 remains the most used version of Java, with over 60% of professional developers in 2020 reporting Java 8 as their office standard version.

Percentage of developers reporting given Java version as their main application:

As a result, regardless if you’re shifting jobs or just starting, proficiency in Java 8 is an essential skill to have in today’s tech world. If you’re just switching Java versions now, you may feel like you’re a bit late to the party, but worry not! The features of Java 8 are easier to pick up than you’d think, and today, I’ll get you familiar with one of the most important Java 8 updates: Stream API.

Today, we’ll go over:

Get a leg up on Java 8 with hands-on experience

Learn all of the powerful Java 8 features hands-on and at your own pace, without having to restart from scratch.

What is a Stream in Java 8?

In the words of the all-powerful Oracle Interface Package Summary, a stream is “a sequence of elements supporting sequential and parallel aggregate operations”. While a great tongue-twister, it’s not the most digestible definition. Allow me to translate.

Читайте также:  Initialize list to null java

Streams are an abstract layer added in Java 8 that allows developers to easily manipulate collections of objects or primitives. It is not a data structure as it does not store data; rather it serves as a transformative medium from the data source to its destination.

Aggregate operations, sometimes called stream operations, are simply operations unique to streams that we can use to transform some (or all) of the stream at once. We’ll see examples of these later on.

Finally, sequential vs parallel refers to the ability to implement concurrency when completing stream operations. The operation can either be applied one at a time by a single thread, or it can be split among multiple threads each applying the operation concurrently.

The Stream is often visualized as a pipeline because it acts as an intermediate step between the source of data, transforms the data in some way, then outputs it in a new form downstream. We’ll revisit this metaphor when we explore Intermediate and Terminal Operations below.

Using the Stream API requires a fundamentally different style of coding than traditional coding — while most coding in Java is written in an imperative style, where the developer instructs what to do and how to complete it, operations from the Stream API require a declarative or event-driven style similar to operations in SQL.

Источник

Use Java Streams to Write Clean Java Code

Traditionally, Java code is verbose, and syntactically demanding. Java requires several lines of code to achieve what other languages do with a few lines of code. The nature of Java as object oriented often leads to over-specification in implementation, increasing complexity of code, and overhead in testing. The Java 8 API has introduced the Stream Interface to tackle this syntactic setback that is long overdue. Java Streams provides an abstraction on Collections and Arrays so that developers can side-step loops and long-winding operations, and simply ask questions on Collections. E.g findFirst(), anyMatch(), sorted().

We will go over how to utilize this powerful new addition to achieve concision of code, without compromising expressiveness.

Imperative vs Declarative Programming

Java Streams introduces declarative style programming, as opposed to the imperative nature of programming in Java prior to streams. Imperative programming involves explicitly specifying all the necessary steps needed to achieve a result. Whereas Declarative style simply leaves out the details of implementation and asks for the results we want. In short, imperative concerns itself with the how of things, whereas Declarative focuses on the what of things. A simple illustration to achieve the count of elements in a list , using both imperative and declarative approach is shown below.

Declarative approach utilizes abstraction which when combined with good variable naming convention leads to cleaner code, better readability, and much simpler code. Furthermore, since code you do not write means code you do not test, declarative approach reduces overhead in testing.

Java Streams has also made functional programming in Java less tedious prior to Java 8. Functional programming is a programming paradigm where the output of the function depends only on its input. A huge advantage of functional programming is its ability to achieve immutability of state and pure functions, which can often reduce bugs and side effects that makes testability of code difficult.

List list = Arrays.asList(new Integer[]); //Imperative style Integer count = 0; for(int ele : list )< count++; >; System.out.println("total count is : " + count); // Declarative style using streams Integer sum = list.stream().count()

Using Java Streams

Streams are essentially wrappers to Collections and Lists, which declaratively describes their source and the computational operations that can be performed in aggregate on their source. Stream operations are composable, and consist of a source (can be a collection, list etc), some intermediate operations (which converts a stream into stream), and a terminal operation which consumes the stream to produce a result. The entire stream pipeline is evaluated only when a terminal operation is evoked on the pipeline.

Creating a Stream

In order to set up a stream pipeline, you must first create a stream. There are a few ways to create a stream depending on the type of the source:

(i) Create streams from Java Collections with the stream() method of the collection:

List list = Arrays.asList(new Integer[]); Stream stream = list.stream()

(ii) Also, create streams from an array like so:

Import Java.util.stream.* . Int[] arr= new Integer[]; Stream stream = Stream.of(arr);

(iii) For primitive types java offers IntStream, DoubleStream, and LongStream:

Import Java.util.stream.* . IntStream stream = DoubleStream.of(8, 4, 3); DoubleStream stream = DoubleStream.of(8.2, 4.5, 2.3); LongStream stream = LongStream.of(8L);

(iv) Generally, you can create streams from individual values as well:

Import Java.util.stream.* . Stream = Stream.of(“Hello”, “Hi”, “Hey”);

Applying Intermediate Operations

After creating a stream, apply one or more intermediate functions chained together to transform the stream into a new stream. These intermediate operations are lazy. Therefore, they are not evaluated until a terminal operation is applied. Examples of intermediate operations include: distinct, sorted, limit, peek, map, filter, skip, etc.

Import Java.util.stream.* . Int[] arr= new Integer[]; Stream stream = Stream.of(arr); Stream intermediateResult = stream .filter(ele -> ele > 2) .sorted()

(ii) The filter is an intermediate operation that allows us to filter elements of a stream given a Predicate , which is expressed as a lambda function. Lambda functions enable us to express single methods in Java more compactly. The Functional Programming paradigm utilizes this concept. Most Java Streams operations expect a Predicate expressed in the form of lambda function. Refer to Java Docs for comprehensive notes on lambda functions.

Applying Terminal Operations

Terminal operations consume the stream pipeline, and turn the pipeline into a result or side-effect: essentially into something else other than a Stream. Computation on the source is only performed once a terminal operation is invoked on the pipeline. Examples include count, forEach, sorted, toArray, reduce, collect, min, max, etc.

(i) To consume a stream pipeline as an ArrayList:

Import Java.util.stream.* . Int[] arr= new Integer[]; Stream stream = Stream.of(arr); ArrayList arrList = stream .filter(ele -> ele > 2) .sorted() .collect(Collectors..toCollection(ArrayList::new))

Ease of Traversing Collections and Data Searching With Stream

Streams operations makes it easy to work with Collections in java, to achieve data traversal and searching. We will illustrate this with two examples:

(i) Looping through a list to print elements:

List greetings = Arrays.asList(“Hello”, “Hi”, “Hey”); for(int i = 0; i

(b) Using streams with anonymous methods:

greetings.stream().forEach(new Consumer() < public void accept(String t) < System.out.println(t); >>)

(c) Using streams with lambda functions:

List greetings = Arrays.asList(“Hello”, “Hi”, “Hey”); greetings.stream().forEach(System.out::println)

(ii) Checking if list contains a string of a certain size with streams:

List greetings = Arrays.asList(“Hello”, “Hi”, “Hey”); System.out.println( greetings.stream().anyMatch(ele -> ele.length == 5) ) // true

Conclusion

Java Streams provides a powerful yet intuitive way to express your code. This tutorial demonstrated a few simple ways to utilize streams.

Samuel is a software developer at an investment banking firm. He works on developing Rest services with Java, and frontend with the popular React framework.

Источник

Оцените статью