Hibernate IQs

  1. Hibernate Mapping with Multiple column
  2.  How data is stored in Cassandra 
  3.  What is difference between Cassandra and RDBMS 
  4. Embedded and embeddable
  5. N+1 Problem and solution

N+1 Problem

Definition

The N+1 problem is a performance issue that can occur in database-driven applications, particularly those using object-relational mapping (ORM) frameworks like Hibernate, when fetching data using relationships. The problem arises when the application executes N+1 database queries to retrieve N entities along with their associated relationships.

Explanation :

  1. Explanation:
    • Suppose you have an entity Parent with a one-to-many relationship to Child. When you fetch a list of Parent entities, each with a collection of associated Child entities, the N+1 problem occurs.
    • For each Parent entity fetched, an additional query is executed to fetch its associated Child entities. Hence, if you have N Parent entities, you end up executing N+1 queries (N queries to fetch the Parent entities and 1 query for each Parent to fetch its Child entities).
  2. Example:
    • Consider the following code:
List<Parent> parents = entityManager.createQuery("SELECT p FROM Parent p", Parent.class).getResultList();
for (Parent parent : parents) {
    // Each iteration triggers an additional query to fetch Child entities for each Parent
    List<Child> children = parent.getChildren();
}

In this example, if there are N Parent entities, it leads to N+1 queries.

Solution to N+1 Problem:

  1. Eager loading (not recommended)
  2. lazy loading with batch fetching(recommended)

Eager Fetching:

  • One solution is to use eager fetching, where the associated entities are fetched along with the main entity in a single query.
  • In JPA/Hibernate, you can use the @OneToMany or @ManyToOne annotation with the fetch attribute set to FetchType.EAGER.
@Entity
public class Parent {
    // Other annotations...

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "parent")
    private List<Child> children;
}

However, eager fetching has its downsides, such as potentially fetching more data than needed and impacting performance in other scenarios.

Lazy Loading with Batch Fetching:

  • A more efficient solution is to use lazy loading for relationships and employ batch fetching to minimize the number of queries.
  • Lazy loading defers the loading of associated entities until they are actually accessed, and batch fetching allows fetching multiple entities in a single query.
@Entity
public class Parent {
    // Other annotations...

    @OneToMany(mappedBy = "parent")
    private List<Child> children;
}

@Entity
public class Child {
    // Other annotations...

    @ManyToOne
    @JoinColumn(name = "parent_id")
    private Parent parent;
}


List<Parent> parents = entityManager.createQuery("SELECT p FROM Parent p", Parent.class).getResultList();
// Batch fetch the children for all parents in a single query
parents.forEach(parent -> {
    List<Child> children = parent.getChildren(); // Lazy loading
    children.size(); // Trigger batch fetch
});

  • With this approach, you fetch the children in batches, reducing the number of queries.

It’s essential to carefully choose the fetching strategy based on your application’s requirements and performance considerations to avoid the N+1 problem and ensure efficient database access.

Embedded and embeddable

@Embedded and @Embeddable annotations are used to represent and map composite or embedded objects in database tables. These annotations allow you to model a part of an entity as an embedded object, and the data of this embedded object is stored in the same table as the owning entity.

@Embeddable:

  1. Annotation:
    • @Embeddable is used to annotate a class whose instances are intended to be embedded into entities.
    • It marks a class as embeddable, meaning its instances can be used as components within other entities.
  2. Example:
    • Let’s say you have an Address class that you want to embed in other entities.
@Embeddable
public class Address {
    private String street;
    private String city;
    private String zipCode;

    // Constructors, getters, setters
}

@Embedded:

  1. Annotation:
    • @Embedded is used to embed an @Embeddable object within an entity.
    • It is placed on a field or a method in the entity class to indicate that the attribute should be stored as an embedded object in the database.
  2. Example:
    • Consider an Employee entity that includes an embedded Address.
@Entity
public class Employee {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Embedded
    private Address address;

    // Constructors, getters, setters
}

In this example, the Employee entity has an @Embedded attribute, address, which is of type Address. The @Embeddable annotation on the Address class indicates that instances of Address can be embedded within other entities.

