To Dos:
- Implementing Stack using other DS
- Use cases
- Post-fix expression evaluation
Index
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 :
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.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).
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:
Eager Fetching:
@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:
@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
});
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 is used to annotate a class whose instances are intended to be embedded into entities.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 is used to embed an @Embeddable object within an entity.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.
| 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. |
Index
Index (for more depth refer drive java-8book)
Index
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);
});
Index
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.
Stream obtained from a collection produces a new Stream without the filtered elements, rather than removing elements from the source collection.
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.
limit(n) or findFirst() can allow computations on infinite streams to complete in finite time.Iterator, a new stream must be generated to revisit the same elements of the source.
Collection via the stream() and parallelStream() methods;Arrays.stream(Object[]);Stream.of(Object[]), IntStream.range(int, int) or Stream.iterate(Object, UnaryOperator);BufferedReader.lines();Files;Random.ints();BitSet.stream(), Pattern.splitAsStream(java.lang.CharSequence), and JarFile.stream().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.
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.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.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.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.
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:
| Operation | Method Signature | Type | Description | Usage Example |
|---|---|---|---|---|
| filter | Stream<T> filter(Predicate<? super T> p) | Intermediate | Returns a stream of elements that match the given predicate. | |
| map | Stream<R> map(Function<? super T, ? extends R> mapper) | Intermediate | Transforms each element using the provided function. | stream.map(x -> x * 2).collect(Collectors.toList()); |
| flatMap | Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) | Intermediate | Similar 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()); |
| distinct | Stream<T> distinct() | Intermediate | Returns a stream of distinct elements. | stream.distinct().forEach(System.out::println); |
| sorted | Stream<T> sorted() | Intermediate | Returns a stream sorted according to the natural order of elements. | stream.sorted().forEach(System.out::println); |
| peek | Stream<T> peek(Consumer<? super T> action) | Intermediate | Applies the specified action to each element and returns a new stream. | stream.peek(x -> System.out.println("Processing: " + x)).count(); |
| forEach | void forEach(Consumer<? super T> action) | Terminal | Performs an action for each element of the stream. | stream.forEach(System.out::println); |
| toArray | Object[] toArray() | Terminal | Returns an array containing the elements of the stream. | Object[] array = stream.toArray(); |
| collect | R collect(Collector<? super T, A, R> collector) | Terminal | Performs a mutable reduction on the elements of the stream using a Collector. | List<Integer> list = stream.collect(Collectors.toList()); |
| reduce | Optional<T> reduce(BinaryOperator<T> accumulator) | Terminal | Performs 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); |
| count | long count() | Terminal | Returns the count of elements in the stream as a long. | long count = stream.count(); |
| anyMatch | boolean anyMatch(Predicate<? super T> predicate) | Terminal | Returns true if any elements of the stream match the given predicate. | boolean anyMatch = stream.anyMatch(x -> x > 5); |
| allMatch | boolean allMatch(Predicate<? super T> predicate) | Terminal | Returns true if all elements of the stream match the given predicate. | boolean allMatch = stream.allMatch(x -> x > 0); |
| noneMatch | boolean noneMatch(Predicate<? super T> predicate) | Terminal | Returns true if no elements of the stream match the given predicate. | boolean noneMatch = stream.noneMatch(x -> x == 0); |
| findFirst | Optional<T> findFirst() | Terminal | Returns the first element of the stream as an Optional. | Optional<Integer> firstElement = stream.findFirst(); |
| findAny | Optional<T> findAny() | Terminal | Returns any element of the stream as an Optional. | Optional<Integer> anyElement = stream.findAny(); |
Index
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?
Functional Interface
Index
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-
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
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
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
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;
}
}