Java Interview Question with Answers and Solution with Code
1. Factory design pattern in Java
2. Hashmap vs concurrent hash map
HashMap: Not thread-safe, fast for single-threaded apps
ConcurrentHashMap: Thread-safe, better for multi-threaded environments
Key Differences:
Feature HashMap ConcurrentHashMap Thread Safety ❌ Not thread-safe ✅ Thread-safe Null Support ✅ Allows null keys/values ❌ No null keys/values Performance Fast single-threaded Optimized multi-threaded Locking Mechanism No internal locking Bucket-level locking Iterator Behavior Fail-fast Weakly consistent Concurrent Access Data corruption risk Safe concurrent access Memory Consistency No guarantees Happens-before guarantees Use Case Single-threaded apps Multi-threaded apps Synchronization Requires external sync Built-in synchronization Java Version Since 1.2 Since 1.5 Internal Structure Array + LinkedList/Tree Segments/Buckets When to Use:
- HashMap: Single-threaded applications
- ConcurrentHashMap: Multi-threaded applications with concurrent access
3. Can we Do Multiple sorting using comparable ?
No - Only with Comparator
// Sort by Name (ascending), then by Grade (descending)
System.out.println("\nSorted by Name (ASC) then Grade (DESC):");
students.sort(Comparator
.comparing(Student::getName)
.thenComparing(Comparator.comparing(Student::getGrade).reversed())
);
students.forEach(System.out::println);
// Sort by Age (ascending), then by Name (ascending)
System.out.println("\nSorted by Age (ASC) then Name (ASC):");
students.sort(Comparator
.comparing(Student::getAge)
.thenComparing(Student::getName)
);
students.forEach(System.out::println);4. How HashSet Works in Java
HashSet is a collection that implements the Set interface and uses a HashMap internally to store elements. It doesn't allow duplicate elements and provides constant-time performance for basic operations.
Step Description 1 Calculate hashCode() of object 2 Find bucket using hash & (n-1) 3 Check if bucket is empty 4 If not empty, use equals() to compare 5 Add if unique, reject if duplicate
Feature HashSet Duplicates ❌ Not allowed Null Elements ✅ Allows one null Order ❌ No guaranteed order Thread Safety ❌ Not thread-safe Performance O(1) average case import java.util.HashSet; public class HashSetExample { public static void main(String[] args) { // Create HashSet HashSet<String> fruits = new HashSet<>(); // Add elements fruits.add("Apple"); fruits.add("Banana"); fruits.add("Orange"); fruits.add("Apple"); // Duplicate - will be ignored // Display System.out.println("HashSet: " + fruits); // Check existence System.out.println("Contains Apple: " + fruits.contains("Apple")); // Remove element fruits.remove("Banana"); System.out.println("After removal: " + fruits); // Iterate for (String fruit : fruits) { System.out.println("Fruit: " + fruit); } } }✅ Use HashSet when:
- Need unique elements only
- Don't care about order
- Fast lookups are important
❌ Avoid HashSet when:
- Need insertion order (use LinkedHashSet)
- Need sorting (use TreeSet)
- Thread-safety required
5. Executor Framework in Java
Executor Framework is a powerful framework for asynchronous task execution and management in Java, providing a higher-level replacement for working with threads directly.
Component Description Executor Interface for executing tasks ExecutorService Extended interface with lifecycle methods ThreadPoolExecutor Most common implementation ScheduledExecutorService For delayed and periodic execution Future Represents result of asynchronous computation
Thread Pool Description Use Case FixedThreadPool Fixed number of threads CPU-intensive tasks CachedThreadPool Creates threads as needed Short-lived asynchronous tasks SingleThreadExecutor Single worker thread Sequential task execution ScheduledThreadPool For scheduled tasks Periodic or delayed execution import java.util.concurrent.*; public class FixedThreadPoolExample { public static void main(String[] args) { // Create fixed thread pool with 4 threads ExecutorService executor = Executors.newFixedThreadPool(4); // Submit tasks for execution for (int i = 1; i <= 10; i++) { final int taskId = i; executor.submit(() -> { System.out.println("Executing task " + taskId + " by " + Thread.currentThread().getName()); try { Thread.sleep(1000); // Simulate work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } // Shutdown the executor executor.shutdown(); } }import java.util.concurrent.*; import java.util.*; public class CallableFutureExample { public static void main(String[] args) throws Exception { ExecutorService executor = Executors.newFixedThreadPool(3); // Submit Callable tasks that return results Future<Integer> future1 = executor.submit(new SumTask(10, 20)); Future<Integer> future2 = executor.submit(new FactorialTask(5)); Future<String> future3 = executor.submit(new StringTask("Hello")); // Get results (blocks until available) System.out.println("Sum: " + future1.get()); System.out.println("Factorial: " + future2.get()); System.out.println("String: " + future3.get()); executor.shutdown(); } } class SumTask implements Callable<Integer> { private int a, b; public SumTask(int a, int b) { this.a = a; this.b = b; } @Override public Integer call() throws Exception { Thread.sleep(1000); // Simulate computation return a + b; } } class FactorialTask implements Callable<Integer> { private int n; public FactorialTask(int n) { this.n = n; } @Override public Integer call() throws Exception { int result = 1; for (int i = 1; i <= n; i++) { result *= i; Thread.sleep(100); // Simulate work } return result; } } class StringTask implements Callable<String> { private String input; public StringTask(String input) { this.input = input; } @Override public String call() throws Exception { Thread.sleep(500); return input.toUpperCase() + " processed by " + Thread.currentThread().getName(); } }import java.util.concurrent.*; public class ScheduledExecutorExample { public static void main(String[] args) throws InterruptedException { ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); // Schedule task to run after 3 seconds delay ScheduledFuture<?> future = scheduler.schedule(() -> { System.out.println("Task executed after 3 seconds"); }, 3, TimeUnit.SECONDS); // Schedule task to run every 2 seconds after initial 1 second delay scheduler.scheduleAtFixedRate(() -> { System.out.println("Repeating task: " + System.currentTimeMillis()); }, 1, 2, TimeUnit.SECONDS); // Let it run for 10 seconds then shutdown Thread.sleep(10000); scheduler.shutdown(); } }import java.util.concurrent.*; import java.util.*; public class InvokeAllExample { public static void main(String[] args) throws Exception { ExecutorService executor = Executors.newFixedThreadPool(3); List<Callable<String>> tasks = Arrays.asList( () -> { Thread.sleep(2000); return "Task 1 completed"; }, () -> { Thread.sleep(1000); return "Task 2 completed"; }, () -> { Thread.sleep(3000); return "Task 3 completed"; } ); // Execute all tasks and wait for all to complete List<Future<String>> futures = executor.invokeAll(tasks); for (Future<String> future : futures) { System.out.println(future.get()); } executor.shutdown(); } }import java.util.concurrent.*; import java.util.*; public class InvokeAnyExample { public static void main(String[] args) throws Exception { ExecutorService executor = Executors.newFixedThreadPool(3); List<Callable<String>> tasks = Arrays.asList( () -> { Thread.sleep(2000); return "Result from Task 1"; }, () -> { Thread.sleep(1000); return "Result from Task 2"; }, () -> { Thread.sleep(1500); return "Result from Task 3"; } ); // Returns the result of the first completed task String firstResult = executor.invokeAny(tasks); System.out.println("First completed: " + firstResult); executor.shutdown(); } }ExecutorService executor = Executors.newFixedThreadPool(4); try { // Submit tasks executor.submit(() -> System.out.println("Task running")); } finally { // Always shutdown executor executor.shutdown(); try { // Wait for tasks to complete if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); // Force shutdown } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); } }import java.util.concurrent.*; public class CustomThreadPool { public static void main(String[] args) { // Create custom thread pool ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // core pool size 5, // maximum pool size 60, // keep alive time TimeUnit.SECONDS, new LinkedBlockingQueue<>(10), // work queue new ThreadPoolExecutor.CallerRunsPolicy() // rejection policy ); // Use the executor for (int i = 0; i < 15; i++) { final int taskId = i; executor.submit(() -> { System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName()); }); } executor.shutdown(); } }✅ Use Executor Framework when:
- Need to manage multiple threads efficiently
- Want to control thread creation and resource usage
- Need task scheduling capabilities
- Working with asynchronous computations
- Building server applications
❌ Avoid when:
- Simple single-threaded applications
- Very small number of tasks
- When complete control over thread lifecycle is needed
Benefit Description Thread Reuse Reduces thread creation overhead Resource Management Controls maximum concurrent threads Task Queueing Handles task submission spikes Lifecycle Management Provides clean startup/shutdown Error Handling Better exception handling with Futures
1. @Qualifier Annotation in Spring
@Qualifier is a Spring annotation used to resolve ambiguity when multiple beans of the same type are available for dependency injection.
When multiple beans implement the same interface, Spring doesn't know which one to inject:
@Component class SMSService implements MessageService { public void send(String message) { System.out.println("Sending SMS: " + message); } } @Component class EmailService implements MessageService { public void send(String message) { System.out.println("Sending Email: " + message); } } @Service class NotificationService { @Autowired private MessageService messageService; // ❌ Which one to inject? SMSService or EmailService? }@Component @Qualifier("sms") class SMSService implements MessageService { public void send(String message) { System.out.println("Sending SMS: " + message); } } @Component @Qualifier("email") class EmailService implements MessageService { public void send(String message) { System.out.println("Sending Email: " + message); } } @Service class NotificationService { @Autowired @Qualifier("sms") // ✅ Explicitly specifies SMSService private MessageService messageService; }@Service class NotificationService { private final MessageService messageService; @Autowired public NotificationService(@Qualifier("email") MessageService messageService) { this.messageService = messageService; // ✅ EmailService will be injected } }@Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface SMS {} @Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface Email {} @Component @SMS class SMSService implements MessageService { // implementation } @Component @Email class EmailService implements MessageService { // implementation } @Service class NotificationService { @Autowired @SMS // ✅ Cleaner approach with custom qualifier private MessageService messageService; }
Aspect Description Purpose Resolves bean ambiguity during dependency injection Usage Used with @Autowired to specify which bean to inject Alternatives @Primary annotation, bean names Best Practice Use with constructor injection for better testability ✅ Use @Qualifier when:
- Multiple beans implement the same interface
- You need explicit control over which bean gets injected
- Different beans serve different purposes in different contexts
❌ Don't use when:
- Only one bean of a type exists
- Using @Primary makes more sense for default bean selection
2. IOC Container in Spring
IOC (Inversion of Control) Container is the core engine of Spring Framework that manages the complete lifecycle of Spring beans - from creation to destruction.
Instead of you creating objects manually, the IOC container creates and manages them for you:
// Without IOC Container (Traditional) UserService userService = new UserService(); UserRepository userRepository = new UserRepository(); userService.setRepository(userRepository); // With IOC Container (Spring) @Component class UserService { @Autowired private UserRepository userRepository; // Spring automatically creates and injects dependency }
Responsibility Description Bean Creation Instantiates beans using reflection Dependency Injection Injects dependencies automatically Lifecycle Management Manages bean lifecycle (init/destroy) Configuration Applies configurations and profiles Scope Management Manages singleton, prototype scopes // Legacy - lighter container Resource resource = new ClassPathResource("beans.xml"); BeanFactory factory = new XmlBeanFactory(resource); MyBean bean = (MyBean) factory.getBean("myBean");// Modern - enterprise features ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); // OR ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); MyService service = context.getBean(MyService.class);
- Scan - Scans for @Component, @Service, @Repository annotations
- Create - Instantiates beans using default/no-arg constructor
- Configure - Applies @Value, @Autowired configurations
- Inject - Resolves and injects dependencies
- Initialize - Calls @PostConstruct methods
- Ready - Bean is ready for use
- Destroy - Calls @PreDestroy methods during shutdown
// Configuration @Configuration @ComponentScan("com.example") public class AppConfig { } // Bean classes @Service class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } } @Repository class UserRepository { // Data access code } // Usage public class Main { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); UserService userService = context.getBean(UserService.class); userService.processUser(); // IOC container manages everything! } }✅ Loose Coupling - Components don't create dependencies
✅ Easy Testing - Can easily mock dependencies
✅ Configuration Management - Centralized configuration
✅ Lifecycle Management - Automatic initialization and destruction
✅ AOP Support - Easy aspect-oriented programming
- IOC = Inversion of Control (Framework controls object creation)
- DI = Dependency Injection (Method of achieving IOC)
- Container = Runtime environment that manages beans
- ApplicationContext is the most commonly used IOC container