With these annotations, when Hibernate generates the database schema, it creates a single table for the Employee entity that includes columns for both the regular attributes of the Employee entity and the attributes of the embedded Address object.

CREATE TABLE Employee (
    id BIGINT PRIMARY KEY,
    name VARCHAR(255),
    address_street VARCHAR(255),
    address_city VARCHAR(255),
    address_zipCode VARCHAR(255)
);

This way, the data of the embedded object is stored as part of the owning entity’s table, allowing you to model more complex data structures in your domain model.

DBMS FAQs

  1. Having, Grouping SQL 
  2.  Indexes in DB 
  3.  Union and union all difference 
  4.  Self join vs outer join 
  5. Self join vs outer join 
  6.  Cascade : when parent data is updated or deleted, child’s data is also 
  7.  Normalization 
  8.  Subquery 

Web-service Interview Questions

  1.  Interceptor and its use 
  2.  How interceptor works 
  3.  Controller Advice 
  4.  How do we create interceptor 
  5. Rest
    1. Richardson Rest Maturity model (4 level that defines the maturity of rest servies)
    2. Rest Api
      1. in rest api a recommended term used to refer to multiple resources
      2. time to first hello world rest
      3. rest api to add the version in header by using accept and content type
        1. https://blog.allegro.tech/2015/01/Content-headers-or-how-to-version-api.html
    3.  Http Response Code 
    4. How communication is done between 2 rest services
    5. What is Response Body
    6. idempotent http req 
    7. restful services architecture 
    8. http methods
    9. API security
      1. using oauth , what scope is required for write access to api
      2. which grant type support refresh token
      3. property to include in json to represent subresources /in rest api (llinks/embedded?)
  6. benefit of GRAPHQL over rest approaches
  7. WebHooks vs sync api and usage
  8. how to handle transactional commit over distributed system (2phase commit , saga pattern – push or pull )
    • when to use push vs when to use pull mechanism

HTTP Verb CRUD Entire Collection (e.g. /customers) Specific Item (e.g. /customers/{id}) 
POST Create 201 (Created), ‘Location’ header with link to /customers/{id} containing new ID. 404 (Not Found), 409 (Conflict) if resource already exists.. 
GET Read 200 (OK), list of customers. Use pagination, sorting and filtering to navigate big lists. 200 (OK), single customer. 404 (Not Found), if ID not found or invalid. 
PUT Update/Replace 405 (Method Not Allowed), unless you want to update/replace every resource in the entire collection. 200 (OK) or 204 (No Content). 404 (Not Found), if ID not found or invalid. 
PATCH Update/Modify 405 (Method Not Allowed), unless you want to modify the collection itself. 200 (OK) or 204 (No Content). 404 (Not Found), if ID not found or invalid. 
DELETE Delete 405 (Method Not Allowed), unless you want to delete the whole collection—not often desirable. 200 (OK). 404 (Not Found), if ID not found or invalid. 

Spring Interview Questions

Index

  1. Spring bean scope
  2. Use of @qualifer and @primary 
  3. Security in spring 
  4. Dependency injection 
  5. Rest controller 
  6.  Difference between DI and IOC 
  7.  Bean lifecycle 
  8. Req attr Param 
  9. Spring JDBC 
  10.  Qualifier 
  11. Exception Handler 
  12. diff @Configuration @EnableAutoConfiguration & @ComponentScan 
  13. Circular Dependency (How to resolve)
  14. Proxy and why needed? how to create one?


Spring bean scope 

Java-8 features

Index (for more depth refer drive java-8book)

  1. Lambda Expression / Functional Interfaces
    1. Why it was introduced in first place
    2. What problem does it solve
    3. Lambda expression internal working 
    4. Need of functional interface 
    5. Ever used functional interface 
  2. Streams
    1. Why it was introduced in first place
    2. What problem does it solve
    3. Sorting, filtering, mapping streams 
    4. Map, flatmap usage 
  3. Interface – Default/Static methods
    1. Why it was introduced in first place
    2. What problem does it solve
  4. Method and Constructor References
    1. Why it was introduced in first place
    2. What problem does it solve
  5. Optional
    1. Why it was introduced in first place
    2. What problem does it solve
  6. Functionality added to Hash-map
    1. Why it was introduced in first place
    2. What problem does it solve
  7. Functionality added for Garbage Collection
    1. Why it was introduced in first place
    2. What problem does it solve
  8. Programming Questions – java -8 features

