Q 1. What are the main features introduced in Java 8?
- Lambda Expressions
- Functional Interfaces
- Stream API
- Default Methods in Interfaces
- Method References
- New Date and Time API (java.time package)
Q 2. What are Lambda Expressions?
Lambda expressions are a concise way to represent a method implementation, essentially treating code as data. They enable the use of functional programming concepts in Java.
Q 3. What is a Functional Interface?
Here are the key points to understand about functional interfaces in Java 8:
1. Single Abstract Method (SAM): A functional interface has only one abstract method. However, it can have multiple default or static methods.
2. Lambda Expressions: Functional interfaces are designed to work seamlessly with lambda expressions.
3. Method References: Method references allow you to reference methods or constructors of functional interfaces, making your code more concise and readable.
4. @FunctionalInterface Annotation: While not required, you can annotate your custom functional interfaces with `@FunctionalInterface` to indicate that they are intended to be used as functional interfaces. The compiler will enforce the single abstract method rule, and if you accidentally violate it, you'll get a compilation error.
Here's an example of a simple functional interface and its usage:
@FunctionalInterface
interface MyFunctionalInterface {
void doSomething(); // Abstract method
}
public class Main {
public static void main(String[] args) {
// Using a lambda expression to implement the abstract method
MyFunctionalInterface functionalInterface = () -> {
System.out.println("Doing something...");
};
// Calling the abstract method through the functional interface
functionalInterface.doSomething();
}
}
Q 4. What is the Stream API?
The Stream API provides a way to process collections of data in a functional-style, allowing operations like filtering, mapping, and reducing on data sets.
Q 5. How do you create a Stream in Java 8?
Streams can be created from various sources such as collections, arrays, or by using `Stream.of()` and `Stream.generate()` methods.
Q 6. What are Method References?
Method references allow you to refer to methods by their names without invoking them. They are shorthand notation for a lambda expression targeting a specific method.
Q 7. Explain Default Methods in Interfaces.
Default methods are methods with an implementation in an interface. They were introduced to support backward compatibility when new methods are added to interfaces. Existing implementing classes don't need to implement these default methods.
Q 8. How does the New Date and Time API differ from the old Date and Calendar classes?
The new Date and Time API (java.time package) provides a more reliable and comprehensive way to handle date and time. It's immutable, thread-safe, and addresses many of the shortcomings of the old Date and Calendar classes.
Q 9. What is the `forEach` method in Java 8 Streams used for?
The `forEach` method is used to iterate through the elements of a stream and perform a specified action on each element.
Q 10. How does Java 8 handle the "NullPointerException" in its Stream operations?
Java 8 introduces the `Optional` class which helps avoid null pointer exceptions. Stream operations like `map`, `filter`, and `flatMap` return `Optional` values, which can be processed safely.
Q 11. What is the purpose of the `Supplier` interface?
The `Supplier` interface represents a supplier of results, and it doesn't take any arguments. It is often used when lazy evaluation is required.
Q 12. How can you convert a `List` to a comma-separated string using Java 8?
You can use the `Collectors.joining()` method along with streams to achieve this:
String result = myList.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
Use of Stream APIS
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class JavaStream {
public static void main(String[] args) {
List<Integer> numbers= Arrays.asList(1,2,3,4,6,5,4,3,6,7,8,9,10);
List<Integer> numbers= Arrays.asList();
List<Integer> even= numbers.stream().
filter(n->n%2==0).
collect(Collectors.toList());
System.out.println(even);
//Square of all numbers
List<Integer> squNumbers = numbers.stream() .map(n->n*n). collect(Collectors. toList());
System.out.println(squNumbers);
//Sum of all numbers
Integer sum=numbers.stream().
mapToInt(Integer::intValue).sum();
System.out.println(sum);
//Sum of even numbers
Integer sumofEven=numbers.stream()
.filter(n->n%2==0)
.mapToInt(Integer::intValue)
.sum();
System.out.println(sumofEven);
//Sum of odd numbers
Integer sumofOdd= numbers.stream()
.filter(n->n%2!=0)
.mapToInt(Integer::intValue)
.sum();
System.out.println(sumofOdd);
//count even numbers
Long countEven= numbers.stream()
.filter(n->n%2==0).
count();
System.out.println(countEven);
//Find max number
Integer maxNumber = numbers.stream()
.max(Integer::compareTo).orElse(-1);
System.out.println(maxNumber);
}
}
Stream API In java 8
The Stream API is one of the significant additions introduced in Java 8, which provides a powerful and efficient way to process data in collections. It allows developers to perform aggregate operations on collections (like lists, sets, or maps) with concise and expressive code.
Introduction to Streams:
A stream is not a data structure instead it takes input from the Collections, Arrays or I/O channels.
Streams don’t change the original data structure, they only provide the result as per the pipelined methods.
Each intermediate operation is lazily executed and returns a stream as a result, hence various intermediate operations can be pipelined. Terminal operations mark the end of the stream and return the result.
Creating Streams:
There are several ways to create a Stream depending on the type of data source you have. Here are the different ways to create a Stream:
From a Collection:
You can create a Stream from a collection (e.g., List, Set) using the `stream()` method available in the Collection interface:
List<String> namesList = Arrays.asList("Alice", "Bob", "Charlie");
Stream<String> namesStream = namesList.stream();
From an Array:
You can create a Stream from an array using the `Arrays.stream()` method:
int[] numbersArray = {1, 2, 3, 4, 5};
IntStream numbersStream = Arrays.stream(numbersArray);
From Individual Elements:
You can create a Stream directly from individual elements using the `Stream.of()` method:
Stream<String> singleElementStream = Stream.of("Hello");
From a String:
You can create a Stream of characters from a String using the `chars()` method:
String text = "Java 8 Stream";
IntStream charStream = text.chars();
Stream Builder:
You can use the `Stream.Builder` interface to manually build a Stream:
Stream.Builder<String> streamBuilder = Stream.builder();
streamBuilder.add("apple").add("banana").add("cherry");
Stream<String> fruitStream = streamBuilder.build();
From File or BufferedReader:
You can create a Stream from the lines of a file or a `BufferedReader` using the `lines()` method:
try (Stream<String> linesStream = Files.lines(Paths.get("file.txt"))) {
// Process each line in the file
} catch (IOException e) {
e.printStackTrace();
}
Stream.generate() and Stream.iterate():
You can create an infinite Stream using `Stream.generate()` or `Stream.iterate()`:
Stream<Double> randomStream = Stream.generate(Math::random); // Generates an infinite stream of random numbers
Stream<Integer> evenNumbersStream = Stream.iterate(0, n -> n + 2); // Generates an infinite stream of even numbers
Intermediate Operations:
Intermediate operations in a Stream are non-terminal operations that allow you to modify, filter, or transform the elements of the Stream. These operations return a new Stream, enabling method chaining. Some commonly used intermediate operations are:
`filter(Predicate<T> predicate)`: Filters the elements based on a specified condition.
`map(Function<T, R> mapper)`: Transforms each element into another value using the given function.
`sorted(Comparator<T> comparator)`: Sorts the elements based on the provided comparator.
`distinct()`: Removes duplicate elements from the Stream.
`limit(long maxSize)`: Limits the Stream to a specified number of elements.
Terminal Operations:
Terminal operations in a Stream produce a result or a side effect, closing the Stream and making it ineligible for further operations. Here are some commonly used terminal operations:
`forEach(Consumer<T> action)`: Performs the specified action on each element of the Stream.
`collect(Collector<T, A, R> collector)`: Aggregates the elements into a collection using the provided collector.
`count()`: Returns the number of elements in the Stream.
`anyMatch(Predicate<T> predicate)`: Checks if any element matches the given condition.
`allMatch(Predicate<T> predicate)`: Checks if all elements match the given condition.
`noneMatch(Predicate<T> predicate)`: Checks if no elements match the given condition.
`reduce(BinaryOperator<T> accumulator)`: Combines the elements of the Stream using the specified binary operator.
Lazy Evaluation:
One of the most important features of Streams is lazy evaluation. Intermediate operations are lazily evaluated, meaning they are not executed until a terminal operation is called. This allows Java to optimise the processing and avoid unnecessary computations.
Parallel Streams:
Java 8 introduced the concept of parallel streams, which allows the Stream operations to be executed in parallel and take advantage of multi-core processors. You can convert a regular stream to a parallel stream using the `parallel()` method.
Stream<String> namesStream = namesList.parallelStream();
When to Use Streams:
Streams are particularly useful when working with large datasets or when you want to process data in a functional and declarative manner. They promote a more concise and readable code style compared to traditional loops.
Overall, the Stream API in Java 8 simplifies the process of data manipulation, filtering, and transformation, making code more expressive and concise while leveraging the potential for parallel processing to improve performance when dealing with large datasets.