Java interview question | Part-II
Created on: Jan 16, 2025
-
If we have two functional interfaces, A and B, can B extend A? What happens if we do so ?
Yes, B can extend A, and it will not cause a compilation error as long as B does not introduce a second abstract method.
@FunctionalInterface interface A { void methodA(); // Single abstract method in A } @FunctionalInterface interface B extends A { // No new abstract methods added } public class Solution { public static void main(String[] args) { // B inherits methodA from A B bInstance = () -> System.out.println("Implementation of methodA"); bInstance.methodA(); } }If B adds a new abstract method, it will no longer be a functional interface because it will have more than one abstract method. This would cause a compilation error if you try to use B in a lambda or method reference.
@FunctionalInterface interface A { void methodA(); // Single abstract method in A } @FunctionalInterface interface B extends A { void methodB(); } public class Solution { public static void main(String[] args) { } }java: Unexpected @FunctionalInterface annotation org.example.B is not a functional interface multiple non-overriding abstract methods found in interface org.example.B; -
What are the other places where we can use
synchronisedother than method.- Synchronised block
public class Test { public void performTask() { System.out.println("Task start"); synchronized (this) { System.out.println("Synchronized block"); } System.out.println("Task end"); } }- Custom object
private final Object lock = new Object(); public void performTask() { synchronized (lock) { System.out.println("Locked by custom object"); } }- Class lavel lock
public static void performStaticTask() { synchronized (MyClass.class) { System.out.println("Class-level lock"); } }- With static method
public static synchronized void performStaticTask() { System.out.println("Static synchronized method"); } -
What are stream in java 8.
A stream in Java is a sequence of objects that supports various methods. It can be pipelined to produce the desired result using different operation like filtering, mapping, reducing, and sorting.
-
What were the limitations of the Future interface that led to the introduction of CompletableFuture?
- Blocking on Result Retrieval
- No Callback Support
- Polling for Completion
- No Built-in Exception Handling
- No Chaining or Composition
-
Difference between Future and CompletableFuture.
Future:
- java 5
- asynchronous computation
- get() operation blocks the thread until computation is complete
- Does not provide built-in exception handling. Exceptions need to be handled separately when calling
get() - Relies on an external executor service for thread management.
CompletableFuture:
- Extends
Futurewith more functionality, like chaining and composing tasks. - Supports non-blocking operations like
thenApply(),thenAccept(), andthenRun(), allowing the computation to proceed asynchronously.
import java.util.concurrent.CompletableFuture; public class Test { public static void main(String[] args) { CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { if (true) throw new RuntimeException("Error!"); return 1; }).exceptionally(ex -> { System.out.println("Handled: " + ex.getMessage()); return 0; }); int res = future.join(); System.out.println("res: "+ res); // 2. Using thenApply() to transform the result CompletableFuture<String> transformedFuture = future.thenApply(result -> { System.out.println("Transforming result..."); return "Result is: " + (result * 2); }); // 3. Using thenAccept() to consume the result without returning anything transformedFuture.thenAccept(finalResult -> { System.out.println("Consuming the result..."); System.out.println(finalResult); }); // 4. Using thenRun() to execute a runnable without accessing the result transformedFuture.thenRun(() -> { System.out.println("Final task - no access to result."); }); // Ensuring main thread waits for async tasks transformedFuture.join(); // This blocks until all tasks are completed } }Handled: java.lang.RuntimeException: Error! res: 0 Transforming result... Consuming the result... Result is: 0 Final task - no access to result.- Supports composition via methods like
thenCombine(),thenCompose(), etc., enabling chaining of dependent tasks - Provides robust exception handling with methods like
handle(),exceptionally(), andwhenComplete()
import java.util.concurrent.CompletableFuture; public class Test { public static void main(String[] args) { // 1. Using thenCompose() to chain dependent futures CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> { System.out.println("Executing Task 1..."); return "Task 1 Result"; }).thenCompose(result -> CompletableFuture.supplyAsync(() -> { System.out.println("Using Task 1 result in Task 2..."); return result + " + Task 2 Result"; })); // 2. Using thenCombine() to combine two futures CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> { System.out.println("Executing Task 3..."); return "Task 3 Result"; }); CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> { System.out.println("Combining Task 1+2 and Task 3 results..."); return result1 + " + " + result2; }); // 3. Using handle() to handle result or exception CompletableFuture<String> handledFuture = combinedFuture.handle((result, ex) -> { if (ex != null) { System.out.println("Handling Exception: " + ex.getMessage()); return "Default Result"; } else { System.out.println("Handling Result: " + result); return result; } }); // 4. Using whenComplete() to perform a final action handledFuture.whenComplete((result, ex) -> { if (ex != null) { System.out.println("Completion with Exception: " + ex.getMessage()); } else { System.out.println("Completion with Result: " + result); } }); // Block to wait for the future to complete handledFuture.join(); } }Executing Task 1... Using Task 1 result in Task 2... Executing Task 3... Combining Task 1+2 and Task 3 results... Handling Result: Task 1 Result + Task 2 Result + Task 3 Result Completion with Result: Task 1 Result + Task 2 Result + Task 3 Result- Built-in support for running tasks in parallel using methods like
allOf()andanyOf() - Comes with its own thread pool (
ForkJoinPool) but also allows specifying custom executors.
import java.util.concurrent.CompletableFuture; import java.util.concurrent.*; import java.util.List; import java.util.stream.Collectors; public class Test { public static void main(String[] args) { // Custom Executor with a fixed thread pool of size 4 ExecutorService executor = Executors.newFixedThreadPool(3); try { // List of tasks to run in parallel List<CompletableFuture<String>> futures = List.of( CompletableFuture.supplyAsync(() -> { System.out.println(Thread.currentThread().getName() + " - Executing Task 1"); sleep(2); // Simulate a delay return "Task 1 Result"; }, executor), CompletableFuture.supplyAsync(() -> { System.out.println(Thread.currentThread().getName() + " - Executing Task 2"); sleep(1); // Simulate a delay return "Task 2 Result"; }, executor), CompletableFuture.supplyAsync(() -> { System.out.println(Thread.currentThread().getName() + " - Executing Task 3"); sleep(3); // Simulate a delay return "Task 3 Result"; }, executor) ); // Combine all results using CompletableFuture.allOf CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); // Wait for all tasks to complete and collect their results CompletableFuture<List<String>> resultsFuture = allOf.thenApply(v -> futures.stream() .map(CompletableFuture::join) // Join each future to get its result .collect(Collectors.toList()) ); // Get the results List<String> results = resultsFuture.join(); System.out.println("All Task Results: " + results); } finally { // Shutdown the executor executor.shutdown(); } } // Utility method to simulate delay private static void sleep(int seconds) { try { TimeUnit.SECONDS.sleep(seconds); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }pool-1-thread-2 - Executing Task 2 pool-1-thread-3 - Executing Task 3 pool-1-thread-1 - Executing Task 1 All Task Results: [Task 1 Result, Task 2 Result, Task 3 Result] -
What is difference between Comparable and comparator.
Aspect Comparable Comparator Purpose Defines the natural ordering of objects. Defines a custom ordering for objects. Interface Location Belongs to the java.langpackage.Belongs to the java.utilpackage.Method to Implement compareTo(Object o)compare(Object o1, Object o2)Sorting Logic Implemented in the class itself. Defined in a separate class or using a lambda/anonymous class. Flexibility Only one sorting logic can be implemented per class. Multiple sorting logics can be implemented in different comparators. Usage Suitable for natural ordering, like sorting numbers. Suitable for custom or multiple sorting criteria. -
Suppose there is a employee class which has attribute empId, name, salary. I have a list of employee object. Can we apply two comparator in stream api operation and sort on the basis of name and salary.
import java.util.*; import java.util.stream.Collectors; class Employee implements Comparable<Employee> { int empId; String name; double salary; public Employee(int empId, String name, double salary) { this.empId = empId; this.name = name; this.salary = salary; } public int getEmpId() { return empId; } public String getName() { return name; } public double getSalary() { return salary; } @Override public int compareTo(Employee o) { return Integer.compare(this.empId, o.empId); } @Override public String toString(){ return String.format("%s , %d , %2f", this.name,this.empId, this.getSalary()); } } public class Test { public static void main(String[] args) { List<Employee> employees = Arrays.asList( new Employee(101, "Alice", 60000), new Employee(102, "Bob", 55000), new Employee(103, "Alice", 75000), new Employee(104, "Charlie", 50000) ); List<Employee> sortedEmployee = employees.stream().sorted(Comparator.comparing(Employee::getSalary).thenComparing(Employee::getName)) .collect(Collectors.toList()); System.out.println(sortedEmployee); } } -
Difference between
ofandofNullablemethod in Optional method.Aspect Optional.of()Optional.ofNullable()Purpose Creates an Optionalfor a non-null value.Creates an Optionalfor a value that can be null.Null Handling Throws NullPointerExceptionif the value is null.Returns an empty Optionalif the value is null.Use Case Use when the value is guaranteed to be non-null. Use when the value might be null, and you want to avoid NPE. Code Example (Non-Null) Optional<String> opt = Optional.of("Hello");Optional<String> opt = Optional.ofNullable("Hello");Code Example (Null) Optional<String> opt = Optional.of(null); // Throws NPEOptional<String> opt = Optional.ofNullable(null); // Empty OptionalPreferred Usage For non-null values to ensure strict null safety. For values that might be null to gracefully handle nullability. -
How to maintain immutable nature of a class from cloning
@Override protected Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException("Cloning is not allowed for immutable classes"); } -
What are the important method in comparator interface ?
compare(T o1, T o2)reversed()thenComparing(Comparator<? super T> other)comparing(Function<? super T, ? extends U> keyExtractor)naturalOrder() and reverseOrder()nullsFirst(Comparator<? super T> comparator) and nullsLast(Comparator<? super T> comparator)
import java.util.*; public class Solution { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return Integer.compare(o1.length(), o2.length()); } }); System.out.println(names); // List<Integer> numbers = Arrays.asList(5, 2, 8, 1); numbers.sort(Comparator.<Integer>naturalOrder().reversed()); System.out.println(numbers); // Output: [8, 5, 2, 1] List<Person> people = Arrays.asList(new Person("Alice", 30), new Person("Bob", 25), new Person("Alice", 25)); // Sort by name, then by age people.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge)); people.forEach(System.out::println); // List<String> words = Arrays.asList("banana", "apple", "kiwi"); // Sort by string length words.sort(Comparator.comparing(String::length)); System.out.println(words); // Output: [kiwi, apple, banana] numbers.sort(Comparator.naturalOrder()); System.out.println(numbers); // Output: [1, 2, 5, 8] // Sort in reverse natural order numbers.sort(Comparator.reverseOrder()); System.out.println(numbers); // Output: [8, 5, 2, 1] List<String> names2 = Arrays.asList("Alice", null, "Bob", null, "Charlie"); // Sort with nulls first names2.sort(Comparator.nullsFirst(Comparator.naturalOrder())); System.out.println(names2); // Output: [null, null, Alice, Bob, Charlie] // Sort with nulls last names2.sort(Comparator.nullsLast(Comparator.naturalOrder())); System.out.println(names2); // Output: [Alice, Bob, Charlie, null, null] } } 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; } @Override public String toString() { return name + " - " + age; } }[Bob, Alice, Charlie] [8, 5, 2, 1] Alice - 25 Alice - 30 Bob - 25 [kiwi, apple, banana] [1, 2, 5, 8] [8, 5, 2, 1] [null, null, Alice, Bob, Charlie] [Alice, Bob, Charlie, null, null] -
What is difference between Streams filter, map, flatmap operation ?
flatmap: Flattens a stream of streams into a single stream and then applies a mapping function.
List<List<Integer>> listOfLists = Arrays.asList( Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6) ); List<Integer> flattenedList = listOfLists.stream() .flatMap(List::stream) .collect(Collectors.toList()); System.out.println(flattenedList); // Output: [1, 2, 3, 4, 5, 6]**map**: Transforms each element in a stream into another form using a mapper function.**filter**: Filters elements of a stream based on a condition (predicate).
List<String> sentences = Arrays.asList("Java is fun", "Streams are powerful"); List<String> result = sentences.stream() .flatMap(sentence -> Arrays.stream(sentence.split(" "))) // Flatten into words .filter(word -> word.length() > 3) // Filter words longer than 3 chars .map(String::toUpperCase) // Map to uppercase .collect(Collectors.toList()); System.out.println(result); // Output: [JAVA, STREAMS, POWERFUL, C++]Difference: Map is for one to one transformation while flatMap maintains One to many transform. Map maintains the structure of stream but flatMap does not.
-
Difference between abstract class and interface from java 8 perspective ?
- In abstract class, a class can extend only one abstract class because Java does not support multiple inheritance for classes. A class can implement multiple interfaces, enabling multiple inheritance of behavior.
- An abstract class can have both instance and static fields, whereas an interface can only have fields that are implicitly
public,static, andfinal(constants). - Abstract class can have constructor while interface don't.
- Abstract class can maintain state while interface don't
-
If a class inherits from multiple interface which has same signature of default method, what happen then
package org.concept; interface InterfaceA { default void show() { System.out.println("InterfaceA's default method"); } } interface InterfaceB { default void show() { System.out.println("InterfaceB's default method"); } } class MyClass implements InterfaceA, InterfaceB { @Override public void show() { System.out.println("Resolving conflict in MyClass"); } } interface interfaceC extends InterfaceA, InterfaceB{ @Override default void show() { InterfaceA.super.show(); } } public class Test { public static void main(String[] args) { MyClass obj = new MyClass(); obj.show(); InterfaceA interfaceA = new MyClass(); interfaceA.show(); } }We have to override that method.
-
How atomic Integer works internally ?
AtomicIntegeruses CAS (Compare-And-Swap) operations at its core, which is a low-level atomic instruction provided by the CPU. CAS ensures that an operation is performed atomically without locking the thread or requiring explicit synchronization.CAS checks the current value of a variable and updates it only if it matches an expected value. This avoids race conditions.Key Components
-
volatileField:
The integer value is stored in avolatile intfield, ensuring visibility of updates to other threads. -
UnsafeClass:
Thesun.misc.Unsafeclass is used to perform low-level operations like CAS directly in memory. -
Memory Offset:
The memory address of thevolatilefield is calculated using theUnsafe.objectFieldOffset()method, ensuring precise updates to the variable.import java.util.concurrent.atomic.AtomicInteger; public class Solution { public static void main(String[] args) { // Create an AtomicInteger with an initial value of 10 AtomicInteger atomicInt = new AtomicInteger(10); // Get the current value System.out.println("Initial value: " + atomicInt.get()); // Increment and get the value System.out.println("Increment and get: " + atomicInt.incrementAndGet()); // Decrement and get the value System.out.println("Decrement and get: " + atomicInt.decrementAndGet()); // Add a value and get the result System.out.println("Add 5 and get: " + atomicInt.addAndGet(5)); // Get and increment (returns the value before incrementing) System.out.println("Get and increment: " + atomicInt.getAndIncrement()); } }
-
-
Give me a example of Dynamic Proxy Creation using Reflection API ?
package org.example; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface Calculator { int add(int a, int b); int subtract(int a, int b); } class CalculatorImpl implements Calculator { @Override public int add(int a, int b) { return a + b; } @Override public int subtract(int a, int b) { return a - b; } } public class Solution { public static void main(String[] args) { // Original object Calculator calculator = new CalculatorImpl(); // Create a proxy instance Calculator proxyInstance = (Calculator) Proxy.newProxyInstance( calculator.getClass().getClassLoader(), calculator.getClass().getInterfaces(), new LoggingHandler(calculator) ); // Use the proxy instance int sum = proxyInstance.add(5, 3); // Logs method details System.out.println("Sum: " + sum); int difference = proxyInstance.subtract(5, 3); // Logs method details System.out.println("Difference: " + difference); } } // Logging handler class LoggingHandler implements InvocationHandler { private final Object target; public LoggingHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Log method details System.out.println("Method called: " + method.getName()); System.out.println("Arguments: " + java.util.Arrays.toString(args)); // Invoke the actual method on the target object Object result = method.invoke(target, args); System.out.println("Result: " + result); return result; } }Method called: add Arguments: [5, 3] Result: 8 Sum: 8 Method called: subtract Arguments: [5, 3] Result: 2 Difference: 2Above program creates proxy instance with
LoggingHandlerhasInvocationHandler. This is used to log data, method call, result when any method is called -
What are the difference between throw and throwable ?
throw:throwis a keyword used to explicitly throw an exception from a method.
throw new IllegalArgumentException("Invalid input");Throwable- It is Superclass of all exceptions and errors.
- It represents all exception types (checked and unchecked).
- Used as a base class for defining exceptions
public void myMethod() throws Throwable {} -
What is the difference between Comparator.comparing vs Comparator.comparingInt
Comparator.comparinginvolves autoboxing and unboxing when working with primitives whilecomparingIntis specialized forintprimitive types.comparingIntavoids unnecessary autoboxing and unboxing, leading to better performance. -
What are the difference Between throw and throws ?
S. No. Key Difference throw throws 1. Point of Usage The throw keyword is used inside a function. It is used when it is required to throw an Exception logically. The throws keyword is used in the function signature. It is used when the function has some statements that can lead to exceptions. 2. Exceptions Thrown The throw keyword is used to throw an exception explicitly. It can throw only one exception at a time. The throws keyword can be used to declare multiple exceptions, separated by a comma. Whichever exception occurs, if matched with the declared ones, is thrown automatically then. 3. Syntax Syntax of throw keyword includes the instance of the Exception to be thrown. Syntax-wise, the throw keyword is followed by the instance variable. Syntax of throws keyword includes the class names of the Exceptions to be thrown. Syntax-wise, the throws keyword is followed by exception class names. 4. Propagation of Exceptions throw keyword cannot propagate checked exceptions. It is only used to propagate the unchecked Exceptions that are not checked using the throws keyword. throws keyword is used to propagate the checked Exceptions only.
