Java Topic

Java Stream API

The Java Stream API, introduced in Java 8, provides a powerful way to process sequences of elements (e.g., collections) in a functional programming style. Streams allow for operations like filtering, mapping, and reducing, which can be performed in a declarative manner.

1. Creating Streams

Streams can be created from various sources, such as collections, arrays, or I/O channels. Common ways to create streams include:

  • From a Collection: Use the stream() method on a collection (e.g., List or Set).
  • From an Array: Use the Arrays.stream() method.
  • Using Stream.of: Create a stream from individual values.

Creating a Stream from a Collection

import java.util.ArrayList;
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        list.stream()
            .forEach(System.out::println);
    }
}

This example demonstrates creating a stream from a list and printing each element.

2. Stream Operations

Streams support several types of operations, which can be categorized into intermediate and terminal operations:

  • Intermediate Operations: These operations transform the stream into another stream and are lazy (i.e., they are not executed until a terminal operation is performed). Examples include filter(), map(), and sorted().
  • Terminal Operations: These operations produce a result or a side-effect and terminate the stream. Examples include collect(), forEach(), and reduce().

Intermediate Operations

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class IntermediateOperations {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

        List<Integer> evenNumbers = numbers.stream()
            .filter(n -> n % 2 == 0)
            .collect(Collectors.toList());

        System.out.println("Even Numbers: " + evenNumbers);
    }
}

This example demonstrates using filter() to keep only even numbers from a list.

Terminal Operations

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class TerminalOperations {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        int sum = numbers.stream()
            .reduce(0, (a, b) -> a + b);

        System.out.println("Sum: " + sum);

        Optional<Integer> max = numbers.stream()
            .max((a, b) -> a - b);

        System.out.println("Max: " + max.orElse(0));
    }
}

This example demonstrates using reduce() to compute the sum of elements and max() to find the maximum value in a stream.

3. Parallel Streams

Streams can be processed in parallel to take advantage of multi-core processors. You can convert a stream to a parallel stream using the parallelStream() method or by calling parallel() on an existing stream:

import java.util.List;
import java.util.Arrays;

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        numbers.parallelStream()
            .forEach(n -> System.out.println("Processing number: " + n + " by " + Thread.currentThread().getName()));
    }
}

This example demonstrates using parallel streams to process elements concurrently, with each element being processed by a different thread.

4. Collectors

The Collectors class provides various implementations for accumulating the elements of a stream into collections or other forms. Some common collectors include:

  • Collectors.toList(): Collects the elements into a List.
  • Collectors.toSet(): Collects the elements into a Set.
  • Collectors.joining(): Concatenates the elements into a single String.
import java.util.List;
import java.util.stream.Collectors;
import java.util.Arrays;

public class CollectorsExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("Java", "Streams", "Example");

        String result = words.stream()
            .collect(Collectors.joining(", "));

        System.out.println("Result: " + result);
    }
}

This example demonstrates using Collectors.joining() to concatenate the elements of a stream into a single string with commas separating the elements.

Java I/O Streams

Java I/O (Input/Output) Streams are used to read data from and write data to various sources, such as files, network connections, or memory buffers. The java.io package provides a comprehensive set of classes and interfaces for I/O operations.

1. Overview of I/O Streams

In Java, I/O streams are divided into two main types:

  • Byte Streams: Handle I/O of raw binary data. The primary classes are InputStream and OutputStream.
  • Character Streams: Handle I/O of character data, automatically handling character encoding. The primary classes are Reader and Writer.

2. Byte Streams

Byte streams are used for reading and writing binary data, such as images or audio files.

Example: FileInputStream and FileOutputStream

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ByteStreamExample {
    public static void main(String[] args) {
        try (FileInputStream in = new FileInputStream("input.txt");
             FileOutputStream out = new FileOutputStream("output.txt")) {
            int byteData;
            while ((byteData = in.read()) != -1) {
                out.write(byteData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

This example demonstrates how to use FileInputStream and FileOutputStream to copy data from one file to another.

3. Character Streams

Character streams are used for reading and writing text data, such as strings or lines of text.

Example: FileReader and FileWriter

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CharacterStreamExample {
    public static void main(String[] args) {
        try (FileReader reader = new FileReader("input.txt");
             FileWriter writer = new FileWriter("output.txt")) {
            int charData;
            while ((charData = reader.read()) != -1) {
                writer.write(charData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

This example demonstrates how to use FileReader and FileWriter to copy text from one file to another.

4. Buffered Streams

Buffered streams use an internal buffer to read and write data more efficiently by reducing the number of I/O operations.

Example: BufferedReader and BufferedWriter

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedStreamExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
             BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

This example demonstrates how to use BufferedReader and BufferedWriter to read and write text line by line.

5. Data Streams

Data streams allow you to read and write Java primitive data types in a portable way.

Example: DataInputStream and DataOutputStream

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataStreamExample {
    public static void main(String[] args) {
        try (DataOutputStream out = new DataOutputStream(new FileOutputStream("data.dat"));
             DataInputStream in = new DataInputStream(new FileInputStream("data.dat"))) {
            out.writeInt(123);
            out.writeDouble(45.67);

            int intValue = in.readInt();
            double doubleValue = in.readDouble();
            System.out.println("Read values: " + intValue + ", " + doubleValue);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

This example demonstrates how to use DataOutputStream and DataInputStream to write and read primitive data types to and from a file.

Java Stream map vs flatMap

In Java, the Stream API provides powerful methods for transforming and processing collections of data. Two commonly used methods are map and flatMap. While they may seem similar, they serve different purposes and have distinct behaviors.

1. map Method

The map method is used to transform each element in a stream into another element by applying a given function. It returns a new stream consisting of the results of applying the function to the elements of the original stream.

Example:

import java.util.List;
import java.util.stream.Collectors;

public class MapExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie");
        List<Integer> nameLengths = names.stream()
                .map(String::length)
                .collect(Collectors.toList());
        System.out.println(nameLengths); // Output: [5, 3, 7]
    }
}

In this example, the map method is used to transform a list of names into a list of their lengths.

2. flatMap Method

The flatMap method is used to flatten a stream of streams into a single stream. It applies a function to each element that produces a stream of elements and then flattens these into a single stream of elements.

Example:

import java.util.List;
import java.util.stream.Collectors;

public class FlatMapExample {
    public static void main(String[] args) {
        List<List<String>> nestedLists = List.of(
                List.of("A", "B"),
                List.of("C", "D"),
                List.of("E", "F")
        );
        List<String> flatList = nestedLists.stream()
                .flatMap(List::stream)
                .collect(Collectors.toList());
        System.out.println(flatList); // Output: [A, B, C, D, E, F]
    }
}

In this example, the flatMap method is used to flatten a list of lists into a single list containing all the elements.

3. Key Differences

The key difference between map and flatMap is:

  • map: Transforms each element into another form and returns a stream of those transformed elements.
  • flatMap: Transforms each element into a stream and flattens the result into a single stream, effectively merging the nested streams.

4. When to Use

Use map when you want to transform each element in a stream to another object of the same or different type. Use flatMap when you have a stream of collections or streams and want to flatten them into a single stream.

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?