Lambda

Index

  1. Introduction
  2. Representation

How does lambda expressions fit into Java’s type system?

Each lambda corresponds to a given type, specified by an interface. A functional interface must contain exactly one abstract method declaration. Each lambda expression of that type will be matched to this abstract method. Since default methods are not abstract you’re free to add default methods to your functional interface.

Representation and usage:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});

The static utility method Collections.sort accepts a list and a comparator in order to sort the elements of the given list. You often find yourself creating anonymous comparators and pass them to the sort method.

Instead of creating anonymous objects all day long, Java 8 comes with a much shorter syntax, lambda expressions:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});

Streams

Index

  1. Introduction
    1. Definition
    2. How streams differ from collection
  2. Representation – ways to obtain stream
  3. Stream Operations and pipeline
  4. Parallelism
  5. FAQs

Stream –

Definition – It conveys elements from a source such as a data structure, an array, a generator function, or an I/O channel, through a pipeline of computational operations.

Description in context to java and programming-

The package java.util.stream provides the classes to support functional-style operations on streams of elements, such as map-reduce transformations on collections. For example:

int sum = widgets.stream()
                      .filter(b -> b.getColor() == RED)
                      .mapToInt(b -> b.getWeight())
                      .sum();

Here we use widgets, a Collection<Widget>, as a source for a stream, and then perform a filter-map-reduce on the stream to obtain the sum of the weights of the red widgets.

Streams differ from collections in several ways:

  1. No storage. A stream is not a data structure that stores elements; instead, it conveys elements from a source such as a data structure, an array, a generator function, or an I/O channel, through a pipeline of computational operations.
  2. Functional in nature. An operation on a stream produces a result, but does not modify its source. For example, filtering a Stream obtained from a collection produces a new Stream without the filtered elements, rather than removing elements from the source collection.
    • Q- if it does not modify the source, how is it functional ? Ans- it performs the operation[functions are known for this and not modifying the source])
  3. Laziness-seeking. Many stream operations, such as filtering, mapping, or duplicate removal, can be implemented lazily, exposing opportunities for optimization. For example, “find the first String with three consecutive vowels” need not examine all the input strings. Stream operations are divided into intermediate (Stream-producing) operations and terminal (value- or side-effect-producing) operations. Intermediate operations are always lazy.
    • Q- Meaning of intermediate operations being lazy? Ans – doesn’t go to next operation unless all streams elements consumed(Or intermediate operation task completed e.g find first String with 3 vowels in beginning) (is it – what in case of infinite stream?)[guess based on understanding but doubt based on working senario]
    • Q- so how is for loop different from stream pipeline – once we find specific string we can break,
      • Ans1- In-build operations are provided, we don’t need to initialise extra variables for looping.
      • Ans2- for looping through we need to store the values in DS,in case we have infinite source of data – it will be a challange to store the data in-memory!
      • Q2-Is it thread safe-the stream operation? Yes because – Streams are not modifiable i.e one can’t add or remove elements from streams.(ref- gfg) where as once can easily remove/add elements from collection.
  4. Possibly unbounded. While collections have a finite size, streams need not. Short-circuiting operations such as limit(n) or findFirst() can allow computations on infinite streams to complete in finite time.
  5. Consumable. The elements of a stream are only visited once during the life of a stream. Like an Iterator, a new stream must be generated to revisit the same elements of the source.
    1. E.g https://www.java67.com/2016/03/how-to-use-flatmap-in-java-8-stream.html
    2. Look for official examples in Oracle website

