Java Quiz - Stream and Kafka
Streams
Stream problems - Find sum of even numbers from array.
Count the occurrence "apple" in the list.
List of employee sort by salary then sort by name.
Given a list of list put all the elements in the same list.
Reveal Answer
List<List<String>> skills = Arrays.asList(
Arrays.asList("java","Spring","SpringBoot"),
Arrays.asList("React","Kafka","Microservice"),
Arrays.asList("MVC","Design Pattern");
);
List<String> allSkills = skills.stream()
.flatMap(skillsSets -> skillsSet.stream())
.collect(Collectors.toList());
// With stream first will get one list, flatmap will combine the list in one list. `flatMap` converts nested lists into one continuous stream.
System.out.println(allSkills);
Find the skills starting with character 's'.
Age of an employee above 30.
Reveal Answer
Reveal Explanation
Apply `filter(x -> x > threshold)` to keep only higher ages.
Count to get the frequency of the string in the list.
Reveal Answer
Reveal Explanation
Frequency counting groups equal values and counts each group.
Reverse a list using stream.
Find employee with highest salary using Java 8.
Reveal Answer
class Employee {
int id;
String name;
double salary;
Employee(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
double getSalary() {
return salary;
}
}
List<Employee> empList = Arrays.asList(
new Employee(101, "Aman", 55000.0),
new Employee(102, "Riya", 72000.0),
new Employee(103, "Karan", 68000.0),
new Employee(104, "Neha", 91000.0),
new Employee(105, "Vijay", 88000.0)
);
Opional<Employee> empWithHigestSalary = empList.stream()
.sorted(Comparator.comparingDouble(Employee::getSalary)
.reversed())
.findFirst();
// Sort salaries in descending order and take the first employee.
**Q9. Find employee with second highest salary.**
Reveal Answer
Reveal Explanation
After sorting descending, skip one record and read the next.
**Q10. Why are streams called lazy?**
Reveal Answer
Stream are called lazy because intermediate operations are not evaluated unless terminal operation is invoked. They are only evaluated when a terminal operation is invoked. The operations are lazy, meaning they do not executed immediately.Reveal Explanation
Intermediate operations build a pipeline, and terminal operations trigger execution.
**Q11. How does streams work in Java 8?**
Reveal Answer
Java Stream is a pipeline of functions that can be evaluated. Java Stream is not a data structure and cannot mutate data, they can only transform data. Streams are built around its main interface, the Stream interface which was released in JDK 8. Three phases - Splitting, Applying and Combining. Elements of a stream is processed individually and then tey finally get collected.Reveal Explanation
A stream flows from source to intermediate steps to final terminal result.
Array contains duplicate element. Print the distinct element.
**Q12. What is Zookeeper in Kafka.**
Reveal Answer
Storig metadate - Zookeeper stores essential data for running a Kafka cluster, such as registered brokers, topic configuration and the current controller. Providing distributed coordination - Zooker acts as a centralized service that provides distributed coordination for applications deployed in a distributed system. Maintaining configurayion information - Zookeepers keeps tract of data related to Kafka topics, brokers, consumers. Fault tolerant - Zookeeper is highly available and can tolerate node failures. Consistency - Zookeper offers a cosistent view of the cluster to all clients.Reveal Explanation
ZooKeeper has been used for metadata and broker coordination in classic Kafka architecture.
How many partition we can create in kafka.
Under the cluster there is broker. Max partition per cluster - 200000 partitions per cluster. Minimum partitions per topic - 1 partition per topic. Partition count is not fixed and depends on broker capacity and cluster sizing.
Reveal Answer
Not fixed. Max pqrtitions per broker - 4000 partitions per broker.Under the cluster there is broker. Max partition per cluster - 200000 partitions per cluster. Minimum partitions per topic - 1 partition per topic. Partition count is not fixed and depends on broker capacity and cluster sizing.
What are nodes and how it scales up with number of nodes.
Scaling Up - Adding new nodes to a Kafka cluster increaes processing power, throughput, reduces latency. This is known as horizontal scaling. Scaling Down - Removing nodes from a Kafka cluster decreases processing power. Node Ids - Strimzi automatocally assigns node Ids starting from 0 and incrementing by one. You can also assign node Id ranges for each node pool. Node pools - You can configure node pools usng a custom resource called KafkaNodePool. This resource supports configuration options such as the number of replicas, storage configuration and resource requirements. Managed disk - You can use multiple disks to achieve 16Tb for each node in the cluster.
Reveal Answer
There are nodes inside Kafka. Node are server that can be added to a cluster to scale up processing power and capacity.Scaling Up - Adding new nodes to a Kafka cluster increaes processing power, throughput, reduces latency. This is known as horizontal scaling. Scaling Down - Removing nodes from a Kafka cluster decreases processing power. Node Ids - Strimzi automatocally assigns node Ids starting from 0 and incrementing by one. You can also assign node Id ranges for each node pool. Node pools - You can configure node pools usng a custom resource called KafkaNodePool. This resource supports configuration options such as the number of replicas, storage configuration and resource requirements. Managed disk - You can use multiple disks to achieve 16Tb for each node in the cluster.
Reveal Explanation
Adding nodes improves throughput and capacity, but requires balancing partitions and resources.
What is Insync Replica?
Reveal Answer
In kafka way to achive data consistency and fault tolerance is by using replication to make sure tat messages are not lost if a broker fails. Every partition of a Kafka topic is replicated across multiple brokers. An insync replica ISR is a set of replicas that are fully in sync and replica with the leader replica of a partition. To put it simple, ISRs are replicas that have fully uptodate with the leader and have the same data as the leader. Kafka replication models has leaders, followers, replication factor, ISR list.Reveal Explanation
ISR members are replicas that are sufficiently caught up with the leader.
How do you decide on how much memory your application will require on production?
Reveal Answer
To find application memory usage with JMeter you can - Go to Free Memory to check memory usage in a test Use the PerfMon Metrics Collector Listener to monitor more than 75 PerfMon metrics, including memory Calculate memory usage using the formula: (Used Memory/Total Memory) * 100 You can also use JMeter to: Identify an application's maximum operating capacity, Find bottlenecks, and Determine which element is causing system degradationReveal Explanation
Use load testing plus memory metrics to estimate steady-state usage and production headroom.
What happens if we do not override the equals() method and the hashCode() method in Java?
By default, the equals() method in Java, inherited from Object, checks for reference equality (whether two objects point to the same memory location). If not overridden, even if two objects have identical properties, they will not be considered equal unless they refer to the same memory location. hashCode() Method -
By default, the hashCode() method generates a hash code based on the memory address of the object. If not overridden, objects with identical properties may generate different hash codes, affecting their behavior in hash-based collections (like HashSet, HashMap)
The output.
Since equals() is not overridden, the two Person objects are not considered equal, even though they have identical properties.
The hashCode() method is also not overridden, so the two objects have different hash codes, leading to both being stored in the HashSet
Only doing the equal method.
Set size is 2.
Even though equals() is overridden, the default hashCode() still generates different hash codes for p1 and p2. Therefore, both objects are stored in the HashSet.
Overridding both the equal and hashCode().
When two object are equal() then the hashCode() should also be equal. When two hashCode is equal then the object may not be equal.
Reveal Answer
equals() Method -By default, the equals() method in Java, inherited from Object, checks for reference equality (whether two objects point to the same memory location). If not overridden, even if two objects have identical properties, they will not be considered equal unless they refer to the same memory location. hashCode() Method -
By default, the hashCode() method generates a hash code based on the memory address of the object. If not overridden, objects with identical properties may generate different hash codes, affecting their behavior in hash-based collections (like HashSet, HashMap)
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
System.out.println("p1.equals(p2): " + p1.equals(p2)); // Reference equality
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println("Set size: " + set.size()); // Unexpected behavior
}
}
import java.util.HashSet;
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println("Set size: " + set.size()); // Still unexpected behavior
}
}
import java.util.HashSet;
import java.util.Objects;
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println("Set size: " + set.size()); // Correct behavior
}
}
Why do we need Spring Boot when there is Spring.
Faster bootstrap - Auto-configuration reduces manual setup for common concerns such as web, data source, JPA, security, and messaging.
Less boilerplate - It follows convention over configuration, so developers spend less time wiring infrastructure and more time writing business logic.
Standalone deployment - Embedded servers such as Tomcat, Jetty, or Undertow allow the application to run as a self-contained service without managing a separate application server.
Dependency simplification - Starter dependencies such as `spring-boot-starter-web` and `spring-boot-starter-data-jpa` provide opinionated, compatible dependency sets.
Production readiness - Actuator adds health checks, metrics, monitoring endpoints, and operational visibility, which are essential in real systems.
Microservice friendliness - It works well with externalized configuration, containerized deployment, and Spring Cloud patterns such as config management, service discovery, and resilience.
Strong ecosystem alignment - It integrates cleanly with Spring Security, Spring Data, Spring Batch, Kafka, Redis, and other enterprise components.
In short, Spring Boot removes setup friction, standardizes application structure, and gives teams a faster path from development to production.
Reveal Answer
Spring Boot is used to make Spring application development faster, more consistent, and production-ready.Faster bootstrap - Auto-configuration reduces manual setup for common concerns such as web, data source, JPA, security, and messaging.
Less boilerplate - It follows convention over configuration, so developers spend less time wiring infrastructure and more time writing business logic.
Standalone deployment - Embedded servers such as Tomcat, Jetty, or Undertow allow the application to run as a self-contained service without managing a separate application server.
Dependency simplification - Starter dependencies such as `spring-boot-starter-web` and `spring-boot-starter-data-jpa` provide opinionated, compatible dependency sets.
Production readiness - Actuator adds health checks, metrics, monitoring endpoints, and operational visibility, which are essential in real systems.
Microservice friendliness - It works well with externalized configuration, containerized deployment, and Spring Cloud patterns such as config management, service discovery, and resilience.
Strong ecosystem alignment - It integrates cleanly with Spring Security, Spring Data, Spring Batch, Kafka, Redis, and other enterprise components.
In short, Spring Boot removes setup friction, standardizes application structure, and gives teams a faster path from development to production.
OOPs
Explain the polymorphism.
Reveal Answer
Compile-time Polymorphism - Achieved using method overloading (methods with the same name but different parameter lists). The method to be called is determined at compile-time. Runtime Polymorphism - Achieved using method overriding (methods with the same name and signature in a parent and child class). The method to be called is determined at runtime, based on the object's actual type. It is Dynamic Binding.
Method Overloading.
Return type can be same or different, method signature (name and parameter) should be unique. Java determines which method to call based on the method signature at compile time.
Reveal Answer
More than one method with same name as long as the method has different parameter lists (different number of parameter or different types of parameters). It is a compile time polymorphism.Return type can be same or different, method signature (name and parameter) should be unique. Java determines which method to call based on the method signature at compile time.
Explain Encapsulation.
Reveal Answer
Encapsulation is the concept of bundling data (fields) and methods (functions) that operate on the data into a single unit, typically a class. It also involves restricting direct access to some of the object's components, ensuring data security and integrity through Getter and Setter methods.
Method Overloading.
Reveal Answer
More than one method with same name as long as the method has different parameter lists (different number of parameter or different types of parameters). It is a compile time polymorphism. Return type can be same or different, method signature (name and parameter) should be unique. Java determines which method to call based on the method signature at compile time.
Explain Encapsulation.
Reveal Answer
Encapsulation is the concept of bundling data (fields) and methods (functions) that operate on the data into a single unit, typically a class. It also involves restricting direct access to some of the object's components, ensuring data security and integrity. Getter and setter.
Generics
Upper-Bounded Wildcard.
Syntax - ( extends Type>) - Restricts the unknown type to be a subtype (or the same type) of the specified class or interface. Useful when you want to read data of a specific type or its subtypes but do not want to modify the collection.
Lower-Bounded Wildcard
Syntax - super Type> - Restricts the unknown type to be a superclass (or the same type) of the specified class or interface.
Useful when you want to write data of a specific type or its subtypes into a collection.
WildCard Use Cases.
Generic Class.
Generic Method.
PECS Principle (Producer Extends, Consumer Super).
Producer: Use extends when you want to fetch data from a collection (Producer Extends)
Consumer: Use super when you want to insert data into a collection (Consumer Super)
Reveal Answer
Wildcards in Java are a feature of generics that provide flexibility in specifying the type of elements a generic class or method can operate on. Wildcards allow you to relax the type constraints when working with generics, enabling more flexible and reusable code. Types of WildCard. Unbounded Wildcard. Syntax - > - Represents any type. Useful when the type is not required and it is just reading data or calling methods that don't depend on the type.public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
List<Integer> intList = List.of(1, 2, 3);
List<String> strList = List.of("A", "B", "C");
printList(intList); // Works
printList(strList); // Works
public double sumOfNumbers(List<? extends Number> list) {
double sum = 0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
List<Integer> intList = List.of(1, 2, 3);
List<Double> doubleList = List.of(1.1, 2.2, 3.3);
System.out.println(sumOfNumbers(intList)); // Outputs: 6.0
System.out.println(sumOfNumbers(doubleList)); // Outputs: 6.6
Useful when you want to write data of a specific type or its subtypes into a collection.
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Number> numberList = new ArrayList<>();
addNumbers(numberList); // Works
System.out.println(numberList); // Outputs: [1, 2]
class Box<T> {
private T value;
public void setValue(T value) { this.value = value; }
public T getValue() { return value; }
}
Box<? extends Number> box = new Box<>();
public static void copy(List<? super Integer> dest, List<? extends Integer> src) {
for (Integer i : src) {
dest.add(i);
}
}
Producer: Use extends when you want to fetch data from a collection (Producer Extends)
Consumer: Use super when you want to insert data into a collection (Consumer Super)
Difference between HashSet and TreeSet.
Reveal Answer
The HashSet and TreeSet classes in java are the implementation of Set interface.| Feature | HashSet | TreeSet |
|---|---|---|
| Performance | Faster (O(1)) | Faster (O(log n)) |
| Order | Unordered. No guarantee of order; it depends on the hash function. | Sorted in natural order or custom order. The order depends on the natural ordering of elements via Comparable or a custom Comparator. |
| Iteration | Iterates in no specific order. | Iterates in ascending sorted order, or in the order defined by a custom comparator. |
| Null Values | Allows one null value. |
Does not allow null. |
| Internal Data Structure | Uses HashMap internally for storage. |
Uses a Red-Black Tree (self-balancing binary search tree) internally. |
| Sorting | Custom sorting is not supported. | Custom sorting is supported via Comparator. |
TreeSet<Integer> treeSet = new TreeSet<>((a, b) -> b - a); // Descending order
treeSet.add(10);
treeSet.add(5);
treeSet.add(20);
System.out.println(treeSet); // Output: [20, 10, 5]
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("Apple");
treeSet.add("Banana");
treeSet.add("Orange");
System.out.println("TreeSet: " + treeSet); // Sorted order
HashSet<String> hashSet = new HashSet<>();
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Orange");
System.out.println("HashSet: " + hashSet); // Order not guaranteed
Difference between HashMap and TreeMap.
Reveal Answer
| Feature | HashMap | TreeMap |
|---|---|---|
| Performance | O(1) average time complexity for basic operations. | O(log n) time complexity for basic operations. |
| Order | No fixed order. | Maintains keys in sorted order, either natural ordering or via a custom comparator. |
| Null Key / Values | Allows one null key and multiple null values. |
Does not allow null keys, but allows multiple null values. |
| Internal Data Structure | Hash table. | Red-Black Tree (self-balancing binary search tree). |
Difference between HashMap and Hashtable.
Reveal Answer
| Feature | HashMap | Hashtable |
|---|---|---|
| Thread Safety | Not thread-safe. It needs external synchronization, such as Collections.synchronizedMap(), in multithreaded environments. |
Thread-safe because synchronization is built into its methods. |
| Null Key / Values | Allows one null key and multiple null values. |
Does not allow null keys or null values and throws NullPointerException. |
| Performance | Usually faster because there is no synchronization overhead. | Usually slower because synchronization adds overhead. |
| Package / History | Introduced in Java 1.2 as part of the Java Collections Framework in java.util. Preferred in modern code. |
Legacy class from Java 1.0 in java.util. Generally not recommended in new code. |
| Usage | Preferred in single-threaded code or when synchronization is handled externally. | Rarely used in modern applications; ConcurrentHashMap is usually preferred for thread-safe access. |
Difference between Map and FlatMap in streamAPI.
FLATMAP.
**Purpose**: Transforms each element into a stream of elements and then flattens these multiple streams into a single stream.
**Output**: Produces a new stream where each element may expand into multiple elements (or none).
**Behavior**: Each element is mapped to a stream, and the resulting streams are merged into a single stream.
**Use Case**: When you need to handle nested structures or when each element can be expanded into multiple elements (like splitting strings or unwrapping lists).
Using map and flatmap.
Map transform each item in a collection into something else and produces a collection of the same size.
Transforms each element of the stream into another form (1-to-1 mapping). The result is a Stream of Streams if the transformation returns a Stream.
Use when you want to transform each element independently, and the transformation results in a single output per input. Example: Converting a list of strings to their lengths.
Flatmap transforms each item but can combine items from nested collections into a single flat collections.
Transforms each element into a Stream and flattens all these Streams into a single Stream (1-to-many mapping).
Use when each element needs to be transformed into multiple elements (or a stream of elements) and you want a flat result. Example: Splitting a list of sentences into words.
Reveal Answer
MAP. **Purpose**: Transforms each element in the stream into another element. **Output**: Produces a new stream of the same size but with transformed elements. **Behavior**: Applies the given function to each element, and the result is a stream of transformed values. **Use Case**: When you need to transform each element into a single element.List<String> list = Arrays.asList("apple", "banana", "cherry");
list.stream()
.map(String::toUpperCase) // Transforms each string to uppercase
.forEach(System.out::println); // Outputs: APPLE, BANANA, CHERRY
List<String> list = Arrays.asList("apple", "banana", "cherry");
list.stream()
.flatMap(s -> Arrays.stream(s.split(""))) // Splits each string into a stream of characters
.forEach(System.out::println); // Outputs: a, p, p, l, e, b, a, n, a, n, a, c, h, e, r, r, y
List<String> list = Arrays.asList("apple", "banana");
// Using map to convert each word to uppercase
list.stream()
.map(word -> word.toUpperCase())
.forEach(System.out::println); // APPLE BANANA
// Using flatMap to split each word into its individual characters
list.stream()
.flatMap(word -> Arrays.stream(word.split("")))
.forEach(System.out::println); // a p p l e b a n a n a
List<String> words = Arrays.asList("hello", "world");
List<Stream<Character>> result = words.stream()
.map(word -> word.chars().mapToObj(c -> (char) c))
.collect(Collectors.toList());
// Result: [Stream[h, e, l, l, o], Stream[w, o, r, l, d]]
List<String> words = Arrays.asList("hello", "world");
List<Integer> lengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
// Result: [5, 5]
Difference between normal stream and parallel stream.
The example of a normal stream.
The output is given.
The output.
Reveal Answer
| Feature | Normal Stream | Parallel Stream |
|---|---|---|
| Execution | Processed sequentially, one element at a time, in source order. | Processed concurrently by splitting the source into chunks handled by multiple threads. |
| Performance | Best for smaller datasets or lightweight operations where parallel overhead is not worth it. | Can improve performance for large datasets or CPU-intensive work, but may be slower for small tasks because of parallel overhead. |
| Thread Management | Runs in a single thread, usually the main thread. | Uses the common ForkJoinPool, typically with worker threads based on available CPU cores. |
| Order | Maintains encounter order. | May not preserve order unless explicitly handled, for example with forEachOrdered(). |
| Usage | Best for simple and small operations where parallelism is unnecessary. | Best for large collections or time-consuming operations that benefit from parallel execution. |
| Processing | Processes all elements in order and commonly uses the main thread. |
Processes elements concurrently using multiple threads such as ForkJoinPool.commonPool-worker-*. |
List<String> items = Arrays.asList("A", "B", "C", "D", "E");
System.out.println("Normal Stream:");
items.stream()
.forEach(item -> {
System.out.println(Thread.currentThread().getName() + " processes " + item);
});
Difference between stream and collection.
Reveal Answer
| Feature | Stream | Collection |
|---|---|---|
| Nature | A stream is a sequence of elements that supports sequential or parallel data processing operations. | A collection is a data structure that stores objects in memory, such as List, Set, or Map. |
| Storage | Does not store data; it operates on a source such as a collection or array. | Stores data in memory. |
| Operation | Mainly used for transformation and processing operations like map, filter, and reduce. |
Manipulated through methods like add, remove, get, and iteration APIs. |
| Lazy Evaluation | Operations are lazy; execution happens only when a terminal operation is invoked. | Eager by nature; data is already stored and available immediately. |
| Consumption | Can be consumed only once; a new stream must be created for reuse. | Can be accessed multiple times without recreation. |
| Parallelism | Can be processed in parallel easily using parallelStream(). |
Does not provide built-in parallel processing in the same way streams do. |
| Use Case | Designed for data transformation and processing. | Designed for storing and managing data. |
Difference between ConcurrentHashMap and SynchronizedHashMap.
**ConcurrentHashMap**.
The ConcurrentHashMap class is part of the Java Collections Framework and extends the AbstractMap class. It implements the ConcurrentMap and Serializable interfaces. Below is the hierarchy:
**How ConcurrentHashMap Works Internally?**
It works on mainly 3 parts.
**Segmented Locking** - The map is divided into segments (buckets) internally.
Locking occurs at the segment level rather than the whole map, ensuring high concurrency. **CAS (Compare-And-Swap)** - Used for atomic updates without locks. Improves performance in high-concurrency scenarios. **Read-Write Operations** - Reads are generally lock-free, allowing for high throughput. Writes use fine-grained locking or CAS to minimize blocking. **Explain CAS** Compare-And-Swap (CAS) is an atomic operation used in concurrent programming to achieve synchronization without locks. It enables threads to update shared variables safely without the overhead and contention caused by traditional locking mechanisms. **Memory Location** - CAS reads the value at the memory location. **Expected Value** - It compares the read value with the expected value. If the current value matches the expected value, CAS updates the variable with the new value. If the current value does not match the expected value, CAS fails, and no update occurs. **New Value** - The operation returns a status indicating whether the swap was successful. Example - Thread A. Memory location 5. Value = 5. Expected value = 5. New Value = 10. The value is updated to 10.
Example - Thread B. Memory location 6. Value = 5. Expected value = 10. New Value = 15. The value is not updated as current value is 10. **Advantages of CAS**. Non-blocking - CAS ensures only the thread that successfully updates the variable proceeds. Other threads retry until they succeed, avoiding the need for locks. High Performance - Eliminates contention and overhead associated with locking. Particularly useful in high-concurrency scenarios. Atomicity - The comparison and update occur as a single, indivisible operation. Ensures thread safety. **Disadvantages of CAS**. ABA Problem - If a variable changes from value A to B and back to A, CAS may incorrectly assume nothing changed. Solution: Use a version number or timestamp alongside the variable. Busy-Waiting - If many threads are competing, repeated retries can cause performance degradation. Limited Use - Works well for single variable updates but becomes complex for larger data structures or multiple variables. Iteration in ConcurrentHashMap does not throw a `ConcurrentModificationException` even if the map is modified during the iteration.
Synchronized HashMap.
Iteration in SynchronizedHashMap requires explicit synchronization when accessed by multiple threads. Modifying the map during iteration will throw a ConcurrentModificationException unless you use explicit synchronization.
Reveal Answer
| Feature | ConcurrentHashMap | SynchronizedHashMap |
|---|---|---|
| Synchronization Mechanism | Uses segment-based locking (Java 7) or bucket-level locking (Java 8+). | Entire map is locked for each operation using synchronized blocks. |
| Concurrency | Allows concurrent reads and writes by multiple threads; only writes to the same bucket are blocked. | Allows only one thread to access the map at a time. |
| Performance | Higher performance in multithreaded environments due to finer-grained locking. | Lower performance due to coarse-grained locking (locks the entire map). |
| Null Values | Does not allow null keys or values. |
Allows a single null key and multiple null values. |
| Thread Safety | Thread-safe for concurrent access with better scalability. | Thread-safe, but less efficient in high-concurrency scenarios. |
| Locking Granularity | Fine-grained locks improve throughput by reducing contention. | Coarse-grained locks block all threads accessing the map, even for independent operations. |
| Iteration Behavior | Does not throw ConcurrentModificationException during iteration; reflects changes made by other threads. |
Throws ConcurrentModificationException if the map is modified during iteration. |
| Use Case | Best suited for high-concurrency applications where reads and updates are frequent. | Suitable for low-concurrency scenarios where simplicity is preferred over performance. |
java.lang.Object
└── java.util.AbstractMap<K, V>
└── java.util.concurrent.ConcurrentHashMap<K, V>
├── ConcurrentMap<K, V> (Interface)
└── Serializable (Interface)
Locking occurs at the segment level rather than the whole map, ensuring high concurrency. **CAS (Compare-And-Swap)** - Used for atomic updates without locks. Improves performance in high-concurrency scenarios. **Read-Write Operations** - Reads are generally lock-free, allowing for high throughput. Writes use fine-grained locking or CAS to minimize blocking. **Explain CAS** Compare-And-Swap (CAS) is an atomic operation used in concurrent programming to achieve synchronization without locks. It enables threads to update shared variables safely without the overhead and contention caused by traditional locking mechanisms. **Memory Location** - CAS reads the value at the memory location. **Expected Value** - It compares the read value with the expected value. If the current value matches the expected value, CAS updates the variable with the new value. If the current value does not match the expected value, CAS fails, and no update occurs. **New Value** - The operation returns a status indicating whether the swap was successful. Example - Thread A. Memory location 5. Value = 5. Expected value = 5. New Value = 10. The value is updated to 10.
Example - Thread B. Memory location 6. Value = 5. Expected value = 10. New Value = 15. The value is not updated as current value is 10. **Advantages of CAS**. Non-blocking - CAS ensures only the thread that successfully updates the variable proceeds. Other threads retry until they succeed, avoiding the need for locks. High Performance - Eliminates contention and overhead associated with locking. Particularly useful in high-concurrency scenarios. Atomicity - The comparison and update occur as a single, indivisible operation. Ensures thread safety. **Disadvantages of CAS**. ABA Problem - If a variable changes from value A to B and back to A, CAS may incorrectly assume nothing changed. Solution: Use a version number or timestamp alongside the variable. Busy-Waiting - If many threads are competing, repeated retries can cause performance degradation. Limited Use - Works well for single variable updates but becomes complex for larger data structures or multiple variables. Iteration in ConcurrentHashMap does not throw a `ConcurrentModificationException` even if the map is modified during the iteration.
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// Thread to modify the map
new Thread(() -> map.put("D", 4)).start();
// Iterating over the map.
// It can modify the map while iterating.
for (String key : map.keySet()) {
System.out.println("Key: " + key + ", Value: " + map.get(key));
// Simulate adding a new key during iteration
if (key.equals("B")) {
map.put("E", 5); // Adding during iteration
}
}
System.out.println("Final Map: " + map);
}
public static void main(String[] args) {
Map<String, Integer> map = Collections.synchronizedMap(new HashMap<>());
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// Thread to modify the map
new Thread(() -> {
synchronized (map) {
map.put("D", 4);
}
}).start();
// Iterating over the map
synchronized (map) { // Explicit synchronization required
for (String key : map.keySet()) {
System.out.println("Key: " + key + ", Value: " + map.get(key));
// Attempting to modify during iteration
if (key.equals("B")) {
map.put("E", 5); // This may throw ConcurrentModificationException
}
}
}
System.out.println("Final Map: " + map);
}
Difference between StringBuilder and StringBuffer and String.
Not Thread safe and not synchronized.
Slower as it will create new string. **StringBuilder**. Mutable. Allows in place modification in string.
Not thread safe and not synchronized.
Faster than StringBuffer as no synchronization.
Used in single threaded environment with frequent string modification. **StringBuffer**.
Mutable and Thread safe. Synchronization ensures thread safety.
Reveal Answer
**String**. Immutable. Any change will create a new String.Not Thread safe and not synchronized.
Slower as it will create new string. **StringBuilder**. Mutable. Allows in place modification in string.
Not thread safe and not synchronized.
Faster than StringBuffer as no synchronization.
Used in single threaded environment with frequent string modification. **StringBuffer**.
Mutable and Thread safe. Synchronization ensures thread safety.
Difference between default and static method.
Default Method.
Static Method.
Reveal Answer
| Feature | Default Method | Static Method |
|---|---|---|
| Definition | A method with a body in an interface, invoked on an instance. | A method declared with the static keyword, invoked on the class. |
| Purpose | Provides a default implementation for interface methods, ensuring backward compatibility. Works with instance variables and methods. | Defines utility or helper methods unrelated to instance-specific behavior. Works only with static data and does not access instance variables or methods. |
| Inheritance | Can be inherited and overridden by implementing classes. | Cannot be overridden but can be hidden (if declared in a class). |
Difference between Future and CompletableFuture, Runnable and Callable.
Reveal Answer
| Feature | Future | CompletableFuture |
|---|---|---|
| Core Idea | Represents the result of an asynchronous computation. | Extends Future with composition and callback APIs for async workflows. |
| Result Handling | Mostly blocking retrieval via get(). |
Provides non-blocking callbacks like thenAccept(), thenApply(), and thenCompose(). |
| Composition | No built-in fluent chaining for multiple async stages. | Supports fluent chaining, combining, and dependent async operations. |
| Exception Handling | No rich built-in exception pipeline; handling is typically done around get(). |
Built-in exception handling via exceptionally(), handle(), and whenComplete(). |
| Feature | Runnable | Callable |
|---|---|---|
| Return Value | Returns no result (void). |
Returns a result of type V. |
| Exception Support | Cannot throw checked exceptions from run(). |
Can throw checked exceptions from call(). |
| Typical Use | Best for fire-and-forget tasks. | Best for tasks that compute and return a value. |
| Method Signature | public abstract void run(); |
V call() throws Exception; |
What is finally, finalize and final.
Purpose - Used for cleanup actions like closing files, releasing resources, or disconnecting from a database.
Key Points - The finally block executes even if the try block contains a return statement. It does not execute if the JVM terminates abruptly (e.g., with System.exit())
The output.
finalize - A method in the Object class that can be overridden to clean up resources before an object is garbage collected.
Purpose - Used for resource management (like closing files or releasing memory), though it's not recommended for modern applications.
Deprecated - As of Java 9, finalize() is deprecated due to performance issues and unpredictability.
Key Points - Called by the garbage collector before the object is destroyed. Not guaranteed to execute promptly or even at all.
The output
final - A keyword that can be applied to variables, methods, or classes to restrict their behavior.
Purpose: Used to define constants, prevent method overriding, and prevent inheritance.
Key Points:
Final Variables: Once assigned, their value cannot change.
Final Methods: Cannot be overridden in subclasses.
Final Classes: Cannot be extended by other classes
Reveal Answer
finally - A block in a try-catch statement that always executes, regardless of whether an exception is thrown or not.Purpose - Used for cleanup actions like closing files, releasing resources, or disconnecting from a database.
Key Points - The finally block executes even if the try block contains a return statement. It does not execute if the JVM terminates abruptly (e.g., with System.exit())
public class FinallyExample {
public static void main(String[] args) {
try {
int result = 10 / 0; // Will throw ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Exception caught: " + e.getMessage());
} finally {
System.out.println("This block always executes.");
}
}
}
finalize - A method in the Object class that can be overridden to clean up resources before an object is garbage collected.
Purpose - Used for resource management (like closing files or releasing memory), though it's not recommended for modern applications.
Deprecated - As of Java 9, finalize() is deprecated due to performance issues and unpredictability.
Key Points - Called by the garbage collector before the object is destroyed. Not guaranteed to execute promptly or even at all.
public class FinalizeExample {
@Override
protected void finalize() throws Throwable {
System.out.println("Finalize method called.");
}
public static void main(String[] args) {
FinalizeExample obj = new FinalizeExample();
obj = null; // Make the object eligible for garbage collection
System.gc(); // Suggest garbage collection
System.out.println("End of main method.");
}
}
Collection Hierarchy.
Reveal Answer
String Interning.
How string intern works. A pool of unique string literals is maintained in the JVM. This pool is part of the heap memory often called the String Intern Pool or String Constant pool. We can manually ensure a string is part of the pool using the `String.intern()` method. String already present in the pool then the intern() returns a referenced to the pooled string. Not present then the string is added to the pool and the referenced to the pooled string is returned.
Comparison.
When the string is interned then == is faster and string not interned then we have to use .equals
Reveal Answer
String interning is a process of reusing strings to optimize memory usage. Strings are immutable in java in order to avoid the duplicate string with same value, Java uses string pool. String pool is a special area in the heap memory.How string intern works. A pool of unique string literals is maintained in the JVM. This pool is part of the heap memory often called the String Intern Pool or String Constant pool. We can manually ensure a string is part of the pool using the `String.intern()` method. String already present in the pool then the intern() returns a referenced to the pooled string. Not present then the string is added to the pool and the referenced to the pooled string is returned.
String str = "hello"; // Added to the pool
String str1 = "hello"; // Refering to the same object as str.
// str and str1 points to the same object inside the pool.
String str3 = new String("hello");
// String object is created inside the heap memory outside the pool.
// We can move it to the pool.
str3 = str3.intern(); // Add str3 to the pool or returns reference to the pooled string.
How the `@Autowired`, `@Resource` and `@Inject` differs from each other.
Reveal Answer
Used for dependency injection and they differs in terms of usage, behaviour and source. | `@Autowired` | `@Resource` | `@Inject` | |-----------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------| | Spring Specific and it does not works outside. Comes from Spring Framework. Works by **type**(bean type). | Java Standard and works both in spring and Java EE frameworks. Works by **name** first and then by type. | Java Standard and works with Java framework and Spring. Works by **type**. | |Behaviour - Spring attempts to match the bean type for injection. If multiple beans of the same type exists, it requires additional qualifiers(@Qualifier) to resolve ambiguity. Can be used on contructors, fields or setter method. Required Behaviour - By default @Autowired is required. If no matching bean is found it throws an exception. `@Autowired(required = false)` to make it optional. | When the name is specified (`@Resource(name = "beanName")`) then it searched for the bean with that name. No name is specified then it falls back to the field name. When not resolved then it falls back to teh type based injection. It does not supports @Qualifier. | Optional and no bean is found then it does not throw an exception by default. Does not supports @Qualifier but works with the @Named qualifier for ambiguity.|public static void main(String[] args) {
Integer num = 10;
modify(num);
System.out.println(num);
}
public static void modify(Integer num) {
num = 200;
}
Reveal Answer
The output is 10. Java is pass by value. Primitive are pass by value. Wrapper class will work but not Integer as Integer is immutable. Wrapper class with immutable like `AtomicInteger` or custom Wrapper class will work. We can reassign. The object will be created. We cannot see the memory of the object. The hashcode is the unique for the object.class Employee {
Address address;
String name;
int age;
}
class Address {
String streetNAme;
String place;
int pinCode;
}
public static void main(String[] args) {
// Sample Data
List<Employee> employees = Arrays.asList(
new Employee("John", 28, new Address("1st Main", "CityA", 560001)),
new Employee("Alice", 32, new Address("2nd Cross", "CityB", 560002)),
new Employee("Bob", 45, new Address("3rd Lane", "CityA", 560001)),
new Employee("Eve", 25, new Address("4th Street", "CityC", 560003))
);
}
Group the employee based on the age.
Reveal Answer
// Group employees by pinCode
Map<Integer, List<Employee>> groupedByPinCode = employees.stream()
.collect(Collectors.groupingBy(e -> e.address.pinCode));
// Print grouped employees
groupedByPinCode.forEach((pinCode, employeeList) -> {
System.out.println("PinCode: " + pinCode);
employeeList.forEach(System.out::println);
System.out.println();
});
PinCode: 560001
Employee{name='John', age=28, address=Address{streetName='1st Main', place='CityA', pinCode=560001}}
Employee{name='Bob', age=45, address=Address{streetName='3rd Lane', place='CityA', pinCode=560001}}
PinCode: 560002
Employee{name='Alice', age=32, address=Address{streetName='2nd Cross', place='CityB', pinCode=560002}}
PinCode: 560003
Employee{name='Eve', age=25, address=Address{streetName='4th Street', place='CityC', pinCode=560003}
Group the employee based on age.
What is the output?
The method is getting the value and not the reference so the value of the varaiable will not change.
public class A1 {
public static void addToInt(int x, int amountToAdd) {
x = x + amountToAdd;
}
public static void main(String[] args) {
var a = 15;
var b = 10;
A1.addToInt(a,b);
System.out.println(a);
}
}
Reveal Answer
The output of the code - 15.The method is getting the value and not the reference so the value of the varaiable will not change.
What are the new features introduced in Java 8.
Before Java 8 there was anonymous inner class to implement functional interface.
New one.
**Difference between lambda and anonymous class.**
Lambda - Short ideal for single method implementations of functional interfaces like Runnable, Callable, Comparator.
`Runnable r = () -> System.out.println("Lambda Example");`
It donot create a new class file. It use invoke dynamic bytecode instruction for functional interface implementation.
Lambda expressions work only with functional interfaces, they can't be used to extend classes or implement multiple methods.
Lambda in inline comparator.
Anonymous class - Verbose code. It is used to implement interfaces with multiple methods or extends classes.
Anonymous class extend class.
Anonymous class in sorting comparaators.
It creates a separate inner class file at runtime.
### Stream API.
Provides a functional approach to process collections, making operations like filtering, mapping and reduction easier.
**Default and static method in interfaces.**
### Optional Class.
Provides a container to handle nullable values and avoid NullPointerException.
It encourages functional programming like map, filter, ifPresent.
Methods in Optional.
isPresent() and ifPresent()
orElse() and orElseGet() and orElseThrow()
get(), filter(), map() and flatMap().
Example of using Optional.
Using Optional.
Date and Time API.
It is in java.time.Package It replaces the outdated `java.util.Date` and `java.util.Calendar` package.
Parallel Array Sorting.
Adds the Arrays.parallelSort() method for faster sorting using multiple thread.
Adding new collector in Stream API.
Add utilities like `Collectors.toMap`, `Collectors.groupingBy`, and `Collectors.partitioningBy` for aggregations.
Concurrency Enhancement.
Introduces CompletableFuture for Asynchronous programming.
Base 64 encoding and decoding.
Provides utility classes for Base 64 encoding and decoding.
Reveal Answer
The new features revolutionizes how java applications are written and optimized. Lambda Expression. Enable functional programming with concise code for implementing functional interfaces. It has 3 parts - Parameters, Arrow tokens, Body. It works in Functional Interfaces example Runnable, Callable, Comparator. Java 8 introduces `@FunctionalInterface` to enforce it.@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}
// Lambda implementation
MathOperation addition = (a, b) -> a + b;
System.out.println(addition.operate(5, 3)); // Output: 8
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Running in a thread");
}
};
new Thread(runnable).start();
Collections.sort(names, (o1, o2) -> o2.compareTo(o1)); // Reverse alphabetical order
System.out.println(names); // Output: [Zara, John, Jane, Adam]
Collections.sort(names, Comparator.reverseOrder());
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Anonymous Class Example");
}
};
class Animal {
void sound() {
System.out.println("Some generic animal sound");
}
}
Animal dog = new Animal() {
@Override
void sound() {
System.out.println("Dog barks");
}
};
dog.sound(); // Output: Dog barks
import java.util.*;
List<String> names = Arrays.asList("John", "Jane", "Adam", "Zara");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1); // Reverse alphabetical order
}
});
System.out.println(names); // Output: [Zara, John, Jane, Adam]
List<Integer> list = Arrays.asList(1, 2, 3, 4, 4, 56, 6, 7, 78, 89);
list.stream().filter(x->x%2==0).forEach(System.out::println);
// Empty optional.
Optional<String> empty = Optional.empty();
// Non Empty Optional.
Optional<String> name = Optional.of("John");
// Optional with Nullable value.
Optional<String> nullableName = Optional.ofNullable(null);
Optional<String> name = Optional.of("John");
// Check if value is present
if (name.isPresent()) {
System.out.println("Name: " + name.get());
}
// Perform action if value is present
name.ifPresent(value -> System.out.println("Name: " + value));
String value = nullableName.orElse("Default Name");
System.out.println(value); // Output: Default Name
String lazyValue = nullableName.orElseGet(() -> "Generated Default Name");
System.out.println(lazyValue);
String value = nullableName.orElseThrow(() -> new IllegalArgumentException("Value is missing!"));
String nameValue = name.get(); // It throws NoSuchElementException.
Optional<String> filtered = name.filter(n -> n.startsWith("J"));
filtered.ifPresent(System.out::println); // Output: John
Optional<Integer> length = name.map(String::length);
length.ifPresent(System.out::println); // Output: 4
Optional<Optional<String>> nestedOptional = Optional.of(Optional.of("Nested Value"));
Optional<String> flattened = nestedOptional.flatMap(value -> value);
flattened.ifPresent(System.out::println); // Output: Nested Value
public String getName(Person person) {
if (person != null) {
Address address = person.getAddress();
if (address != null) {
return address.getCity();
}
}
return "Unknown";
}
public String getName(Person person) {
return Optional.ofNullable(person)
.map(Person::getAddress)
.map(Address::getCity)
.orElse("Unknown");
}
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
Provides utility classes for Base 64 encoding and decoding.
Collection Hierarchy.
Reveal Answer
Compare 2 json values.