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.


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:


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();

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);

You can create a Stream directly from individual elements using the `Stream.of()` method:


Stream<String> singleElementStream = Stream.of("Hello");


You can create a Stream of characters from a String using the `chars()` method:


String text = "Java 8 Stream";

IntStream charStream = text.chars();


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();


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();

}


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 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:



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.


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.


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();


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.