Streams can be obtained in a number of ways. Some examples include:

  1. From a Collection via the stream() and parallelStream() methods;
  2. From an array via Arrays.stream(Object[]);
  3. From static factory methods on the stream classes, such as Stream.of(Object[])IntStream.range(int, int) or Stream.iterate(Object, UnaryOperator);
  4. The lines of a file can be obtained from BufferedReader.lines();
  5. Streams of file paths can be obtained from methods in Files;
  6. Streams of random numbers can be obtained from Random.ints();
  7. Numerous other stream-bearing methods in the JDK, including BitSet.stream()Pattern.splitAsStream(java.lang.CharSequence), and JarFile.stream().

Stream Operations and Pipeline

A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.

  1. Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.
  2. Terminal operations, such as Stream.forEach or IntStream.sum, may traverse the stream to produce a result or a side-effect. After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used; if you need to traverse the same data source again, you must return to the data source to get a new stream. In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning. Only the terminal operations iterator() and spliterator() are not; these are provided as an “escape hatch” to enable arbitrary client-controlled pipeline traversals in the event that the existing operations are not sufficient to the task.
  3. Processing streams lazily allows for significant efficiencies; in a pipeline such as the filter-map-sum example above, filtering, mapping, and summing can be fused into a single pass on the data, with minimal intermediate state. Laziness also allows avoiding examining all the data when it is not necessary; for operations such as “find the first string longer than 1000 characters”, it is only necessary to examine just enough strings to find one that has the desired characteristics without examining all of the strings available from the source. (This behavior becomes even more important when the input stream is infinite and not merely large.)
  4. Intermediate operations are further divided into stateless and stateful operations. Stateless operations, such as filter and map, retain no state from previously seen element when processing a new element — each element can be processed independently of operations on other elements. Stateful operations, such as distinct and sorted, may incorporate state from previously seen elements when processing new elements.
  5. Stateful operations may need to process the entire input before producing a result. For example, one cannot produce any results from sorting a stream until one has seen all elements of the stream. As a result, under parallel computation, some pipelines containing stateful intermediate operations may require multiple passes on the data or may need to buffer significant data. Pipelines containing exclusively stateless intermediate operations can be processed in a single pass, whether sequential or parallel, with minimal data buffering.
  6.  some operations are deemed short-circuiting operations. An intermediate operation is short-circuiting if, when presented with infinite input, it may produce a finite stream as a result. A terminal operation is short-circuiting if, when presented with infinite input, it may terminate in finite time. Having a short-circuiting operation in the pipeline is a necessary, but not sufficient, condition for the processing of an infinite stream to terminate normally in finite time.

Parallelism

The stream implementations in the JDK create serial streams unless parallelism is explicitly requested. For example, Collection has methods Collection.stream() and Collection.parallelStream(), which produce sequential and parallel streams respectively; other stream-bearing methods such as IntStream.range(int, int) produce sequential streams but these streams can be efficiently parallelized by invoking their BaseStream.parallel() method. To execute the prior “sum of weights of widgets” query in parallel, we would do:

int sumOfWeights = widgets.parallelStream()
                               .filter(b -> b.getColor() == RED)mmel, 3. Nginx, 4. Spring Boot, 5. Liel, 3. Nginx, 4. Spring Boot, 5. Li
                               .mapToInt(b -> b.getWeight())
                               .sum();

When the terminal operation is initiated, the stream pipeline is executed sequentially or in parallel depending on the orientation of the stream on which it is invoked.

Non-Interference

Streams enable you to execute possibly-parallel aggregate operations over a variety of data sources, including even non-thread-safe collections such as ArrayList. This is possible only if we can prevent interference with the data source during the execution of a stream pipeline. Except for the escape-hatch operations iterator() and spliterator(), execution begins when the terminal operation is invoked, and ends when the terminal operation completes.

List<String> l = new ArrayList(Arrays.asList("one", "two"));
     Stream<String> sl = l.stream();
     l.add("three");
     String s = sl.collect(joining(" "));

First a list is created consisting of two strings: “one”; and “two”. Then a stream is created from that list. Next the list is modified by adding a third string: “three”. Finally the elements of the stream are collected and joined together. Since the list was modified before the terminal collect operation commenced the result will be a string of “one two three”.


Stream Operations summarised:

