A Lambda Expression is a concise way to represent an anonymous function in Java. It provides a clear and concise syntax for implementing functional interfaces (interfaces with a single abstract method). Lambda expressions are primarily used to enable functional programming and simplify the development of inline implementations.
Syntax:(parameters) -> { body }
Lambda expressions can take different numbers of parameters based on the use case. The syntax adjusts accordingly for zero, single, or multiple parameters.
Types of Lambda Expression Parameters:Zero Parameter:
Syntax:() -> System.out.println("Zero parameter lambda");
Runnable r = () -> { System.out.println("No parameters in this lambda."); }; r.run();
Single Parameter:
Syntax :(p) -> System.out.println("One parameter: " + p);
import java.util.ArrayList; class Test { public static void main(String args[]) { ArrayList<Integer> arrL = new ArrayList<Integer>(); arrL.add(1); arrL.add(2); arrL.add(3); arrL.add(4); // Using lambda expression to print all elements arrL.forEach(n -> System.out.println(n)); } }
Multiple Parameters:
java.util.function.BiFunctionKey Points:add = (a, b) -> { return a + b; }; System.out.println("Sum: " + add.apply(5, 10));
Stream API in Java 8 is a powerful tool introduced to process collections of data in a functional and declarative manner. It allows developers to perform operations like filtering, mapping, and reducing on data with minimal boilerplate code. Streams enable a more expressive way to handle data transformations and aggregations.
Key Features:import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamAPIExample { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve"); // Filtering and Mapping using Stream API List<String> filteredNames = names.stream() .filter(name -> name.startsWith("A")) .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println(filteredNames); // Output: [ALICE] } }Types of Operations:
A Functional Interface in Java 8 is an interface that contains exactly one abstract method. It can have any number of default or static methods, but it must have only one abstract method. Functional interfaces are the foundation of lambda expressions in Java, enabling functional programming. Runnable, ActionListener, and Comparator are common examples of Java functional interfaces.
Defining and Using a Functional Interface:
@FunctionalInterface interface MyFunctionalInterface { void displayMessage(String message); } public class FunctionalInterfaceExample { public static void main(String[] args) { // Using a Lambda Expression MyFunctionalInterface myFunction = (message) -> System.out.println("Message: " + message); // Invoking the abstract method myFunction.displayMessage("Hello, Functional Interface!"); } }Built-in Functional Interfaces:
Both map and flatMap are functions in the Stream API that allow transformation of elements in a stream, but they differ in how they handle nested structures.
1. mapThe map function is used when you want to apply a transformation to each element in the stream and get a single, transformed element for each input element. It produces a stream of transformed elements.
Example:
List<String> words = Arrays.asList("apple", "banana", "cherry"); List<Integer> wordLengths = words.stream() .map(String::length) .collect(Collectors.toList()); System.out.println(wordLengths); // Output: [5, 6, 6]
In this case, each word is transformed into its length using map.
2. flatMapThe flatMap function is used when the transformation results in multiple elements for a single element. It flattens the nested stream structure into a single stream.
Example:
List<List<String>> nestedList = Arrays.asList( Arrays.asList("apple", "banana"), Arrays.asList("cherry", "date"), Arrays.asList("elderberry", "fig") ); List<String> flatList = nestedList.stream() .flatMap(List::stream) .collect(Collectors.toList()); System.out.println(flatList); // Output: [apple, banana, cherry, date, elderberry, fig]
In this case, flatMap is used to flatten the nested lists into a single list.
Key Differences:Yes, it is possible to extend a functional interface from another functional interface in Java. A functional interface can inherit from another functional interface, as long as it still maintains the rule of having exactly one abstract method.
When a functional interface extends another functional interface, it can inherit the abstract method(s) of the parent interface, and you can add additional methods as long as the single abstract method rule is maintained.
Example:@FunctionalInterface interface Animal { void makeSound(); } @FunctionalInterface interface Dog extends Animal { void bark(); } public class FunctionalInterfaceExample { public static void main(String[] args) { Dog dog = new Dog() { @Override public void makeSound() { System.out.println("Dog makes sound"); } @Override public void bark() { System.out.println("Dog barks"); } }; dog.makeSound(); // Output: Dog makes sound dog.bark(); // Output: Dog barks } }Key Points:
Predicate <Integer> isEven = num -> num % 2 == 0;
Consumer<String> print = s -> System.out.println(s);
Supplier<Double> random = () -> Math.random();
Function<Integer, String> toString = num -> "Number: " + num;
BiFunction<Integer, Integer, Integer> sum = (a, b) -> a + b;
Java 8 Stream API provides a wide variety of methods to work with collections in a functional style. Below are explanations and examples for each of the mentioned methods.
1. anyMatch()Returns true if any elements of the stream satisfy the provided predicate.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0); System.out.println(hasEven); // Output: true2. noneMatch()
Returns true if no elements of the stream satisfy the provided predicate.
boolean noNegative = numbers.stream().noneMatch(n -> n < 0); System.out.println(noNegative); // Output: true3. mapToLong()
Transforms the elements of the stream into long values.
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5); long sum = integers.stream().mapToLong(Integer::longValue).sum(); System.out.println(sum); // Output: 154. findAny()
Returns any element from the stream, or an empty Optional if no element is present. It may be useful in parallel streams.
Optional<Integer> anyNumber = numbers.stream().findAny(); System.out.println(anyNumber.orElse(-1)); // Output: Any element5. forEachOrdered()
Performs an action for each element of the stream, maintaining the encounter order (in case of parallel streams).
numbers.stream().forEachOrdered(System.out::println); // Output: 1 2 3 4 5 (in order)6. forEach()
Performs an action for each element of the stream.
numbers.stream().forEach(System.out::println); // Output: 1 2 3 4 57. allMatch()
Returns true if all elements of the stream satisfy the provided predicate.
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); System.out.println(allEven); // Output: false8. filter()
Filters elements of the stream based on a predicate.
List<Integer> evenNumbers = numbers.stream().filter(n -> n % 2 == 0).collect(Collectors.toList()); System.out.println(evenNumbers); // Output: [2, 4]9. findFirst()
Returns the first element of the stream, or an empty Optional if no element is present.
Optional<Integer> first = numbers.stream().findFirst(); System.out.println(first.orElse(-1)); // Output: 110. flatMapToInt()
Transforms the elements of the stream into an IntStream by flattening nested collections.
List<List<Integer>> nestedNumbers = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4)); int sum = nestedNumbers.stream() .flatMapToInt(list -> list.stream().mapToInt(Integer::intValue)) .sum(); System.out.println(sum); // Output: 1011. mapToInt()
Transforms the elements of the stream into IntStream.
List<Integer> integers = Arrays.asList(1, 2, 3, 4); int sum = integers.stream().mapToInt(Integer::intValue).sum(); System.out.println(sum); // Output: 1012. map()
Transforms the elements of the stream using a function.
List<String> words = Arrays.asList("hello", "world"); List<String> upperWords = words.stream().map(String::toUpperCase).collect(Collectors.toList()); System.out.println(upperWords); // Output: [HELLO, WORLD]13. peek()
Allows for performing an action on each element as the stream is processed, without modifying the stream.
List<Integer> modifiedList = numbers.stream() .peek(n -> System.out.println("Processing: " + n)) .map(n -> n * 2) .collect(Collectors.toList());14. counting()
Counts the number of elements in the stream.
long count = numbers.stream().count(); System.out.println(count); // Output: 515. Iterator()
Converts the stream to an Iterator (only available in the Stream interface).
Iterator<Integer> iterator = numbers.stream().iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }16. generate()
Generates an infinite stream using a supplier function.
Stream<Integer> infiniteStream = Stream.generate(() -> (int) (Math.random() * 100)); infiniteStream.limit(5).forEach(System.out::println);17. skip()
Skips the first n elements of the stream.
List<Integer> skippedNumbers = numbers.stream().skip(2).collect(Collectors.toList()); System.out.println(skippedNumbers); // Output: [3, 4, 5]18. SummaryStatistics()
Collects statistics, such as count, sum, min, average, and max, from a stream of data.
IntSummaryStatistics stats = numbers.stream().mapToInt(Integer::intValue).summaryStatistics(); System.out.println(stats); // Output: IntSummaryStatistics{count=5, sum=15, min=1, average=3.000000, max=5}19. Builder()
Used to create a stream through Stream.builder().
Stream<Integer> stream = Stream.20. empty()builder().add(1).add(2).add(3).build(); stream.forEach(System.out::println); // Output: 1 2 3
Returns an empty stream.
Stream<String> emptyStream = Stream.empty(); System.out.println(emptyStream.count()); // Output: 021. Stream toArray()
Converts a stream into an array.
Integer[] array = numbers.stream().toArray(Integer[]::new); System.out.println(Arrays.toString(array)); // Output: [1, 2, 3, 4, 5]22. Sum of List with Stream Filter
Calculates the sum of elements after filtering the stream based on a condition.
int sumOfEvens = numbers.stream() .filter(n -> n % 2 == 0) .mapToInt(Integer::intValue) .sum(); System.out.println(sumOfEvens); // Output: 6
The Optional class in Java 8 is a container object used to represent the presence or absence of a value. It helps to avoid NullPointerException by providing methods that allow the programmer to check if a value is present or not, and to handle cases where a value may be missing without directly dealing with null.
Key Features of Optional:Optional<String> nonEmptyOptional = Optional.of("Hello"); Optional<String> emptyOptional = Optional.empty(); Optional<String> nullableOptional = Optional.ofNullable(null);Common Methods:
Returns true if the value is present, otherwise false.
Optional<String> optionalName = Optional.of("John"); System.out.println(optionalName.isPresent()); // Output: true Optional<String> emptyOptional = Optional.empty(); System.out.println(emptyOptional.isPresent()); // Output: false2. ifPresent()
If the value is present, it performs the provided action on it.
optionalName.ifPresent(name -> System.out.println("Hello, " + name)); // Output: Hello, John emptyOptional.ifPresent(name -> System.out.println("Hello, " + name)); // No output since the value is absent3. get()
Returns the value if present, or throws NoSuchElementException if not.
String name = optionalName.get(); System.out.println(name); // Output: John // Uncommenting the following will throw NoSuchElementException // String emptyName = emptyOptional.get();4. orElse()
Returns the value if present, or a default value if not.
String result = optionalName.orElse("Default Name"); System.out.println(result); // Output: John String resultEmpty = emptyOptional.orElse("Default Name"); System.out.println(resultEmpty); // Output: Default Name5. orElseGet()
Returns the value if present, or invokes a supplier to return a default value if not.
String resultFromSupplier = emptyOptional.orElseGet(() -> "Generated Default Name"); System.out.println(resultFromSupplier); // Output: Generated Default Name6. orElseThrow()
Returns the value if present, or throws an exception if not.
String value = optionalName.orElseThrow(() -> new IllegalArgumentException("Value is absent")); System.out.println(value); // Output: John // Uncommenting the following will throw IllegalArgumentException // String emptyValue = emptyOptional.orElseThrow(() -> new IllegalArgumentException("Value is absent"));7. filter()
Returns an empty Optional if the value does not satisfy the given predicate.
Optional<String> filteredName = optionalName.filter(name -> name.startsWith("J")); System.out.println(filteredName.orElse("No match")); // Output: John Optional<String> filteredEmpty = emptyOptional.filter(name -> name.startsWith("J")); System.out.println(filteredEmpty.orElse("No match")); // Output: No match8. map()
Transforms the value inside the Optional if it is present.
Optional<String> upperCaseName = optionalName.map(String::toUpperCase); System.out.println(upperCaseName.orElse("No value")); // Output: JOHN Optional<String> emptyUpperCase = emptyOptional.map(String::toUpperCase); System.out.println(emptyUpperCase.orElse("No value")); // Output: No value9. flatMap()
Similar to map(), but expects an Optional return value from the transformation function.
Optional<String> flatMappedName = optionalName.flatMap(name -> Optional.of(name.toUpperCase())); System.out.println(flatMappedName.orElse("No value")); // Output: JOHN Optional<String> emptyFlatMap = emptyOptional.flatMap(name -> Optional.of(name.toUpperCase())); System.out.println(emptyFlatMap.orElse("No value")); // Output: No valueBenefits of Optional:
The Date-Time API, introduced in Java 8 as part of the java.time package, is designed to overcome the limitations of the older java.util.Date and java.util.Calendar classes. The new API is more user-friendly, immutable, and thread-safe, and it follows the ISO-8601 standard for time representation.
Key Features of Date-Time API:import java.time.LocalDate; import java.time.LocalTime; import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; public class DateTimeExample { public static void main(String[] args) { // LocalDate LocalDate today = LocalDate.now(); System.out.println("Today's date: " + today); // LocalTime LocalTime now = LocalTime.now(); System.out.println("Current time: " + now); // LocalDateTime LocalDateTime dateTime = LocalDateTime.now(); System.out.println("Current date and time: " + dateTime); // ZonedDateTime ZonedDateTime zonedDateTime = ZonedDateTime.now(); System.out.println("Current date and time with time zone: " + zonedDateTime); // Formatting Date-Time DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm"); String formattedDateTime = dateTime.format(formatter); System.out.println("Formatted date-time: " + formattedDateTime); } }Benefits of Date-Time API:
Default methods, introduced in Java 8, allow developers to add concrete methods to interfaces without affecting the classes that implement the interface. They provide a way to add new functionality to interfaces without breaking existing code.
Key Features of Default Methods:interface MyInterface { default void defaultMethod() { System.out.println("This is a default method."); } }Example:
interface Animal { default void sound() { System.out.println("Animals make sound"); } void sleep(); } class Dog implements Animal { @Override public void sleep() { System.out.println("Dog is sleeping"); } } public class Main { public static void main(String[] args) { Dog dog = new Dog(); dog.sound(); // Output: Animals make sound dog.sleep(); // Output: Dog is sleeping } }Multiple Inheritance of Default Methods:
Java allows multiple interfaces with default methods. In case of a conflict (when two interfaces define the same default method), the class implementing the interfaces must explicitly override the method to resolve the conflict.
interface InterfaceA { default void commonMethod() { System.out.println("InterfaceA default method"); } } interface InterfaceB { default void commonMethod() { System.out.println("InterfaceB default method"); } } class TestClass implements InterfaceA, InterfaceB { @Override public void commonMethod() { System.out.println("TestClass overrides commonMethod"); } } public class Main { public static void main(String[] args) { TestClass obj = new TestClass(); obj.commonMethod(); // Output: TestClass overrides commonMethod } }Benefits of Default Methods:
CompletableFuture is a class introduced in Java 8 as part of the java.util.concurrent package. It provides a framework for asynchronous programming, enabling developers to write non-blocking and event-driven code. It is an enhancement of the Future interface, adding features for chaining, combining tasks, and handling exceptions.
Key Features of CompletableFuture:import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) { // Asynchronous computation CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // Simulating a long-running task try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } return "Hello, CompletableFuture!"; }); // Chain another task future.thenApply(result -> { return result.toUpperCase(); }).thenAccept(result -> { System.out.println("Result: " + result); }); // Keep the program alive to see the result try { Thread.sleep(2000); // Wait for completion } catch (InterruptedException e) { e.printStackTrace(); } } }Explanation of Code:
Result: HELLO, COMPLETABLEFUTURE!Benefits of CompletableFuture:
Java 8 introduced CompletableFuture as an enhanced version of the Future interface, addressing its limitations and providing more powerful and flexible tools for asynchronous programming.
Limitations of Future:import java.util.concurrent.*; package com.java.java8; import java.util.Arrays; import java.util.List; import java.util.concurrent.*; public class FutureLimitation { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); Future> future1 = executor.submit(() -> { System.out.println(Thread.currentThread().getName()); // block main thread delay(); return Arrays.asList(1, 2, 3, 4); }); Future
> future2 = executor.submit(() -> { System.out.println(Thread.currentThread().getName()); // block main thread delay(); return Arrays.asList(1, 2, 3, 4); }); Future
> future3 = executor.submit(() -> { System.out.println(Thread.currentThread().getName()); // block main thread delay(); return Arrays.asList(1, 2, 3, 4); }); // can not combine || join || chain the features List
list1 = future1.get(); List list2 = future2.get(); List list3 = future3.get(); System.out.println(list1); System.out.println(list2); System.out.println(list3); // Cant handle exception Future > future4 = executor.submit(() -> { System.out.println(10/0); return Arrays.asList(1,2,3); }); List
list4 = future4.get(); executor.shutdown(); } private static void delay(){ try { Thread.sleep(30); }catch (InterruptedException e){ e.getMessage(); } } } ========================================== output: pool-1-thread-2 pool-1-thread-1 pool-1-thread-3 [1, 2, 3, 4] [1, 2, 3, 4] [1, 2, 3, 4] Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122) at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191) at com.java.java8.FutureLimitation.main(FutureLimitation.java:48) Caused by: java.lang.ArithmeticException: / by zero at com.java.java8.FutureLimitation.lambda$main$3(FutureLimitation.java:43) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:842)
Limitation: The future.get() call blocks the thread, which defeats asynchronous programming.
import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) { CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } return "CompletableFuture Result"; }); // Non-blocking and chaining future.thenApply(result -> result.toUpperCase()) .thenAccept(result -> System.out.println(result)); // Keep main thread alive for async result try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }
Advantage: Non-blocking execution and result processing with chaining.
Conclusion:CompletableFuture is a significant improvement over Future, enabling non-blocking, flexible, and scalable asynchronous programming with better error handling and chaining capabilities.
Method reference is a shorthand notation of a lambda expression to call a method directly. It is introduced in Java 8 and allows developers to refer to methods of a class or an object without invoking them. Method references make code more readable and concise by reducing boilerplate code.
Types of Method References:import java.util.function.Consumer; public class StaticMethodReference { public static void printMessage(String message) { System.out.println(message); } public static void main(String[] args) { Consumer<String> consumer = StaticMethodReference::printMessage; consumer.accept("Hello, Method Reference!"); } }
import java.util.function.Consumer; public class InstanceMethodReference { public void displayMessage(String message) { System.out.println(message); } public static void main(String[] args) { InstanceMethodReference obj = new InstanceMethodReference(); Consumer<String> consumer = obj::displayMessage; consumer.accept("Instance Method Reference Example"); } }
import java.util.function.Function; public class ArbitraryObjectMethodReference { public static void main(String[] args) { Function<String, String> function = String::toUpperCase; String result = function.apply("method reference example"); System.out.println(result); } }
import java.util.function.Supplier; public class ConstructorReference { public ConstructorReference() { System.out.println("Constructor Reference Example"); } public static void main(String[] args) { Supplier<ConstructorReference> supplier = ConstructorReference::new; supplier.get(); // Invokes the constructor } }Advantages of Method References:
Method references are a feature of Java 8 that enhance the expressiveness and simplicity of lambda expressions by enabling direct references to existing methods or constructors.
The Java Class Dependency Analyzer (jdeps) is a command-line tool introduced in Java 8. It is used to analyze the dependencies of Java classes and provides insights into the packages and modules that a Java application depends on. This helps developers better understand the structure of their applications and identify potential issues, such as cyclic dependencies or unnecessary dependencies on internal APIs.
Key Features of jdeps:jdeps [options] <path-to-jar/class-files>Options:
jdeps -s myapp.jar
Output: Displays a summary of dependencies for the JAR file.
jdeps -verbose:class myapp.jar
Output: Provides detailed dependencies at the class level.
jdeps -jdkinternals myapp.jar
Output: Lists internal APIs used by the application, helping to avoid compatibility issues in future JDK versions.
Benefits of jdeps:The Java Class Dependency Analyzer (jdeps) is a powerful tool introduced in Java 8 for analyzing and managing dependencies in Java applications. It is especially useful for optimizing applications and preparing them for modularization and long-term compatibility with newer Java versions.
The expression String::valueOf is a method reference in Java 8. It refers to the static valueOf method in the String class. The valueOf method is used to convert various types of data (such as primitives, objects, or arrays) into their string representation.
How It Works:import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class MethodReferenceExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4); // Using String::valueOf as a method reference List<String> stringNumbers = numbers.stream() .map(String::valueOf) .collect(Collectors.toList()); System.out.println(stringNumbers); // Output: [1, 2, 3, 4] } }
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class LambdaExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4); // Using a lambda expression equivalent to String::valueOf List<String> stringNumbers = numbers.stream() .map(x -> String.valueOf(x)) .collect(Collectors.toList()); System.out.println(stringNumbers); // Output: [1, 2, 3, 4] } }Common Uses of String::valueOf:
Java lambda expressions do not allow checked exceptions to be thrown directly.
If a lambda throws a checked exception, it must be handled inside the lambda or wrapped in an unchecked
exception.
package com.java.java8; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; public class LambdaCheckedException { public static void main(String[] args) { Listfiles = Arrays.asList("file1.txt", "file2.txt", "file3.txt", "missing.txt", "1"); files.forEach(handleCheckedException(file -> { if ("missing.txt".equals(file)) { throw new RuntimeException("File not found!"); } System.out.println("Processing: " + file); })); } public static Consumer handleCheckedException(CheckedConsumer consumer) { return value -> { try { consumer.accept(value); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } }; } @FunctionalInterface public interface CheckedConsumer { void accept(T t) throws RuntimeException; } } ====================== Output: Processing: file1.txt Processing: file2.txt Processing: file3.txt Exception in thread "main" java.lang.RuntimeException: File not found!
Aspect | Collections | Stream |
---|---|---|
Nature | Collections are in-memory data structures that store and organize data. | Streams are a sequence of elements that allow functional-style operations on data. |
Storage | Collections store data explicitly and can be modified. | Streams do not store data; they process data on-demand. |
Processing | Collections process data eagerly (immediate evaluation). | Streams process data lazily (evaluation happens only when required). |
Mutability | Data in a collection can be added, removed, or modified. | Streams are immutable; operations produce a new stream without modifying the source. |
Iteration | Collections support both external and internal iteration. | Streams only support internal iteration. |
Parallelism | Collections require explicit handling for parallel operations. | Streams have built-in support for parallel operations using parallelStream(). |
Data Source | Collections act as a data source for streams. | Streams operate on a data source, such as collections, arrays, or I/O channels. |
Reusability | Collections can be reused multiple times. | Streams cannot be reused; once a terminal operation is invoked, the stream is closed. |
API Introduced | Introduced in earlier versions of Java (e.g., Java 2 for the java.util package). | Introduced in Java 8 as part of the java.util.stream package. |
Feature | Runnable | Callable<T> |
---|---|---|
Method | public void run() | public T call() throws Exception |
Return Type | void (No return value) | T (Returns a result) |
Exception Handling | Cannot throw checked exceptions | Can throw checked exceptions |
How to Execute? | Thread / ExecutorService.submit(Runnable) | ExecutorService.submit(Callable<T>) |
Use Case | Tasks without a return value (e.g., logging, updates) | Tasks that return results (e.g., fetching data) |
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class RunnableExample { public static void main(String[] args) { Runnable task = () -> { System.out.println("Running in thread: " + Thread.currentThread().getName()); }; ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(task); executor.shutdown(); } }
import java.util.concurrent.*; public class CallableExample { public static void main(String[] args) { Callabletask = () -> { Thread.sleep(1000); return "Hello from Callable!"; }; ExecutorService executor = Executors.newSingleThreadExecutor(); Future<String> future = executor.submit(task); try { System.out.println("Result: " + future.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }
import java.util.*; import java.util.stream.*; public class FindDuplicates { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 5, 1, 6, 7, 8, 5); Set<Integer> seen = new HashSet<>(); Set<Integer> duplicates = new HashSet<>(); numbers.stream() .forEach(n -> { if (!seen.add(n)) { duplicates.add(n); } }); duplicates.forEach(n -> System.out.println("Duplicate element: " + n)); } }Output:
Duplicate element: 1 Duplicate element: 2 Duplicate element: 5Explanation of Alternative Approach:
You can use the Stream API to efficiently count the occurrences of a given character in a string by converting the string into a stream of characters and using the filter() method to count matches.
Optimized and Easy Solution:import java.util.stream.*; public class CharacterCount { public static void main(String[] args) { String str = "Java Stream API is awesome"; char targetChar = 'a'; // Using Stream API to count occurrences of the character long count = str.chars() // Convert string to an IntStream .filter(c -> c == targetChar) // Filter the characters that match the target .count(); // Count the matching characters System.out.println("Occurrence of character '" + targetChar + "': " + count); } }Explanation:
Occurrence of character 'a': 3
In Java, you can get a slice (a substream) of a stream by using the skip() and limit() methods. These methods allow you to select a specific portion of a stream by skipping elements and limiting the number of elements you want to process.
Optimized Solution:The most efficient and simple way to get a slice of a stream is by chaining the skip() and limit() methods:
import java.util.*; import java.util.stream.*; public class StreamSliceExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // Get a slice of the stream from index 3 to index 7 (exclusive) List<Integer> slicedList = numbers.stream() .skip(3) // Skip first 3 elements .limit(5) // Limit the stream to the next 5 elements .collect(Collectors.toList()); System.out.println(slicedList); // Output: [4, 5, 6, 7, 8] } }Explanation of Code:
[4, 5, 6, 7, 8]
Reversing elements of a parallel stream in Java can be tricky because parallel streams process elements in parallel, which can affect the order. To reverse the elements, the most efficient and easy solution is to collect the elements into a list first, then reverse the list.
Optimized Solution:import java.util.*; import java.util.stream.*; public class ReverseParallelStream { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); // Reverse the elements of a parallel stream List<Integer> reversedList = numbers.parallelStream() .collect(Collectors.toList()); // Collect to List Collections.reverse(reversedList); // Reverse the List // Create a new stream from the reversed list reversedList.stream().forEach(System.out::println); // Output reversed elements } }Explanation of Code:
8 7 6 5 4 3 2 1Why This Solution is Optimized:
Java 8 streams do not provide a direct way to work with indices, but you can achieve this by using the IntStream to generate indices and map them to elements in the stream. This approach is optimized and straightforward.
Example Code:import java.util.stream.IntStream; import java.util.List; import java.util.Arrays; public class StreamWithIndices { public static void main(String[] args) { List<String> items = Arrays.asList("A", "B", "C", "D"); // Iterate with indices IntStream.range(0, items.size()) .forEach(index -> System.out.println("Index: " + index + ", Value: " + items.get(index))); } }Explanation of Code:
Index: 0, Value: A Index: 1, Value: B Index: 2, Value: C Index: 3, Value: D
import java.util.*; import java.util.stream.*; public class SecondHighest { public static void main(String[] args) { // Sample List List<Integer> numbers = Arrays.asList(10, 20, 15, 30, 25, 40, 35); // Find the second highest number Optional<Integer> secondHighest = numbers.stream() .sorted(Comparator.reverseOrder()) // Sort in descending order .distinct() // Remove duplicates .skip(1) // Skip the highest number .findFirst(); // Get the second highest number // Print the result secondHighest.ifPresentOrElse( value -> System.out.println("Second Highest Number: " + value), () -> System.out.println("No second highest number found") ); } }Output:
Second Highest Number: 35
import java.util.*; import java.util.stream.*; public class RemoveDuplicates { public static void main(String[] args) { // Sample list with duplicates List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 5, 1, 6, 7, 8, 5); // Remove duplicates using Stream API List<Integer> uniqueNumbers = numbers.stream() .distinct() // Removes duplicates .collect(Collectors.toList()); // Collects the result into a List // Print the list without duplicates System.out.println("List without duplicates: " + uniqueNumbers); } }Output:
List without duplicates: [1, 2, 3, 4, 5, 6, 7, 8]
import java.util.*; import java.util.stream.*; import java.util.function.Predicate; public class PartitionStrings { public static void main(String[] args) { List<String> strings = Arrays.asList("apple", "cat", "dog", "elephant", "bat", "rat"); // Partitioning strings based on length (greater than 3 characters) Map<Boolean, List<String>> partitioned = strings.stream() .collect(Collectors.partitioningBy(str -> str.length() > 3)); // Print the partitioned lists System.out.println("Strings with length greater than 3: " + partitioned.get(true)); System.out.println("Strings with length 3 or less: " + partitioned.get(false)); } }Output:
Strings with length greater than 3: [apple, elephant] Strings with length 3 or less: [cat, dog, bat, rat]
import java.util.*; import java.util.stream.*; class Employee { private String name; private int id; // Constructor public Employee(String name, int id) { this.name = name; this.id = id; } // Getter for name public String getName() { return name; } } public class EmployeeNames { public static void main(String[] args) { // Create a list of employees List<Employee> employees = Arrays.asList( new Employee("Alice", 1), new Employee("Bob", 2), new Employee("Charlie", 3) ); // Retrieve the names of all employees List<String> employeeNames = employees.stream() .map(Employee::getName) // Extract the name .collect(Collectors.toList()); // Collect into a list // Print the names System.out.println("Employee Names: " + employeeNames); } }Output:
Employee Names: [Alice, Bob, Charlie]
Transaction maxTransaction = transactions.stream() .max(Comparator.comparing(Transaction::getAmount)) .orElse(null);
import java.util.*; import java.util.stream.*; public class MaxMinValues { public static void main(String[] args) { // Sample list of integers List<Integer> numbers = Arrays.asList(10, 20, 5, 8, 15, 3, 25); // Find the maximum value Optional<Integer> max = numbers.stream() .max(Comparator.naturalOrder()); // Find the minimum value Optional<Integer> min = numbers.stream() .min(Comparator.naturalOrder()); // Print the results System.out.println("Maximum value: " + max.orElse(null)); System.out.println("Minimum value: " + min.orElse(null)); } }Output:
Maximum value: 25 Minimum value: 3
import java.util.*; import java.util.stream.*; @Getter @AllArgConstructor @ToString class Employee { private String name; private String department; private double salary; } public class SecondHighestSalary { public static void main(String[] args) { // Sample employee list List<Employee> employees = Arrays.asList( new Employee("Alice", "IT", 80000), new Employee("Bob", "IT", 75000), new Employee("Charlie", "HR", 90000), new Employee("David", "IT", 85000), new Employee("Eve", "HR", 87000) ); // Specify the department String targetDepartment = "IT"; int nthHighest = 2; // Example: Find the 2nd highest salary // Find the nth highest salary employee in the specific department Optional<Employee> nthHighestEmployee = employees.stream() .filter(emp -> emp.getDepartment().equalsIgnoreCase(targetDepartment)) // Filter by department .sorted(Comparator.comparingDouble(Employee::getSalary).reversed()) // Sort by salary in descending order .skip(nthHighest - 1) // Skip n-1 employees .findFirst(); // Get the nth employee // Print the result nthHighestEmployee.ifPresentOrElse( emp -> System.out.println("Employee with " + nthHighest + " highest salary: " + emp), () -> System.out.println("No employee found for the given criteria.") ); } }Output:
Employee with second highest salary in IT: Employee{name=Bob, department=IT, salary=75000.0}
import java.util.*; import java.util.function.*; import java.util.stream.*; public class FirstNonRepeatedCharacter { public static void main(String[] args) { String input = "swiss"; // Find the first non-repeated character Optional<Character> firstNonRepeated = input.chars() .mapToObj(c -> (char) c) .collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting())) .entrySet() .stream() .filter(entry -> entry.getValue() == 1) .map(Map.Entry::getKey) .findFirst(); // Print the result System.out.println("First Non-Repeated Character: " + firstNonRepeated.orElse('None')); } }Input and Output Example:
For the input string "swiss", the program will output:
First Non-Repeated Character: wExplanation:
import java.util.*; import java.util.stream.*; public class FirstRepeatedCharacter { public static void main(String[] args) { String input = "streamfunction"; // Find the first repeated character Optional<Character> firstRepeated = input.chars() .mapToObj(c -> (char) c) // Convert int to char .filter(new HashSet<>()::add) .findFirst(); // Print the result firstRepeated.ifPresentOrElse( c -> System.out.println("First repeated character: " + c), () -> System.out.println("No repeated characters found") ); } }Explanation:
First repeated character: t
public class SortValues{ public static void main(String args[]) { List<Integer> myList = Arrays.asList(10,15,8,49,25,98,98,32,15); myList.stream() .sorted() .forEach(System.out::println); /* Or can also try below way */ Arrays.stream(arr).boxed().sorted().collect(Collectors.toList()) } }
public class SortDescending{ public static void main(String args[]) { List<Integer> myList = Arrays.asList(10,15,8,49,25,98,98,32,15); myList.stream() .sorted(Collections.reverseOrder()) .forEach(System.out::println); } }
public boolean containsDuplicate(int[] nums) { List<Integer> list = Arrays.stream(nums) .boxed() .collect(Collectors.toList()); Set<Integer> set = new HashSet<>(list); if(set.size() == list.size()) { return false; } return true; /* or can also try below way */ Set<Integer> setData = new HashSet<>(); return Arrays.stream(nums) .anyMatch(num -> !setData.add(num)); }output
Input: nums = [1,2,3,1] Output: true Input: nums = [1,2,3,4] Output: false
import java.util.*; import java.util.stream.*; @Getter @AllArgConstructor @ToString class Person { private String name; private int age; } public class ListToSortedMap { public static void main(String[] args) { // Sample list of Person objects List<Person> people = Arrays.asList( new Person("Alice", 30), new Person("Bob", 25), new Person("Alice", 28), new Person("David", 35), new Person("Bob", 22) ); // Convert to a sorted map with duplicate keys handled Map<String, List<Person>> sortedMap = people.stream() .collect(Collectors.groupingBy( Person::getName, // Group by the 'name' key () -> new TreeMap<>(), // Use TreeMap for sorted keys Collectors.toList() // Collect the grouped values into a List )); // Print the resulting sorted map sortedMap.forEach((key, value) -> System.out.println(key + ": " + value) ); } }Explanation:
Alice: [Person{name=Alice, age=30}, Person{name=Alice, age=28}] Bob: [Person{name=Bob, age=25}, Person{name=Bob, age=22}] David: [Person{name=David, age=35}]
import java.util.*; import java.util.stream.*; public class WordCount { public static void main(String[] args) { // Sample ArrayList of words List<String> words = Arrays.asList("apple", "banana", "apple", "orange", "banana", "apple"); // Count each word using Stream API Map<String, Long> wordCount = words.stream() .collect(Collectors.groupingBy(word -> word, Collectors.counting())); // Print the word count wordCount.forEach((word, count) -> System.out.println(word + ": " + count)); } }Explanation:
apple: 3 banana: 2 orange: 1
import java.util.*; import java.util.stream.*; public class CharacterCount { public static void main(String[] args) { String input = "hello world"; // Calculate character frequency Map<Character, Long> characterCount = input.chars() .filter(c -> !Character.isWhitespace(c)) // Ignore spaces .mapToObj(c -> (char) c) // Convert int to char .collect(Collectors.groupingBy(c -> c, Collectors.counting())); // Print the character counts characterCount.forEach((character, count) -> System.out.println("Character: " + character + ", Count: " + count)); } }Output for Input "hello world":
Character: h, Count: 1 Character: e, Count: 1 Character: l, Count: 3 Character: o, Count: 2 Character: w, Count: 1 Character: r, Count: 1 Character: d, Count: 1
import java.util.*; import java.util.stream.*; public class FindNumbersStartingWithOne { public static void main(String[] args) { // Sample list of integers List<Integer> numbers = Arrays.asList(10, 15, 20, 30, 1, 100, 121, 99); // Stream API to find numbers starting with '1' List<Integer> result = numbers.stream() .filter(n -> String.valueOf(n).startsWith("1")) .collect(Collectors.toList()); // Print the result System.out.println("Numbers starting with 1: " + result); } }Output:
Numbers starting with 1: [10, 15, 1, 100, 121]