Here are 100 Java Programming and LLD Interview Questions Handbook with Answers:
These questions cover a range of topics within Java and its ecosystem. Being able to address them would demonstrate a deep understanding of Java’s inner workings, standard libraries, design patterns, and best practices.
- Describe the Java Memory Model.
- How does Java Garbage Collection work? Describe the major garbage collectors and their advantages/disadvantages.
- What are the differences between ArrayList and LinkedList?
- How does a HashMap work in Java?
- Discuss the differences between a HashMap, Hashtable, and ConcurrentHashMap.
- How would you design a thread-safe singleton in Java?
- Describe the Java object serialization mechanism. What are its pros and cons?
- Explain the difference between deep copy and shallow copy.
- How does the
synchronized
keyword work in Java? - Explain Java’s
volatile
keyword. - How would you handle deadlocks in Java?
- Describe the producer-consumer problem and implement a solution using Java’s
wait()
and notify()
methods. - How does Java’s try-with-resources work?
- What are Java annotations? How can they be useful in system design?
- How would you design a cache mechanism in Java?
- Explain the differences between Comparable and Comparator in Java.
- Describe the various access specifiers in Java.
- What are Java Streams? How can they help in processing collections?
- Explain how method overloading and method overriding work in Java.
- How can you avoid memory leaks in Java?
- Describe the difference between
final
, finally
, and finalize
. - What is the difference between checked and unchecked exceptions in Java?
- Explain Java Generics. Why are they used?
- Describe the Java class loading mechanism.
- Explain how reflection works in Java.
- What is the difference between a Java Class and a Java Object?
- How do you handle versioning in a Java-based REST API?
- Describe the differences between
String
, StringBuilder
, and StringBuffer
. - How can you make an immutable object in Java?
- How does autoboxing and unboxing work in Java?
- Describe the
ExecutorService
in Java and its advantages. - How would you design a rate limiter in Java?
- What are Java lambda expressions, and how can they be useful?
- Explain Java’s fork/join framework.
- Describe the difference between an abstract class and an interface in Java.
- How does Java’s
LinkedBlockingQueue
work? - What’s the purpose of Java’s
CountDownLatch
? - Describe the various types of reference objects in Java, like
SoftReference
, WeakReference
, and PhantomReference
. - How would you design a connection pool in Java?
- Explain the purpose of the
transient
keyword in Java. - How can you handle circular references in Java serialization?
- What are Java Enumerations and why are they useful?
- How would you design a logging framework in Java?
- Describe the Observer pattern and how it can be implemented in Java.
- Explain how Java’s
PriorityQueue
works. - What is the difference between
fail-fast
and fail-safe
iterators in Java? - Explain the Factory and Singleton design patterns and their implementation in Java.
- How do you ensure thread safety in a Java class?
- Describe Java’s AOP (Aspect-Oriented Programming).
- How does Java’s
ReentrantLock
work, and when would you use it? - What is the role of the
pom.xml
file in a Java project? - How does Java handle internationalization?
- Describe the contract between
hashCode
and equals
methods in Java. - How can Java interfaces be used to achieve multiple inheritance?
- Describe the Model-View-Controller (MVC) pattern in the context of a Java web application.
- How would you implement pagination in a Java-based web application?
- Explain the JDBC API in Java.
- How would you handle optimistic concurrency in Java?
- Describe how annotations are used in Spring or Hibernate.
- Explain the
JOIN
operation in SQL, and how you’d implement a JOIN-like operation in Java. - What is the purpose of Java’s
CompletableFuture
? - Describe Java’s
Date
and Time
API. How does it handle time zones? - How do you handle sensitive information like passwords in a Java application?
- Explain the process of Oauth 2.0 authentication in a Java web application.
- How does the Proxy design pattern work, and how is it used in Java’s Spring framework?
- How do you ensure that a Java application is scalable?
- Describe Java’s event-driven programming model.
- What are the challenges of using Java for microservices architecture, and how can they be overcome?
- How do Java threads communicate with each other?
- What are the benefits of using dependency injection in Java?
- Explain the life cycle of a servlet.
- How does the Strategy design pattern work, and where would you use it in a Java application?
- Explain the difference between session and cookie in a Java-based web application.
- What is the purpose of the Java Naming and Directory Interface (JNDI)?
- Describe the challenges of multi-threading in Java and how they can be mitigated.
- How can you design a chat system using Java’s WebSocket API?
- Describe the benefits of using a Java bytecode instrumentation tool.
- How does lazy loading work in the Hibernate framework?
- Describe the Command design pattern and its use cases in Java.
- How can you manage database migrations in a Java-based application?
- What is the use of the
super
keyword in Java? - Describe the difference between SOAP and RESTful web services in Java.
- How do you handle exceptions in a Java-based REST API?
- What is the purpose of the Java’s
Stream
API, and when would you use it? - Describe the Template Method design pattern in Java.
- How do you design an API rate limiter using Java’s
Semaphore
class? - What are the typical use cases for Java’s
ReentrantReadWriteLock
? - Explain the purpose of the
@Override
annotation in Java. - How do you manage transactions in a Java-based application using the Spring framework?
- Describe the process of object-relational mapping (ORM) in Java.
- How can you implement data compression in a Java application?
- Explain the difference between serialization and deserialization in Java.
- Describe how Java manages object lifecycles using constructors and destructors.
- How would you design an efficient autocomplete system for a search engine using Java?
- Explain how inversion of control works in Java’s Spring framework.
- Describe the Adapter and Bridge design patterns and their significance in Java.
- What are the performance considerations when using Java’s
Stream
API for large datasets? - How would you design a distributed message queue system in Java?
- Describe the significance of the Liskov Substitution Principle in Java.
- How do you handle versioning of serialized objects in Java?
–
Answers
I can provide detailed answers with code snippets for above mentioned questions.
Describe the Java Memory Model.
- Answer: The Java Memory Model (JMM) describes how threads in Java interact with memory, and it guarantees visibility and ordering of variable accesses. The JMM defines rules for reading and writing variables in a multi-threaded environment. Java’s
volatile
, synchronized
, and the various java.util.concurrent
classes are built on top of these guarantees.
How does Java Garbage Collection work? Describe the major garbage collectors and their advantages/disadvantages.
- Answer: Java Garbage Collection automatically reclaims memory by finding and deleting objects that are no longer reachable. Java has several garbage collectors:
- Serial GC: Uses a single thread for GC. Suitable for single-threaded apps.
- Parallel GC: Uses multiple threads for young generation GC, suitable for multi-threaded apps.
- CMS (Concurrent Mark-Sweep): Minimizes application pause times. It works concurrently with the application threads.
- G1 GC: Divides the heap into regions and performs garbage collection on a set of regions. It aims to achieve high throughput and low latency.
- Code: No code for this, as it’s more of an explanation of JVM internals. However, you can influence the choice of GC with JVM flags, e.g.,
-XX:+UseG1GC
for G1 GC.
What are the differences between ArrayList and LinkedList?
- Answer:
ArrayList
is backed by an array. It provides O(1) lookup time but O(n) for insertions/deletions in the worst case.LinkedList
is a double-linked list providing O(n) lookup time but O(1) for insertions/deletions if the node reference is known.
- Code:
1
2
3
4
5
6
7
8
9
| ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("Java");
arrayList.add("Python");
String firstElement = arrayList.get(0); // O(1) access time
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("Java");
linkedList.add("Python");
linkedList.removeFirst(); // O(1) removal time
|
How does a HashMap work in Java?
- Answer: A
HashMap
stores key-value pairs. It uses the key’s hashcode to determine where the value should be stored. If two keys have the same hashcode, a linked list is used to store the collided keys, though in more recent Java versions, this may turn into a balanced tree for large enough lists, improving worst-case performance. - Code:
1
2
3
4
| HashMap<String, Integer> map = new HashMap<>();
map.put("One", 1);
map.put("Two", 2);
int value = map.get("One"); // retrieves the value 1
|
Discuss the differences between a HashMap, Hashtable, and ConcurrentHashMap.
- Answer:
HashMap
: Allows one null key and multiple null values. It’s not synchronized, making it faster but not thread-safe.Hashtable
: Doesn’t allow any null key or value. It’s synchronized, making it thread-safe but slower than HashMap
.ConcurrentHashMap
: Segments the Map into different parts and locks only a particular segment during updates, allowing a certain level of concurrency. It doesn’t allow null keys or values.
- Code:
1
2
3
4
5
6
7
8
| HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("Key1", "Value1");
Hashtable<String, String> hashtable = new Hashtable<>();
hashtable.put("Key1", "Value1");
ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<>();
concurrentHashMap.put("Key1", "Value1");
|
How would you design a thread-safe singleton in Java?
- Answer: One way is to use the “Bill Pugh Singleton” method utilizing an inner static helper class.
- Code:
1
2
3
4
5
6
7
8
9
10
11
| public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
|
Describe the Java object serialization mechanism. What are its pros and cons?
- Answer: Java serialization is the process of converting an object into a stream of bytes to store or transmit. Deserialization is the reverse. Pros: Simplifies RPC, persistence. Cons: Performance, security risks, versioning issues.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| import java.io.*;
class Sample implements Serializable {
int value;
Sample(int v) {
this.value = v;
}
}
public class SerializationDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Sample inputObject = new Sample(5);
// Serialize
FileOutputStream fos = new FileOutputStream("data.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(inputObject);
oos.close();
fos.close();
// Deserialize
FileInputStream fis = new FileInputStream("data.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Sample outputObject = (Sample) ois.readObject();
ois.close();
fis.close();
}
}
|
Explain the difference between deep copy and shallow copy.
- Answer: A shallow copy replicates references to objects. A deep copy replicates referenced objects themselves.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| class ComplexObject implements Cloneable {
int[] data;
public ComplexObject(int size) {
data = new int[size];
}
// Shallow copy
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
// Deep copy (simplified for int array)
public ComplexObject deepCopy() {
ComplexObject newObj = new ComplexObject(data.length);
System.arraycopy(data, 0, newObj.data, 0, data.length);
return newObj;
}
}
|
Explain the synchronized
keyword in Java.
- Answer: The
synchronized
keyword provides a mechanism that prevents multiple threads from executing a method or block of code concurrently, ensuring thread safety. This is achieved by acquiring a lock before the synchronized code executes. - Code:
1
2
3
4
5
6
7
8
9
10
11
| public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
|
Explain Java’s volatile
keyword.
- Answer: The
volatile
keyword indicates that a field may be accessed by multiple threads. It ensures that all reads and writes go directly to and from main memory, guaranteeing visibility. It doesn’t provide atomicity. - Code:
1
2
3
4
5
6
7
8
9
10
11
| public class SharedResource {
private volatile boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
|
- How would you handle deadlocks in Java?
- Answer: Deadlocks occur when threads wait indefinitely for resources. Solutions include timeout mechanisms, ordered locks, and deadlock detection. Java also provides tools like
jstack
for diagnosing deadlocks. - Code: (Demonstrating ordered locks to avoid deadlock)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| public class Resource {}
public class Worker implements Runnable {
private final Resource resource1;
private final Resource resource2;
public Worker(Resource r1, Resource r2) {
this.resource1 = r1;
this.resource2 = r2;
}
public void run() {
synchronized(resource1) {
System.out.println("Locked resource1");
synchronized(resource2) {
System.out.println("Locked resource2");
}
}
}
public static void main(String[] args) {
Resource r1 = new Resource();
Resource r2 = new Resource();
Thread t1 = new Thread(new Worker(r1, r2));
Thread t2 = new Thread(new Worker(r2, r1)); // This will cause a potential deadlock if not handled
t1.start();
t2.start();
}
}
|
- Describe the producer-consumer problem and implement a solution using Java’s
wait()
and notify()
methods.
- Answer: The producer-consumer problem involves two processes, the producer and the consumer, who share a common buffer. The producer writes to the buffer while the consumer reads from it. The challenge is to ensure synchronization.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
| import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumer {
private final Queue<Integer> queue = new LinkedList<>();
private final int LIMIT = 5;
public void produce() throws InterruptedException {
int value = 0;
while (true) {
synchronized (this) {
while (queue.size() == LIMIT) {
wait();
}
queue.offer(value++);
System.out.println("Produced " + value);
notify();
Thread.sleep(1000);
}
}
}
public void consume() throws InterruptedException {
while (true) {
synchronized (this) {
while (queue.isEmpty()) {
wait();
}
int value = queue.poll();
System.out.println("Consumed " + value);
notify();
Thread.sleep(1000);
}
}
}
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
Thread producerThread = new Thread(() -> {
try {
pc.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumerThread = new Thread(() -> {
try {
pc.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producerThread.start();
consumerThread.start();
}
}
|
- How does Java’s try-with-resources work?
- Answer: The try-with-resources statement ensures that each resource declared in the try statement is closed at the end of the statement. Any class that implements the
AutoCloseable
or Closeable
interface can be used as a resource. - Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TryWithResourcesDemo {
public static void main(String[] args) {
String fileName = "sample.txt";
try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
- What are Java annotations? How can they be useful in system design?
- Answer: Annotations provide metadata about the program, which is not part of the program itself. They don’t change the action of a compiled program. They can be used by the compiler, runtime, frameworks, or even custom tools.
- Code:
1
2
3
4
5
6
7
8
9
| @interface CustomAnnotation {
String author() default "Unknown";
String date();
}
@CustomAnnotation(date = "2023-09-25")
public class AnnotatedClass {
//...
}
|
- How would you design a cache mechanism in Java?
- Answer: A simple cache mechanism can use a combination of a HashMap and a LinkedList (for maintaining order). Here’s a basic implementation of an LRU (Least Recently Used) cache:
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| import java.util.HashMap;
import java.util.LinkedList;
public class LRUCache<K, V> {
private final int capacity;
private final HashMap<K, V> map;
private final LinkedList<K> list;
public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new HashMap<>();
this.list = new LinkedList<>();
}
public V get(K key) {
if (map.containsKey(key)) {
list.remove(key);
list.addFirst(key);
return map.get(key);
}
return null;
}
public void put(K key, V value) {
if (map.containsKey(key)) {
list.remove(key);
} else if (list.size() == capacity) {
K lastKey = list.removeLast();
map.remove(lastKey);
}
map.put(key, value);
list.addFirst(key);
}
}
|
- Explain the differences between Comparable and Comparator in Java.
- Answer:
Comparable
: Used to define a natural order for objects of a class. The class itself implements the Comparable
interface.Comparator
: A separate entity (often an external class) that defines an order for the objects.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| class Person implements Comparable<Person> {
int age;
String name;
@Override
public int compareTo(Person other) {
return this.age - other.age;
}
}
import java.util.Comparator;
class PersonNameComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p1.name.compareTo(p2.name);
}
}
|
- Describe the various access specifiers in Java.
- Answer: Java has four access specifiers:
private
: Accessible only within the class.default
(or package-private): Accessible within the same package.protected
: Accessible within the same package and by subclasses.public
: Accessible from any other class.
- Code:
1
2
3
4
5
6
| public class AccessDemo {
private int privateVar;
int defaultVar;
protected int protectedVar;
public int publicVar;
}
|
- What are Java Streams? How can they help in processing collections?
- Answer: Java Streams represent a sequence of elements (e.g., from collections) that can be processed in parallel or sequentially. They allow for functional-style operations on data, enabling more readable and concise code.
- Code:
1
2
3
4
5
6
7
8
| import java.util.List;
import java.util.stream.Collectors;
List<String> names = List.of("Alice", "Bob", "Charlie", "David");
List<String> namesWithD = names.stream()
.filter(name -> name.startsWith("D"))
.collect(Collectors.toList());
System.out.println(namesWithD); // Outputs: [David]
|
- Explain how method overloading and method overriding work in Java.
- Answer:
- Overloading: Defining multiple methods in the same class with the same name but different parameters.
- Overriding: When a subclass provides a specific implementation for a method that is already defined in its superclass.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| class Demo {
// Method Overloading
void print(int i) {
System.out.println(i);
}
void print(String s) {
System.out.println(s);
}
// Method to be Overridden
void show() {
System.out.println("In parent class");
}
}
class ChildDemo extends Demo {
// Method Overriding
@Override
void show() {
System.out.println("In child class");
}
}
|
- How can you avoid memory leaks in Java?
- Answer: While Java has garbage collection, memory leaks can still occur due to:
- Holding long-lived references
- Static fields
- Caches
- Listeners & callbacks
- Native resources
To avoid these, you should:
- Nullify references when they’re no longer needed.
- Use WeakReferences for caches.
- Be cautious with static variables.
- Close resources like streams, connections, etc.
- Code: (Demonstrating the use of
WeakReference
to avoid potential leaks in cache) 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
class Cache<K, V> {
private final Map<K, WeakReference<V>> cacheMap = new HashMap<>();
public void put(K key, V value) {
cacheMap.put(key, new WeakReference<>(value));
}
public V get(K key) {
WeakReference<V> weakReference = cacheMap.get(key);
if (weakReference != null) {
return weakReference.get();
}
return null;
}
}
|
- Describe the difference between
final
, finally
, and finalize
.
- Answer:
final
: A keyword that can be applied to classes, methods, or variables. A final class cannot be subclassed, a final method cannot be overridden, and a final variable, once assigned, cannot be reassigned.finally
: Used in try-catch exception handling. It provides a block of code that is always executed, regardless of whether an exception occurred.finalize
: A method in the Object
class that gets called by the garbage collector on an object when garbage collection determines that there are no more references to the object.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| final class FinalClass {}
public class DemoClass {
final int finalVar = 10;
final void finalMethod() {
System.out.println("This is a final method.");
}
public void someMethod() {
try {
// some code that might throw an exception
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("This always gets executed.");
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("Finalize method called.");
}
}
|
- What is the difference between checked and unchecked exceptions in Java?
- Answer:
- Checked Exceptions: Are checked by the compiler at compile-time. Methods that produce such exceptions are required to either handle them or declare them using the
throws
keyword. Example: IOException
. - Unchecked Exceptions: Extend
RuntimeException
and are not checked during compile-time. They’re typically used for programming errors. Example: NullPointerException
.
- Code:
1
2
3
4
5
6
7
8
9
| public class ExceptionDemo {
public void checkedExceptionMethod() throws IOException {
throw new IOException("Checked exception");
}
public void uncheckedExceptionMethod() {
throw new ArithmeticException("Unchecked exception");
}
}
|
- Explain Java Generics. Why are they used?
- Answer: Java Generics allow types (classes and interfaces) to be parameters. It provides type-checking at compile time, ensuring type safety, eliminating the need for casting, and making code more readable.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| class Box<T> {
private T item;
public void set(T item) {
this.item = item;
}
public T get() {
return item;
}
}
public class GenericsDemo {
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.set("Hello, Generics");
System.out.println(stringBox.get());
}
}
|
- Describe the Java class loading mechanism.
- Answer: Java classes are loaded into the JVM dynamically, as they’re required by an application. This is done by the class loader subsystem. The process involves: Loading -> Linking (Verification, Preparation, and Resolution) -> Initialization.
- Bootstrap ClassLoader: Loads JDK internal classes, typically from rt.jar.
- Extensions ClassLoader: Loads jars from JDK extensions directory.
- System ClassLoader: Loads classes from the system classpath.
- Code: (A simple custom class loader example)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| import java.io.*;
public class CustomClassLoader extends ClassLoader {
public Class<?> findClass(String className) {
try {
byte[] classData = loadClassData(className);
return defineClass(className, classData, 0, classData.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private byte[] loadClassData(String className) throws IOException {
FileInputStream fis = new FileInputStream(className.replace('.', File.separatorChar) + ".class");
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = fis.read();
while (-1 != nextValue) {
byteStream.write(nextValue);
nextValue = fis.read();
}
fis.close();
return byteStream.toByteArray();
}
}
|
- Explain how reflection works in Java.
- Answer: Reflection allows Java code to inspect and manipulate the internal properties of Java classes, methods, fields, constructors, and interfaces at runtime. It’s a powerful tool but can compromise security and reduce performance.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
| import java.lang.reflect.Method;
public class ReflectionDemo {
public static void main(String[] args) {
Class<?> clazz = String.class;
// Print all methods of the String class
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
}
}
|
- What is the difference between a Java Class and a Java Object?
- Answer:
- Java Class: A blueprint or prototype that defines the variables (fields) and methods (functions) common to all objects of a certain kind. It’s a logical entity.
- Java Object: A physical and logical entity. An instance of a class. It’s created based on the blueprint provided by the class.
- Code:
1
2
3
4
5
6
7
8
9
10
| public class Dog { // Dog class
public void bark() {
System.out.println("Woof!");
}
public static void main(String[] args) {
Dog myDog = new Dog(); // myDog object of Dog class
myDog.bark();
}
}
|
- How do you handle versioning in a Java-based REST API?
- Answer: API versioning is crucial for backward compatibility when changes are introduced. Common strategies include:
- URI versioning:
/v1/users
, /v2/users
- Header versioning: Using custom headers, e.g.,
X-API-Version: v1
- Accept header versioning: Modifying the standard
Accept
header, e.g., Accept: application/vnd.company.app-v1+json
- Code: (Using URI versioning with Spring Boot)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/v1/users")
public String getUsersV1() {
return "User API v1";
}
@GetMapping("/v2/users")
public String getUsersV2() {
return "User API v2";
}
}
|
- Describe the differences between
String
, StringBuilder
, and StringBuffer
.
- Answer:
String
: Immutable character sequences. Every modification results in a new object.StringBuilder
: Mutable character sequences. Not thread-safe, but typically faster than StringBuffer
.StringBuffer
: Like StringBuilder
, but thread-safe.
- Code:
1
2
3
4
5
6
7
8
| String immutableString = "Hello, ";
immutableString += "World!"; // Creates a new string
StringBuilder stringBuilder = new StringBuilder("Hello, ");
stringBuilder.append("World!"); // Modifies the existing object
StringBuffer stringBuffer = new StringBuffer("Hello, ");
stringBuffer.append("World!"); // Modifies the existing object and is thread-safe
|
- How can you make an immutable object in Java?
- Answer: An immutable object is one whose state cannot change after it’s created. To create one:
- Make the class final, so it can’t be extended.
- Make all fields private and final.
- Don’t provide setters.
- Ensure deep copies in constructors and getters for fields pointing to mutable objects.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public final class ImmutablePerson {
private final String name;
private final int age;
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
|
- How does autoboxing and unboxing work in Java?
- Answer: Autoboxing is the automatic conversion of primitive types to their corresponding object wrapper classes. Unboxing is the reverse. This feature was introduced in Java 5.
- Code:
1
2
3
4
5
6
7
8
9
| public class BoxingDemo {
public static void main(String[] args) {
// Autoboxing
Integer boxedInt = 5; // int -> Integer
// Unboxing
int unboxedInt = boxedInt; // Integer -> int
}
}
|
- Describe the
ExecutorService
in Java and its advantages.
- Answer:
ExecutorService
is an interface in the java.util.concurrent
package that provides high-level replacement for the traditional way of managing threads in Java. Advantages include thread pooling, easy management of groups of asynchronous tasks, and resource optimizations. - Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
Runnable worker = new WorkerThread("" + i);
executor.execute(worker);
}
executor.shutdown();
while (!executor.isTerminated()) { }
System.out.println("Finished all threads");
}
}
class WorkerThread implements Runnable {
private final String command;
public WorkerThread(String command) {
this.command = command;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Start. Command = " + command);
processCommand();
System.out.println(Thread.currentThread().getName() + " End.");
}
private void processCommand() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
|
- How would you design a rate limiter in Java?
- Answer: A rate limiter limits the number of requests a user can send in a specific time window, e.g., 1000 requests/hour. A simple way to implement this is using a token bucket algorithm.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class RateLimiter {
private final int maxRequest;
private final long timeWindowInMillis;
private final AtomicInteger token;
private long lastRefillTime;
public RateLimiter(int maxRequest, long timeWindow, TimeUnit unit) {
this.maxRequest = maxRequest;
this.timeWindowInMillis = unit.toMillis(timeWindow);
this.token = new AtomicInteger(maxRequest);
this.lastRefillTime = System.currentTimeMillis();
}
public synchronized boolean allowRequest() {
refill();
if (token.get() > 0) {
token.decrementAndGet();
return true;
}
return false;
}
private void refill() {
long now = System.currentTimeMillis();
if (now - lastRefillTime > timeWindowInMillis) {
token.set(maxRequest);
lastRefillTime = now;
}
}
}
|
- How do Java Futures work?
- Answer: A
Future
represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result. - Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| import java.util.concurrent.*;
public class FutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> {
TimeUnit.SECONDS.sleep(2);
return 123;
});
System.out.println("Is the future done? " + future.isDone());
Integer result = future.get(); // This will block until the result is available
System.out.println("Is the future done? " + future.isDone());
System.out.println("Result: " + result);
executor.shutdown();
}
}
|
- Describe Java lambda expressions and their benefits.
- Answer: Lambda expressions, introduced in Java 8, are a way to represent instances of single-method interfaces (functional interfaces). They can be used primarily to define inline implementations of functional interfaces.
- Benefits: More readable and concise code, functional programming constructs, easier-to-use APIs (especially in collections and streams).
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import java.util.*;
public class LambdaDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Without lambda
Collections.sort(names, new Comparator<String>() {
public int compare(String a, String b) {
return b.compareTo(a);
}
});
// With lambda
Collections.sort(names, (a, b) -> b.compareTo(a));
}
}
|
- What are default methods in Java interfaces?
- Answer: Default methods, introduced in Java 8, allow developers to add new methods to interfaces with keeping backward compatibility. These methods have a default implementation and can be overridden by implementing classes.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| public interface Animal {
void sound();
default void breathe() {
System.out.println("Breathing...");
}
}
public class Dog implements Animal {
@Override
public void sound() {
System.out.println("Woof!");
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.sound();
dog.breathe();
}
}
|
- Explain the difference between
notify()
, notifyAll()
, and wait()
methods in Java.
- Answer:
notify()
: Wakes up a single waiting thread from the same object’s monitor.notifyAll()
: Wakes up all the waiting threads from the same object’s monitor.wait()
: Causes the current thread to wait until another thread invokes the notify()
or notifyAll()
method for the same object.
- These methods are used for inter-thread communication and synchronization.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| public class WaitNotifyDemo {
private final Object lock = new Object();
public void method1() {
synchronized (lock) {
try {
System.out.println("Method 1: Waiting...");
lock.wait();
System.out.println("Method 1: Resumed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void method2() {
synchronized (lock) {
System.out.println("Method 2: Running...");
lock.notify();
System.out.println("Method 2: Sent notification");
}
}
public static void main(String[] args) {
WaitNotifyDemo demo = new WaitNotifyDemo();
new Thread(demo::method1).start();
new Thread(demo::method2).start();
}
}
|
Sure, here are more questions and their explanations:
- How does the
volatile
keyword ensure visibility in Java?
- Answer: When a field is declared
volatile
, the Java Memory Model ensures that all reads of that field are read directly from the main memory, and all writes to that field are written directly to the main memory. This guarantees that changes made by one thread are visible to other threads immediately. - Code:
1
2
3
4
5
6
7
8
9
10
11
| public class SharedResource {
private volatile boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
|
- Describe how Java handles exception handling.
- Answer: Java uses a try-catch-finally construct for exception handling. Code that might throw exceptions is placed inside a
try
block. If an exception occurs, it’s caught in a corresponding catch
block. The finally
block, if present, always executes whether an exception occurred or not. - Code:
1
2
3
4
5
6
7
8
9
10
11
| public class ExceptionHandlingDemo {
public static void main(String[] args) {
try {
int result = 10 / 0; // This will throw an ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Caught an exception: " + e.getMessage());
} finally {
System.out.println("This will always run.");
}
}
}
|
- How would you handle thread-safe lazy initialization in Java?
- Answer: The Double-Checked Locking idiom combined with the
volatile
keyword is a common approach to ensure thread-safe lazy initialization in Java. - Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
|
- Explain the difference between
fail-fast
and fail-safe
iterators in Java collections.
- Answer:
fail-fast
: Iterators that throw a ConcurrentModificationException
if the underlying collection is modified while an iteration is in progress.fail-safe
: Iterators that work on a cloned copy of the underlying collection and thus do not throw any exception even if the underlying collection is modified.
- Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| import java.util.*;
public class IteratorDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
// Using a fail-fast iterator (from ArrayList)
for (String item : list) {
list.add("D"); // This will throw ConcurrentModificationException
}
// For fail-safe, consider using classes from java.util.concurrent package
}
}
|
Let me know if you want to proceed with more questions, or if there’s any specific area you’d like to focus on!
Stay tuned for other answers. :)
If you found this article helpful share this articles with others on LinkedIn, it motivates me to write more such useful articles in the future.
You can buy me a Coffee here ☕︎ https://www.buymeacoffee.com/acethecloud
Follow for regular awesome content and insights.