OperationMethod SignatureTypeDescriptionUsage Example
filterStream<T> filter(Predicate<? super T> p)IntermediateReturns a stream of elements that match the given predicate.stream.filter(x -> x > 5).forEach(System.out::println);
mapStream<R> map(Function<? super T, ? extends R> mapper)IntermediateTransforms each element using the provided function.stream.map(x -> x * 2).collect(Collectors.toList());
flatMapStream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)IntermediateSimilar to map, but each input element is mapped to a stream and the resulting streams are concatenated.stream.flatMap(str -> Arrays.stream(str.split(" "))).distinct().collect(Collectors.toList());
distinctStream<T> distinct()IntermediateReturns a stream of distinct elements.stream.distinct().forEach(System.out::println);
sortedStream<T> sorted()IntermediateReturns a stream sorted according to the natural order of elements.stream.sorted().forEach(System.out::println);
peekStream<T> peek(Consumer<? super T> action)IntermediateApplies the specified action to each element and returns a new stream.stream.peek(x -> System.out.println("Processing: " + x)).count();
forEachvoid forEach(Consumer<? super T> action)TerminalPerforms an action for each element of the stream.stream.forEach(System.out::println);
toArrayObject[] toArray()TerminalReturns an array containing the elements of the stream.Object[] array = stream.toArray();
collectR collect(Collector<? super T, A, R> collector)TerminalPerforms a mutable reduction on the elements of the stream using a Collector.List<Integer> list = stream.collect(Collectors.toList());
reduceOptional<T> reduce(BinaryOperator<T> accumulator)TerminalPerforms a reduction on the elements of the stream using an associative accumulation function and returns an Optional.Optional<Integer> result = stream.reduce((a, b) -> a * b);
countlong count()TerminalReturns the count of elements in the stream as a long.long count = stream.count();
anyMatchboolean anyMatch(Predicate<? super T> predicate)TerminalReturns true if any elements of the stream match the given predicate.boolean anyMatch = stream.anyMatch(x -> x > 5);
allMatchboolean allMatch(Predicate<? super T> predicate)TerminalReturns true if all elements of the stream match the given predicate.boolean allMatch = stream.allMatch(x -> x > 0);
noneMatchboolean noneMatch(Predicate<? super T> predicate)TerminalReturns true if no elements of the stream match the given predicate.boolean noneMatch = stream.noneMatch(x -> x == 0);
findFirstOptional<T> findFirst()TerminalReturns the first element of the stream as an Optional.Optional<Integer> firstElement = stream.findFirst();
findAnyOptional<T> findAny()TerminalReturns any element of the stream as an Optional.Optional<Integer> anyElement = stream.findAny();

Interface Default Methods

Index

  1. Introduction
  2. Representation
  3. Why it was introduced in first place
interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }

Why it was introduced in first place


To provide a mechanism for adding new methods to interfaces without breaking existing implementations.

Before Java 8, interfaces in Java could only declare abstract methods, and any class implementing an interface was required to provide concrete implementations for all of those methods. This created challenges when evolving interfaces in existing codebases, as adding a new method to an interface would force all implementing classes to provide an implementation,

Standard library example:

a new method is introduced to a widely used built-in interface like List. Without default methods, every class that implements List would need to be updated to provide an implementation for the new method.

Example

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

public class IterableExample {
    public static void main(String[] args) {
        List<String> myList = Arrays.asList("Java", "is", "powerful", "and", "flexible");

        // Using forEach method added to the Iterable interface
        myList.forEach(System.out::println);
    }
}
//In this example, the forEach method is called on the List class, which implements the Iterable interface.
//The addition of forEach as a default method in the Iterable interface made it possible to perform concise iteration on any class implementing the Iterable interface without the need for boilerplate code.

What key issues it addressed for interface?

  1. Backward Compatibility: Existing code that implements an interface isn’t required to provide an implementation for new default methods. This ensures backward compatibility with classes that were written before the introduction of the new methods.
  2. Library Evolution: Developers can add new methods to interfaces in standard Java libraries without breaking existing code that relies on those interfaces.
  3. Functional Interfaces and Lambda Expressions: Default methods played a crucial role in the adoption of lambda expressions and the functional programming paradigm in Java 8. Functional interfaces are interfaces with a single abstract method, and default methods allow these interfaces to provide additional utility methods without violating their functional nature.


