In Java 8, Streams provide a powerful way to process sequences of elements (such as collections or arrays) in a functional style. They allow you to process data in a concise and declarative manner, which improves code readability and maintainability. Streams can be created from collections, arrays, or individual values.
In Java 8, Streams provide a powerful way to process sequences of elements (such as collections or arrays) in a functional style. They allow you to process data in a concise and declarative manner, which improves code readability and maintainability. Streams can be created from collections, arrays, or individual values.
A Collection (e.g., List
, Set
) is one of the most common sources of data from which you can create a stream.
stream()
method: Collections in Java 8 implement the Stream
interface, and you can create a stream using the stream()
method.import java.util.List;
import java.util.Arrays;
public class StreamFromCollection {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// Create a stream from a collection
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println); // Output: Alice
}
}
Explanation: Here, we use the stream()
method to create a stream from the List
. We then use operations like filter()
to process the elements in the stream. The forEach()
terminal operation prints the result.
Arrays are another common data source for streams in Java. You can create a stream from an array using the Arrays.stream()
method or the stream()
method (if working with arrays in general).
Arrays.stream()
:import java.util.Arrays;
public class StreamFromArray {
public static void main(String[] args) {
String[] fruits = {"Apple", "Banana", "Mango", "Pineapple"};
// Create a stream from an array
Arrays.stream(fruits)
.filter(fruit -> fruit.length() > 5)
.forEach(System.out::println); // Output: Banana, Mango, Pineapple
}
}
Explanation: The Arrays.stream()
method creates a stream from the array fruits
. The stream operations such as filter()
and forEach()
are used to process the elements.
In addition to collections and arrays, you can create streams from individual values using the Stream.of()
method. This method is useful when you want to create a stream from a small number of elements.
Stream.of()
:
import java.util.stream.Stream;
public class StreamFromValues {
public static void main(String[] args) {
// Create a stream from individual values
Stream<String> stream = Stream.of("John", "Jane", "Mike", "Anna");
// Use operations on the stream
stream.filter(name -> name.length() > 3)
.forEach(System.out::println); // Output: John, Jane, Mike
}
}
Explanation: Here, we use Stream.of()
to create a stream from individual values. We apply the filter()
operation to select names with more than 3 characters, and forEach()
is used to print the filtered results.
For arrays of primitive types (e.g., int[]
, double[]
, etc.), Java provides specialized stream types: IntStream
, LongStream
, and DoubleStream
. These allow efficient handling of primitive data types without the overhead of autoboxing.
Using Arrays.stream()
for primitive arrays:
Example:
import java.util.Arrays;
public class StreamFromPrimitiveArray {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
// Create an IntStream from an int array
Arrays.stream(numbers)
.filter(num -> num % 2 == 0)
.forEach(System.out::println); // Output: 2, 4
}
}
Explanation: We create an IntStream
from the int[]
array using Arrays.stream()
. The filter()
method is applied to select even numbers, and forEach()
prints the results.
Source | Method to Create Stream | Example |
---|---|---|
Collection | stream() method | List<String> list = ...; list.stream() |
Array | Arrays.stream() method | int[] arr = ...; Arrays.stream(arr) |
Individual Values | Stream.of() method | Stream<String> stream = Stream.of("a", "b", "c") |
Primitive Arrays | Arrays.stream() for primitive types (int[] , double[] ) | int[] numbers = {1, 2, 3}; Arrays.stream(numbers) |
filter()
, map()
, reduce()
, and more.
In a gaming project, suppose you have a list of players, and you want to filter out all players whose scores are below a certain threshold and then sort the remaining players by their scores.
import java.util.List;
import java.util.Arrays;
import java.util.stream.Collectors;
class Player {
String name;
int score;
Player(String name, int score) {
this.name = name;
this.score = score;
}
public String toString() {
return name + ": " + score;
}
}
public class StreamExample {
public static void main(String[] args) {
List<Player> players = Arrays.asList(
new Player("Alice", 1500),
new Player("Bob", 800),
new Player("Charlie", 1200),
new Player("David", 2000)
);
// Use streams to filter and sort
List<Player> filteredPlayers = players.stream()
.filter(player -> player.score > 1000)
.sorted((p1, p2) -> Integer.compare(p2.score, p1.score)) // Sort by score descending
.collect(Collectors.toList());
filteredPlayers.forEach(System.out::println); // Output: David: 2000, Alice: 1500, Charlie: 1200
}
}
In Java 8, the Stream API provides a set of operations to process sequences of elements in a functional style. The Stream API can be categorized into two main types based on how data flows and is processed: Streams and Primitive Streams.
Here’s a breakdown of the types of Stream API in Java:
Stream<T>: The general stream type for working with objects (non-primitive types).
String
, Integer
, Custom objects
).Example:
import java.util.List;
import java.util.Arrays;
public class ReferenceStreamExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
names.stream()
.filter(name -> name.length() > 3)
.forEach(System.out::println); // Output: Alice, Charlie, David
}
}
Features:
map()
, filter()
, forEach()
, reduce()
.
IntStream, LongStream, and DoubleStream: These are specialized streams for handling primitive types (int
, long
, and double
), providing better performance by avoiding autoboxing (the conversion between primitives and objects).
int
values.long
values.double
values.Why Primitive Streams?
Integer
, Long
, and Double
.sum()
, average()
, and min()
, which are optimized for primitive data types.Example:
import java.util.stream.IntStream;
public class PrimitiveStreamExample {
public static void main(String[] args) {
IntStream.range(1, 6)
.filter(n -> n % 2 == 0)
.forEach(System.out::println); // Output: 2, 4
}
}
Features:
sum()
, average()
, max()
, and min()
.IntStream
but for long
and double
data types, respectively.mapToObj()
, flatMapToInt()
, etc., to transform between primitive and object streams.
Regardless of whether you're using reference streams or primitive streams, the stream operations can be categorized into:
map()
, filter()
, distinct()
). They are lazy, meaning they are not executed until a terminal operation is invoked.collect()
, forEach()
, reduce()
). A terminal operation marks the end of the stream pipeline.anyMatch()
, allMatch()
, findFirst()
, etc.
Stream Type | For Primitive Types | For Objects/Reference Types |
---|---|---|
Stream | No | Yes |
IntStream | Yes | No |
LongStream | Yes | No |
DoubleStream | Yes | No |
Types Of Stream API In Java 8
Intermediate operations are operations that transform a stream into another stream. These operations are lazy, meaning they do not perform any processing until a terminal operation is invoked. When you perform intermediate operations, you create a stream pipeline that is executed when a terminal operation is triggered. Since they are lazy, intermediate operations can be chained together and only evaluated when necessary, which can help improve performance by avoiding unnecessary computations.
Here are the key Intermediate Operations available in Java 8 Streams:
package com.niteshsynergy.java8.stream;
import com.niteshsynergy.Emp;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Demo01IntermediateOperations {
public static void main(String[] args) {
//filter() – Filters elements based on a condition.
List<String> list1 = Arrays.asList("apple", "banana", "carrot","Mango","Anans","Appu","Awla");
list1.stream()
.filter(s->s.startsWith("a"))
.forEach(System.out::println);
//map() – Transforms each element of the stream.
list1.stream()
.map(s->s.replaceAll("a","b"))
.forEach(System.out::println);
//distinct() – Removes duplicates.
List<Integer> list2 = Arrays.asList(1,2,3,4,5,6,7,8,9);
list2.stream()
.distinct()
.forEach(System.out::println);
//sorted() – Sorts the stream.
List<Integer> list3 = Arrays.asList(1,2,3,4,5,6,7,8,9);
list3.stream()
.sorted()
.forEach(System.out::println);
//limit() – Limits the number of elements in the stream.
List<Integer> list4 = Arrays.asList(1,2,3,4,5,6,7,8,9);
list4.stream()
.limit(3)
.forEach(System.out::println);
//skip() – Skips the first n elements.
List<Integer> list5 = Arrays.asList(1,2,3,4,5,6,7,8,9);
list5.stream()
.skip(3)
.forEach(System.out::println);
//Below is an example using an Emp class to demonstrate various intermediate operations in the Stream API.
// List of employees
List<Emp> employees = Arrays.asList(
new Emp(1, "Alice", 75000),
new Emp(2, "Bob", 55000),
new Emp(3, "Charlie", 70000),
new Emp(4, "David", 85000),
new Emp(5, "Eva", 65000),
new Emp(6, "Frank", 70000)
);
// 1. filter - Get employees with salary greater than 60000
System.out.println("Filtered Employees (Salary > 60000):");
employees.stream()
.filter(e->e.getId()>60000)
.forEach(System.out::println);
// 2. map - Get employee names in uppercase
System.out.println("\nEmployee Names in Uppercase:");
employees.stream()
.map(e-> e.getName().toUpperCase())
.forEach(System.out::println);
// 3. sorted - Sort employees by salary
System.out.println("\nEmployees Sorted by Salary:");
employees.stream()
.sorted(Comparator.comparingDouble(Emp::getSalary))
.forEach(System.out::println);
// 4. distinct - Get distinct salary values
System.out.println("\nDistinct Salaries:");
employees.stream()
.map(emp->emp.getSalary())
.distinct()
.forEach(System.out::println);
// 5. flatMap - Flatten a list of employees with additional employee objects for illustration
System.out.println("\nAll Employees (After flatMap):");
List<Emp> additionalEmployees = Arrays.asList(
new Emp(7, "George", 70000),
new Emp(8, "Helen", 72000)
);
List<Emp> allEmployees = Stream.concat(employees.stream(),
additionalEmployees.stream())
.collect(Collectors.toList());
// 6. limit - Get first 3 employees by salary
System.out.println("\nTop 3 Employees by Salary:");
employees.stream()
.sorted(Comparator.comparingDouble(Emp::getSalary).reversed())
.limit(3)
.forEach(System.out::println);
// 7. skip - Skip the first 2 employees and get the rest
System.out.println("\nEmployees after skipping the first 2:");
employees.stream()
.skip(2)
.forEach(System.out::println);
//To create a Map where the key is the empId and the value is the same Emp object, we can utilize a HashMap or TreeMap in Java
// Create a few Emp objects
System.out.println("\nTo create a Map where the key is the empId and the value is the same Emp object, we can utilize a HashMap or TreeMap in Java");
Emp emp1 = new Emp(1, "Alice", 75000);
Emp emp2 = new Emp(2, "Bob", 55000);
Emp emp3 = new Emp(3, "Charlie", 70000);
Emp emp4 = new Emp(4, "David", 85000);
// Creating a Map with empId as key and Emp object as value
Map<Integer, Emp> empMap = new HashMap<>();
empMap.put(emp1.getId(), emp1);
empMap.put(emp2.getId(), emp2);
empMap.put(emp3.getId(), emp3);
empMap.put(emp4.getId(), emp4);
// Display all entries in the Map
System.out.println("Emp Map:");
empMap.forEach((key, value) -> System.out.println("empId: " + key + " => " + value));
// Example: Retrieve an Emp by empId (key)
Emp retrievedEmp = empMap.get(3);
System.out.println("\nRetrieved Employee with empId 3: " + retrievedEmp);
// Example: Check if an Emp exists in the map
boolean containsEmp = empMap.containsKey(2);
System.out.println("\nDoes the map contain empId 2? " + containsEmp);
// Example: Remove an Emp by empId
empMap.remove(4);
System.out.println("\nMap after removing empId 4:");
empMap.forEach((key, value) -> System.out.println("empId: " + key + " => " + value));
// Create a few Emp objects
Emp emp11 = new Emp(1, "Alice", 75000);
Emp emp21 = new Emp(2, "Bob", 55000);
Emp emp31 = new Emp(3, "Charlie", 70000);
Emp emp41 = new Emp(4, "David", 85000);
// Create a list of Emp objects
List<Emp> empList1 = Arrays.asList(emp11, emp21, emp31, emp41);
// Use filter, map, and Collectors.toMap
Map<Integer, Emp> empMap1 = empList1.stream()
.filter(emp111 -> emp1.getSalary() > 60000) // Filter employees with salary > 60000
.map(emp111 -> new Emp(emp1.getId(), emp1.getName(), emp1.getSalary())) // Map operation (can modify objects if needed)
.collect(Collectors.toMap(Emp::getId, emp111 -> emp1)); // Collect into a Map with empId as the key
// Display the resulting map
System.out.println("Filtered and Mapped Emp Map:");
empMap1.forEach((key1, value1) -> System.out.println("empId: " + key1 + " => " + value1));
// Example: Retrieve an Emp by empId (key)
Emp retrievedEmp1 = empMap1.get(3);
System.out.println("\nRetrieved Employee with empId 3: " + retrievedEmp1);
// Print different way
empList1.stream()
.filter(pp-> pp.getSalary()>40000)
.map(ee-> new Emp(ee.getId(), ee.getName(), ee.getSalary()))
.collect(Collectors.toMap(Emp::getId, ee1 -> ee1))
.forEach((key, value) -> System.out.println("empId: " + key + " => " + value));
}
}
Terminal Operations are the operations that trigger the processing of the stream and produce a result, a side-effect, or a final computation. Once a terminal operation is invoked, the stream is consumed and can no longer be used. These operations mark the end of the stream pipeline and cause the intermediate operations to be evaluated.
Here’s a breakdown of some key Terminal Operations in Java 8 Streams:
package com.niteshsynergy.java8.stream;
import com.niteshsynergy.Emp;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.RandomAccess;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Demo03TerminalOperations {
public static void main(String[] args) {
//collect() – Collects elements into a collection like a List, Set, or Map.
List<String> list = Arrays.asList("apple", "banana", "cherry");
List<String> result = list.stream()
.collect(Collectors.toList());
System.out.println(result); // Output: [apple, banana, cherry]
list.stream()
.collect(Collectors.toMap(Function.identity(), l->list))
.forEach((key, value) -> System.out.println("Identity: " + key + " => " + value));
//forEach() – Performs an action for each element in the stream.
list.stream()
.forEach(System.out::println);
//reduce() – Reduces the stream to a single value (e.g., sum, product)
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println(" integers.stream()\n" +
" .reduce(0, Integer::sum);"+ integers.stream()
.reduce(0, Integer::sum));
System.out.println(" integers.stream()\n" +
" .reduce(10, Integer::sum)"+ integers.stream()
.reduce(10, Integer::sum));
System.out.println(" integers.stream()\n" +
" .reduce(10, Integer::max)"+ integers.stream()
.reduce(10, Integer::max));
System.out.println("Count of integers: " + integers.stream().count());
// real time example
// Create a few Emp objects
Emp emp11 = new Emp(1, "Alice", 75000);
Emp emp21 = new Emp(2, "Bob", 55000);
Emp emp31 = new Emp(3, "Charlie", 70000);
Emp emp41 = new Emp(4, "David", 85000);
List<Emp> empList1 = Arrays.asList(emp11, emp21, emp31, emp41);
empList1.stream()
.filter(emp -> emp.getSalary() > 70000);
//anyMatch() / allMatch() / noneMatch() – Tests if any, all, or none of the elements match a given predicate.
List<Integer> listInt = Arrays.asList(1, 2, 3, 4, 5);
boolean anyMatch = listInt.stream().anyMatch(x -> x>4);
boolean allMatch = listInt.stream().allMatch(x -> x>0);
boolean noneMatch = listInt.stream().noneMatch(x -> x<0);
System.out.println(anyMatch); // Output: true
System.out.println(allMatch); // Output: true
System.out.println(noneMatch); // Output: true
}
}
Short-circuiting operations in Java 8 Streams are special types of terminal operations that terminate the stream processing early. These operations evaluate the stream lazily, which means that they stop processing elements as soon as the condition is met. This can result in performance optimization, especially when working with large datasets, as it avoids unnecessary processing.
Short-circuiting operations are used when you don’t need to process the entire stream, but only a part of it. They "short-circuit" the evaluation once a result is determined.
package com.niteshsynergy.java8.stream;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class Demo04ShortCircuitingOperations {
//anyMatch() – Returns true if any element in the stream matches the given predicate (stops processing as soon as the match is found).
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
boolean anyMatch = list.stream().anyMatch(x -> x > 4);
System.out.println(anyMatch); // Output: true
boolean allMatch = list.stream().allMatch(x -> x > 0);
System.out.println(allMatch); // Output: tr
boolean noneMatch = list.stream().noneMatch(x -> x < 0);
System.out.println(noneMatch); // Output: true
Optional<Integer> first = list.stream().findFirst();
first.ifPresent(System.out::println); // Output: 1
Optional<Integer> any = list.stream().findAny();
any.ifPresent(System.out::println); // Output: 1 (or any element)
}
}