Functional Interfaces
Functional interfaces have a single abstract method and are the basis of lambda expressions. Key interfaces include:
- Supplier: Provides a result without input.
- Consumer: Accepts a single input and performs an action.
- Function: Accepts input and returns a result.
- Predicate: Represents a boolean condition.
Example:
Supplier<String> supplier = () -> "Hello World";System.out.println(supplier.get()); Predicate<Integer>isEven = n -> n % 2 == 0; System.out.println(isEven.test(4)); // true Consumer<String> print = s -> System.out.println("Input: " + s); print.accept("Hello, World!"); // Output: Input: Hello, World! Function<Integer, String> intToString = n -> "The number is: " + n; String result = intToString.apply(10); // Result: "The number is: 10" System.out.println(result);
Stream Operations
Stream operations are divided into:
- Terminal Operations: End the stream pipeline, e.g.,
collect()
,forEach()
,reduce()
. - Intermediate Operations: Transform the stream, e.g.,
filter()
,map()
,flatMap()
.
Example:
Listnames = Arrays.asList("John", "Alice", "Bob"); List upperNames = names.stream() .filter(name -> name.length() > 3) .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println(upperNames); // [JOHN, ALICE]
Intermediate Operations
Intermediate operations transform a stream and are lazy (executed only when terminal operations are invoked).
- filter(): Filters elements based on a condition.
- map(): Transforms each element.
- flatMap(): Flattens nested structures.
Example:
Listnames = Arrays.asList("John", "Alice", "Bob"); names.stream() .filter(name -> name.startsWith("A")) .map(String::toUpperCase) .forEach(System.out::println); // ALICE //flatMap List<List<String>> nestedList = Arrays.asList( Arrays.asList("A", "B"), Arrays.asList("C", "D"), Arrays.asList("E", "F") ); List<String> flattenedList = nestedList.stream() .flatMap(List::stream) // Flattens each inner list into a single stream .collect(Collectors.toList()); System.out.println(flattenedList); // Output: [A, B, C, D, E, F]
Primitive Streams
Specialized streams for primitives avoid boxing overhead:
- DoubleStream: For
double
values. - IntStream: For
int
values. - LongStream: For
long
values.
Example:
IntStream.range(1, 5).forEach(System.out::println); // 1 2 3 4
Primitive streams are specialized streams designed for primitive types to avoid the overhead of boxing and unboxing. This improves performance when working with large datasets of primitive values by reducing memory usage and computation time. They also provide specialized methods like sum(), average(), and min() for convenience.
Optional
Optional
helps avoid NullPointerException
by representing a value that may or may not be present.
Example:
Optionaloptional = Optional.ofNullable(null); System.out.println(optional.orElse("Default Value")); // Default Value
Searching Streams
Streams provide methods to search elements based on conditions:
- findFirst(): Retrieves the first element in the stream.
- findAny(): Retrieves any element, useful for parallel streams.
- anyMatch(): Checks if any element matches a condition.
- allMatch(): Checks if all elements match a condition.
- noneMatch(): Checks if no elements match a condition.
Example:
Listnumbers = Arrays.asList(1, 2, 3, 4); boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0); // true boolean allPositive = numbers.stream().allMatch(n -> n > 0); // true
Sorting Streams
Streams can be sorted using the sorted()
method. Sorting can be in natural order or with a custom Comparator
.
- Natural Order: Uses the elements' natural ordering.
- Custom Comparator: Defines custom sorting logic.
Example:
// Natural order sorting Listnames = Arrays.asList("John", "Alice", "Bob"); names.stream().sorted().forEach(System.out::println); // Custom comparator names.stream() .sorted((a, b) -> b.compareTo(a)) // Descending order .forEach(System.out::println);
Method References
Method references simplify lambda expressions by directly referring to a method by its name. There are four types:
- Static Method Reference:
ClassName::staticMethod
- Instance Method Reference:
object::instanceMethod
- Constructor Reference:
ClassName::new
- Reference to an Instance Method of an Arbitrary Object:
ClassName::instanceMethod
Example:
// Static method reference Listnumbers = Arrays.asList(1, 2, 3, 4); numbers.forEach(System.out::println); // Constructor reference Supplier > listSupplier = ArrayList::new; List
list = listSupplier.get();
Mutable vs Immutable Objects in Java Streams
When using Java Streams, handling mutable and immutable objects in operations like
forEach
leads to different outcomes. Mutable objects (e.g., StringBuilder
) allow in-place
modifications, which can introduce side effects. For example:
List<StringBuilder> list = Arrays.asList(new StringBuilder("one"), new StringBuilder("two"));
list.stream().forEach(sb -> sb.append(" updated")); // Modifies the original objects
Here, the original StringBuilder
instances are altered. In contrast, immutable objects (e.g.,
String
) cannot be changed directly, ensuring thread safety and preventing side effects:
List<String> list = Arrays.asList("one", "two");
list.stream().map(String::toUpperCase).forEach(System.out::println); // Creates new objects
This creates new values while keeping the original list intact, promoting a functional approach.
0 Comments