JAVA Interview Questions (1)
JAVA Interview Questions (1)
1. What is ?
2. OOP Concepts
● Inheritance: Code reuse between superclasses and subclasses; private members are
not inherited.
● Encapsulation: Protects code by making variables private and using getter/setter
methods.
● Polymorphism: Objects behave differently based on their type (method overriding).
● Abstraction: Hides implementation details while exposing essential features.
● Interface: Provides multiple inheritance by defining method declarations.
3. Access Modifiers
4. SOLID Principles
● byte (1 byte), short (2 bytes), char (2 bytes), int (4 bytes), long (8 bytes)
● float (4 bytes), double (8 bytes), boolean (1 byte)
6. Order of Initialization
10. Serialization
11. Cloning
● Shallow Copy: Only copies primitive fields; object references point to the same
memory.
● Deep Copy: Creates new objects for all references, duplicating all data.
16. Exceptions
● Iterable: The root interface enabling iteration over elements (e.g., for-each loop).
● Collection: Extends Iterable and defines methods like add, remove, size, and more.
○ List: Ordered, index-based collections (e.g., ArrayList, LinkedList).
○ Set: Unordered collections with unique elements (e.g., HashSet, TreeSet).
○ Queue: Supports element ordering for processing (e.g., PriorityQueue).
● Map: Key-value pairs, distinct from Collection, emphasizing mapping rather than
collections.
Each interface serves a unique purpose, ensuring flexibility and scalability in designing
applications.
● HashMap: Best for general-purpose use where order is not a concern. O(1) time
complexity for operations.
● LinkedHashMap: Retains insertion order, ideal for access-order-sensitive applications
(e.g., LRU caches).
● TreeMap: Maintains keys in sorted order. Suitable for range queries or ordered data
(logarithmic access time).
● ConcurrentHashMap: Optimized for concurrent access, avoiding global locks for
better performance in multi-threaded environments.
● Hashtable: Synchronized but outdated; typically replaced by ConcurrentHashMap.
● ArrayList:
○ Backed by a resizable array.
○ Fast random access (O(1)).
○ Costly insertions/removals in the middle (O(n)) due to shifting elements.
● LinkedList:
○ Uses a doubly-linked list.
○ Efficient insertions/removals (O(1)).
○ Slower traversal (O(n)).
○ Higher memory overhead due to node references.
Use Case: Use ArrayList for frequent access; prefer LinkedList for heavy
insertions/deletions.
● HashSet:
○ Backed by HashMap.
○ No order guarantee.
○ Fast operations (O(1)).
● TreeSet:
○ Backed by TreeMap.
○ Maintains natural or custom order.
○ Slower operations (O(log n)).
Use Case: Use HashSet for performance; prefer TreeSet for ordered sets.
5. HashMap Implementation
A HashMap employs:
● Hashing: Calculates the bucket index from the hash code of keys.
● Buckets: Hold key-value pairs. Collisions are handled using linked lists or red-black
trees (since 8).
● Resizing: Automatically doubles in size when the load factor exceeds the threshold.
Key operations like put and get are optimized for O(1), but excessive collisions can degrade
performance to O(log n).
● Initial Capacity: Starting size of the internal array (rounded to the nearest power of 2).
● Load Factor: Threshold (default 0.75) for resizing when the ratio of elements to
buckets is exceeded.
● Fail-Fast:
○ Detects concurrent modifications and throws
ConcurrentModificationException.
○ Found in collections like ArrayList, HashMap.
● Fail-Safe:
○ Operates on a copy of the collection.
○ Found in CopyOnWriteArrayList, ConcurrentHashMap.
● Comparable:
○ Natural ordering defined by compareTo method.
○ Example: Sorting integers in ascending order.
● Comparator:
○ Custom ordering via compare method.
○ Example: Sorting strings by length.
Both interfaces are pivotal for sorting collections using methods like Collections.sort or for
maintaining order in TreeSet/TreeMap.
Using Runnable:
1.
2.
Runnable vs Callable:
Daemon Threads:
● Definition: Threads supporting non-daemon threads. JVM exits once all non-daemon
threads finish.
Creation:
Executor vs ExecutorService:
ExecutorService Implementations:
● Guarantees:
○ Visibility: Changes to volatile variables are immediately visible to other
threads.
○ Atomicity: Single read/write of long or double variables.
Atomicity of Operations:
● Atomic: Writes to int or volatile long.
● Non-atomic: Incrementing a volatile long.
Synchronized Keyword:
Garbage collection occurs in the Heap Memory, specifically in the Young Generation and
Old Generation.
1. Minor GC:
1. Mark-and-Sweep:
1.
2.
3.
1. Explicitly Suggest:
○ Using System.gc() or Runtime.getRuntime().gc().
○ No guarantee that GC will run immediately.
2. JVM Triggered:
○ Happens automatically based on JVM's memory thresholds and
requirements.
Heap vs Stack Memory in Java
Aspect Heap Memory Stack Memory
Purpose Stores objects and JRE classes. Stores method calls, local
variables, and references.
Thread Scope Shared across threads. Thread-local (each thread has its
stack).
1. Young Generation:
○
Divided into Eden Space and Survivor Spaces.
○
New objects are allocated here, and minor garbage collection occurs to
remove short-lived objects.
2. Old Generation (Tenured Space):
○ Similar to Metaspace but had a fixed size and was part of the heap.
PermGen vs Metaspace
Aspect PermGen (Java 7 and earlier) Metaspace (Java 8 and later)
Garbage Part of the heap, managed by Managed by GC, but part of native
Collection GC. memory.
Example:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
String description() default "No description";
int priority() default 0;
Class<? extends Throwable> expectedExceptions() default None.class;
}
3. Explain the Difference Between Runtime and Compile-Time Annotations. How Are
They Processed?
● Compile-Time Annotations:
○ Examples: @Override, @Deprecated.
○ Processed during compilation.
○ Ensures correctness and compliance with Java conventions.
● Runtime Annotations:
○ Examples: Custom annotations with @Retention(RUNTIME).
○ Accessed using reflection during runtime.
○ Commonly used in frameworks like Spring and Hibernate.
4. How Can You Implement a Custom Annotation Processor?
Example Implementation:
@SupportedAnnotationTypes("com.example.MyCustomAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
roundEnv) {
// Custom processing logic
return true;
}
}
6. What Are the Limitations of Annotations, and How Can They Be Overcome?
● Limitations:
○ Cannot contain method implementations.
○ Limited to specific element types.
○ May introduce runtime overhead.
● Strategies:
○ Combine annotations with reflective or bytecode-based tools.
○ Design minimalistic annotations to reduce complexity.
● Annotations:
○ Flexible metadata, no hierarchy constraints.
○ Better suited for declarative programming.
● Marker Interfaces:
○ Used for type-checking and enforceable contracts.
○ Limited flexibility compared to annotations.
8. How Would You Implement a Custom Validation Framework Using Annotations?
Example:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Validate {
boolean notNull() default false;
int minLength() default 0;
int maxLength() default Integer.MAX_VALUE;
}
9. How Do Frameworks Like Spring Utilize Annotations for Dependency Injection and
Configuration?
● Annotations:
○ @Autowired: For injecting dependencies.
○ @Component: Marks a class as a Spring bean.
○ @Configuration: Indicates a configuration class.
○ @Bean: Defines beans in configuration classes.
● Mechanism:
○ Frameworks scan annotations at runtime or compile-time to perform injections
and configurations.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
String name();
boolean primaryKey() default false;
}
Constructor Injection:
Setter Injection:
3. Advanced DI Concepts
Annotations:
@Component
public class UserService {
@Autowired
private UserRepository repository;
}
Example Implementation:
public class DIContainer {
private Map<Class<?>, Object> instances = new HashMap<>();
● Circular Dependencies:
○ A depends on B, and B depends on A.
○ Solved using @Lazy initialization or refactoring.
● Handling Optional Dependencies:
○ Use Optional<T> or setter injection.
● Lazy Initialization:
○ Delays the creation of dependencies until needed.
● Prototype vs Singleton Scopes:
○ Prototype: New instance per injection.
○ Singleton: Single shared instance.
@Service
public class UserService {
private final UserRepository repository;
@Autowired
public UserService(UserRepository repository) {
this.repository = repository;
}
}
● Best Practices:
○ Avoid too many dependencies.
○ Use dependency graphs judiciously.
○ Properly scope beans to avoid memory leaks.
Example:
@Service
public class GenericService<T> {
private final GenericRepository<T> repository;
@Autowired
public GenericService(GenericRepository<T> repository) {
this.repository = repository;
}
}
● Use mocking frameworks like Mockito for dependency injection during tests.
@InjectMocks
private UserService userService;
@Test
public void testUserCreation() {
// Test logic here
}
}
● Strategies:
○ Mock dependencies for isolated unit testing.
○ Test integration with actual dependencies for end-to-end testing.
10. Java EE and Jakarta EE DI Annotations
Annotations:
@Named
@ApplicationScoped
public class UserService {
@Inject
private UserRepository repository;
}
Creational Patterns
Creational Design Patterns are focused on the process of object creation. They aim to
reduce complexity and instability by creating objects in a controlled manner.
The Singleton Design Pattern ensures that only one instance of a class exists throughout
the Virtual Machine (JVM) and provides a global access point to that instance.
private Singleton() {}
return SingletonHolder.instance;
private Singleton() {}
If you serialize a Singleton class, it can create new instances during deserialization, violating
the Singleton property. Implementing the Serializable interface alone won't solve this.
Advanced users can break the Singleton by using reflection to change the access modifier of
the constructor at runtime, allowing for multiple instances of the Singleton.
Enums provide a much more robust solution. Since enums are inherently serializable, they
avoid the serialization and reflection issues. Here’s how you can implement a Singleton with
an enum:
INSTANCE;
return value;
this.value = value;
Usage:
System.out.println(singleton.getValue());
singleton.setValue(2);
System.out.println(singleton.getValue());
The Factory Design Pattern defines an interface for creating objects, but lets subclasses
decide which class to instantiate. This pattern is often used to create objects based on input,
such as the number of sides of a polygon.
● Example:
String getType();
switch (numberOfSides) {
}
}
The Abstract Factory Design Pattern is used to create families of related or dependent
objects. It’s essentially a factory of factories. This is useful when dealing with complex
objects that belong to a specific family and need to be created together.
The Builder Design Pattern is used when the creation of an object becomes too complex. It
separates the construction of an object from its representation.
● Example:
this.name = name;
this.accountNumber = accountNumber;
this.email = email;
return this;
this.newsletter = newsletter;
return this;
}
}
Usage:
.BankAccountBuilder("Jon", "22738022275")
.withEmail("[email protected]")
.wantNewsletter(true)
.build();
● When the object creation process is complex and has many mandatory and optional
parameters.
● When constructors with many parameters lead to a large number of constructors.
● When clients expect different representations of the object.
Structural Patterns
Structural Design Patterns deal with the composition of classes and objects, focusing on
how objects are composed to form larger structures.
Proxy Pattern
The Proxy Pattern creates an intermediary object that acts as an interface to another
resource, such as a file or database connection. It allows for controlled access and protects
the original object from complexity.
● Example:
void process();
}
public class ExpensiveObjectImpl implements ExpensiveObject {
public ExpensiveObjectImpl() {
heavyInitialConfiguration();
@Override
System.out.println("Processing complete.");
@Override
if (object == null) {
object.process();
Decorator Pattern
The Decorator Pattern enhances the behavior of an object dynamically by adding additional
responsibilities without modifying the original class.
● Example:
String decorate();
@Override
this.tree = tree;
@Override
super(tree);
@Override
● When you want to add, enhance, or remove the behavior of an object dynamically.
● When you need to modify the functionality of a single object without changing others.