Functional Interface

Index

  1. Introduction
  2. Representation
  3. FunctionalInterfaces
    1. Predicates
    2. Function
    3. Supplier
    4. Consumer

To ensure that your interface meet the requirements, you should add the @FunctionalInterface annotation. The compiler is aware of this annotation and throws a compiler error as soon as you try to add a second abstract method declaration to the interface.

Example:

@FunctionalInterface
interface Converter {
T convert(F from);
}

Built-in Functional Interfaces (from java.util.function package)

The JDK 1.8 API contains many built-in functional interfaces. Some of them are well known from older versions of Java like Comparator or Runnable. Those existing interfaces are extended to enable Lambda support via the @FunctionalInterface annotation.

Broadly the functional interface can be categorized in following types-

  1. Predicates
  2. Functions
  3. Suppliers
  4. Consumers

Predicates- Predicates are boolean-valued functions of one argument. The interface contains various default methods for composing predicates to complex logical terms (and, or, negate)

//Syntax : Predicate<TYPE> predicate= [lambda-function with condition on TYPE] 
//Example-1
Predicate<String> predicate = (s) -> s.length() > 0;

predicate.test("foo");              // true
predicate.negate().test("foo");     // false
//Example2
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

//Example3 - external Method
    import java.util.function.Predicate;  
    public class PredicateInterfaceExample {  
        static Boolean checkAge(int age){  
            if(age>17)  
                return true;  
            else return false;  
        }  
        public static void main(String[] args){  
            // Using Predicate interface  
            Predicate<Integer> predicate =  PredicateInterfaceExample::checkAge;  
            // Calling Predicate method  
            boolean result = predicate.test(25);  
            System.out.println(result);  
        }  
    }  -

Functions-Functions accept one argument and produce a result. Default methods can be used to chain multiple functions together (compose, andThen).

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123");     // "123"

Suppliers – Suppliers produce a result of a given generic type. Unlike Functions, Suppliers don’t accept arguments.

Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person

Consumers– Consumers represents operations to be performed on a single input argument.

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));



:: Operator – Method and Constructor References

Index

  1. Introduction
  2. Representation

Java 8 enables you to pass references of methods or constructors via the :: keyword.
The below example shows how to reference a static method. But we can also reference object methods:

Converter<String, Integer> converter = Integer::parseInt;
Integer converted = converter.convert("123");
System.out.println(converted);   // 123

Optional -Handles the Null values

Index

  1. Introduction
  2. Representation
	private static String someString;// = "someString";
	public static void main(String arg[]) {
		Optional<String> optinalString = Optional.ofNullable(someString); // of(someString);
		if (optinalString.isPresent()) {//If the Optional contains a non-null value, isPresent() returns true.
			System.out.println(optinalString.get());
		}
		// System.out.println(optinalString.isPresent());
		System.out.println(optinalString.orElse("DefaultString"));
	}

Programming Questions – java -8 features

  1. Basic Lambda Expression:
    • Write a lambda expression that takes two integers and returns their sum.
  2. Stream Filtering:
    • Given a list of strings, use streams to filter out strings that start with the letter “A” and print the remaining strings.
  3. Mapping with Lambdas:
    • Given a list of integers, use streams to square each integer and store the results in a new list.
  4. Combining Filters and Mapping:
    • Given a list of persons (similar to the previous example), use streams to filter out persons with an age less than 25 and print their names.
  5. Grouping with Streams:
    • Given a list of persons, use streams to group them by age and print the groups.
  6. Sorting with Streams:
    • Given a list of strings, use streams to sort them alphabetically and print the sorted list.
  7. Collectors and Joining:
    • Given a list of strings, use streams and collectors to concatenate all the strings into a single comma-separated string.
  8. Parallel Streams:
    • Write a program that demonstrates the use of parallel streams to process a large list of numbers and print the result.
  9. FlatMap Operation:
    • Given a list of lists of integers, use streams and flatMap to flatten the lists and print the unique integers.
  10. Exception Handling with Streams:
    • Write a program using streams that reads a list of numbers as strings, converts them to integers, and handles any NumberFormatException gracefully, printing an error message for invalid input.

Programs:

   /**
     * 1.Write a lambda expression that takes two integers and returns their sum.
     * @return
     */
    static int returnSumOFwoInteger(){
        int a=5;
        int b=10;
        SumFunction sumF=(x,y)->x+y;
        return  sumF.sum(a,b);
    }
//Helper Functional-interface
@FunctionalInterface
interface SumFunction{
    int sum(int a,int b);
}

 /**
     * 2.Given a list of strings, use streams to filter out strings that start with the letter "A" and print the remaining strings.
     * @param 
     */
    private static void filterPrefixStingANdPringRest() {
        List<String> al= Arrays.asList("Akram","ABhijit","zimba");
        al.stream().filter(s->s.startsWith("A")).map(a->a.substring(1)).forEach(System.out::println);
    }
    /**
     * 3.Given a list of integers, use streams to square each integer and store the results in a new list.
     * @param 
     */
    private static void squareListOfIntegers() {
        List<Integer> arrINt=Arrays.asList(1,2,3,4,5);
        List<Integer>  res=  arrINt.stream().map(a->a*a).collect(Collectors.toList());
        //print
        System.out.println(res);
    }
   /**
     * 4.Given a list of persons (similar to the previous example), use streams to filter out persons with an age less than 25 and print their names.
     */
    private static void printPersonsNAmeshoseAgeAgreaterThan20() {
        List<Person> persons = Arrays.asList(
                new Person("Alice", 25),
                new Person("Bob", 30),
                new Person("Charlie", 28),
                new Person("David", 35)
        );
        persons.stream().filter(p->p.getAge()>26).map(Person::getName).forEach(System.out::println);
    }

//helper class
class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
    /**
     * 5.Given a list of persons, use streams to group them by age and print the groups.
     */
    private static void printPersonsGroupByAge() {
        List<Person> persons = Arrays.asList(
                new Person("Alice", 25),
                new Person("Bob", 30),
                new Person("Charlie", 22),
                new Person("David", 35),
                new Person("Eva", 30),
                new Person("Frank", 22)
        );
       Map<Integer,List<Person>> groupBYAge=persons.stream().collect(Collectors.groupingBy(Person::getAge));

       groupBYAge.forEach((age,group)->{
           System.out.println("age::"+age);
       group.forEach(p->System.out.println(p.getName()));
       });
    }

/**
*variation person with - 0-18 in a group - 19-35 in a group
*/

Map<String, List<Person>> pGroupMap=personList.stream().collect(Collectors.groupingBy((p)->{
			if(p.getAge()<18) {
				return "0-18";
			}
			if(p.getAge()>18&&p.getAge()<35) {
				return "19-35";
			}
			else {
				return "35+";
			}
			
		}));
		pGroupMap.forEach((group,p)->
		System.out.println(group+":"+p));	
};

/*by default the value of group is the object itself if none returned in secoind param  or else iof returned then that will be the group e.g*/
/*WAP in java 8 streams
class Resource {
int id;
String resourceName;
List skills;
}
count how many resources are their in each skillSet?
--
*/
public class Demo {
    public static void main(String[] args) {

        List<Resource> resourceList = getListOFResources();

        Map<String, Long> mapOFSkills = resourceList.stream().flatMap(r -> r.getSkills().stream()).collect(Collectors.groupingBy(skil -> skil, Collectors.counting()));
        mapOFSkills.forEach((group, rl) -> {
            System.out.println(group + " Group and resocues " + rl);
        });
    }

    private static List<Resource> getListOFResources() {
        Resource r1 = new Resource(1, "Sevaand", Arrays.asList("java", "spring", "springboot"));
        Resource r2 = new Resource(2, "aand", Arrays.asList("react", "spring", "springboot"));
        Resource r3 = new Resource(3, "Seva", Arrays.asList("typescript", "spring", "springboot"));
        Resource r4 = new Resource(4, "Shiva", Arrays.asList("javascript", "spring", "springboot"));
        Resource r5 = new Resource(5, "raavan", Arrays.asList("python", "spring", "springboot"));
        Resource r6 = new Resource(6, "Rama", Arrays.asList("python", "django"));
        Resource r7 = new Resource(7, "laskhamana", Arrays.asList("angular", "javascript", "typescript"));

        List<Resource> resourceList = new ArrayList<>();
        resourceList.add(r1);
        resourceList.add(r2);
        resourceList.add(r3);
        resourceList.add(r4);
        resourceList.add(r5);
        resourceList.add(r6);
        resourceList.add(r7);


        return resourceList;
    }
}

/*
output:
spring Group and resocues 5
angular Group and resocues 1
python Group and resocues 2
django Group and resocues 1
java Group and resocues 1
react Group and resocues 1
typescript Group and resocues 2
javascript Group and resocues 2
springboot Group and resocues 5

*/

 /**
     * 6.Given a list of strings, use streams to sort them alphabetically and print the sorted list.
     */
    private static void printSortedListedAlphabetically() {
        List<String> arrS=Arrays.asList(
                "Bullbog",
                "Abra",
                "Ka",
                "Dabra"
        );

        arrS.stream().sorted().forEach(System.out::println);

    }
//sorting in descending order
//string
 arrS.stream().sorted((s1,s2)->s2.compareTo(s1)).forEach(System.out::println);
//objects in descending order  
personList.stream().sorted((p1,p2)->p2.getName().compareTo(p1.getName())).forEach(p->System.out.println(p));
/*note :- 1.ByDefault sorted() on stream expects that the class should implement Comparable else you case use overloaded method sorted(Comparator<?> arg)
2.if class overrides comparable -& lambda also mention the sorted order by comparator - lambda's input will be given preference i.e i class has sort by age and lambda has sort by name - result will be sort by name*/

/*Also you can use the overloaded cmethod as follows : 
1.	personList.stream().sorted(Comparator.comparingInt(Person::getAge).reversed()).forEach(System.out::println);
2.		personList.stream().sorted(Comparator.comparing(Person::getName).reversed()).forEach(System.out::println);*/
  /**
     * 7.Given a list of strings, use streams and collectors to concatenate all the strings into a single comma-separated string.
     */
    private static void printListedOfStringAsSIgnleStringSpeatrtedByCOmma() {
        List<String> arrS=Arrays.asList(
                "Bullbog",
                "Abra",
                "Ka",
                "Dabra"
        );
        String commnSe=arrS.stream().collect(Collectors.joining(","));
        System.out.println(commnSe);
    }
   /**
     * 8.Write a program that demonstrates the use of parallel streams to process a large list of numbers and print the result as sum.
     */
    private static void processLargeListedOfIntWithParallelStream() {
        List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
       int sum= numberList.parallelStream().mapToInt(Integer::intValue).sum();
        System.out.println(sum);
    }

   /**
     *9.Given a list of lists of integers, use streams and flatMap to flatten the lists and print the unique integers.
     */
    private static void flatListOfLIstAndPrintUniqueValue() {
        List<List<Integer>> listOfLists = Arrays.asList(
                Arrays.asList(1, 2, 3),
                Arrays.asList(4, 5, 6),
                Arrays.asList(7, 8, 9)
        );
        listOfLists.stream().flatMap(List::stream).distinct().forEach(System.out::println);
    }

 /**
     *10.Write a program using streams that reads a list of numbers as strings, converts them to integers, and handles any NumberFormatException gracefully, printing an error message for invalid input..
     */   
 private static void handleExceptionWIthLambdaFOrStringToIntConversion() {
        List<String> numberStrings = Arrays.asList("10", "20", "30", "abc", "40");

            numberStrings.stream().map(Java8Features::convertToInt).filter(n -> n != null).forEach(System.out::println);

    }
//helper method in same class i.e Java8Features.java
   private static Integer convertToInt(String stringInterger) throws RuntimeException {
    try {
      return   Integer.parseInt(stringInterger);
    }catch (NumberFormatException nfe ){
        System.out.println("nfe"+nfe);
        return  null;

    }
    }