Core Java Fundamentals
Abstract Classes vs. Interfaces
Both constructs prevent direct instantiation and require subclasses to implement abstract methods. However, they differ significantly in implementation:
Abstract Classes:
- May contain concrete methods, instance variables, and initialization blocks
- Support constructors for subclass initialization
- Permit fields of any visibility and type
- Ideal for sharing code among related classes and defining template method patterns
Interfaces (prior to Java 8):
- Restricted to method signatures, constants (
public static finalimplicitly), and static/default methods - No constructors allowed
- Enable multiple inheritance of behavior
- Best suited for defining contracts across unrelated classes
// Interface defining a contract
interface Drawable {
double PI = 3.14159; // Implicitly public static final
void render();
default void logAction() {
System.out.println("Rendering shape...");
}
}
// Abstract class providing shared functionality
abstract class GraphicElement {
protected int x, y;
public GraphicElement(int xCoord, int yCoord) {
this.x = xCoord;
this.y = yCoord;
}
public void moveTo(int newX, int newY) {
this.x = newX;
this.y = newY;
}
abstract void display();
}
Method Overloading and Overriding
Overloading occurs within the same class, where multiple methods share an identifier but differ in parameter lists (type, count, or order). Return types and access modifiers may vary independently.
Overriding happens in subclasses redefining inherited methods. The signature must match exactly, though the return type may be covariant. Access privileges cannot be more restrictive than the parent, and declared exceptions must be compatible subsets.
class Calculator {
// Overloaded methods
public int compute(int a, int b) {
return a + b;
}
public double compute(double a, double b) {
return a + b;
}
}
class AdvancedCalculator extends Calculator {
// Overriding with specific behavior
@Override
public int compute(int a, int b) {
System.out.println("Performing advanced computation");
return super.compute(a, b) * 2;
}
}
Equality Comparison: == versus equals()
The == operator compares primitive values directly or reference addresses for objects. The equals() method, inherited from Object, defaults to reference comparison but may be overridden for value equality (e.g., String, wrapper classes). When overriding equals(), always accompany it with hashCode() to maintain the contract that equal objects generate identical hash codes.
Exception Handling Architecture
Java manages exceptional conditions through try-catch-finally constructs or declarative throws clauses. The finally block executes regardless of exception occurrence, except when System.exit() is invoked or the JVM terminates abruptly. Uncaught exceptions propagate up the call stack until handled or causing program termination.
public class ResourceManager {
public void processFile(String filename) throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(filename));
String content = reader.readLine();
if (content == null) {
throw new IllegalArgumentException("Empty file provided");
}
} catch (FileNotFoundException e) {
System.err.println("Resource not found: " + e.getMessage());
} finally {
if (reader != null) {
reader.close();
}
}
}
}
Autoboxing and Unboxing
Autoboxing automatically converts primitives to their wrapper equivalents (int → Integer), while unboxing performs the reverse. Explicit conversion uses valueOf() and xxxValue() methods. Note that Integer.valueOf() caches values between -128 and 127 for performance.
HashMap Internal Mechanics
HashMap utilizes an array of buckets where entries are stored. Key hashing involves:
- Computing
hashCode()of the key - Applying a supplemental hash function:
(h = key.hashCode()) ^ (h >>> 16)to improve distribution - Determining bucket index:
(capacity - 1) & hash
Collision resolution uses chaining (linked lists) until treeification threshold (8 entries) converts lists to red-black trees for O(log n) lookup. JDK 8 employs tail insertion rather than head insertion to prevent infinite loops during concurrent resizing.
// Hash distribution demonstration
static int supplementalHash(int h) {
return h ^ (h >>> 16);
}
static int indexFor(int hash, int length) {
return hash & (length - 1);
}
Programming Paradigms: OOP vs. Procedural
Object-Oriented Programming encapsulates data and behavior within objects, promoting modularity through inheritance, polymorphism, and encapsulation. This approach excels in large, evolving systems but incurs instantiation overhead.
Procedural Programming organizes code into functions/procedures operating on shared data. It offers superior performance for computation-intensive tasks and simpler execution flow, suitable for system-level programming and straightforward scripts.
Object Copying Semantics
Shallow Copy duplicates references, causing source and copy to share nested objects. Deep Copy recursively clones all referenced objects, creating completely independant instances. Implementation typically involves clone() method overriding or serialization techniques.
Polymorphism Implementation
Polymorphism requires three components: inheritance hierarchy, method overriding, and dynamic dispatch through superclass references. This decouples code from specific implementations, enhancing extensibility and maintainability.
Reflection Capabilities
Reflection enables runtime introspection of classes, allowing examination of fields, methods, and constructors regardless of access modifiers. Applications include dependency injection frameworks, serialization libraries, and dynamic proxy generation.
public class ReflectionDemo {
public void inspectClass(String className) throws Exception {
Class<?> targetClass = Class.forName(className);
Object instance = targetClass.getDeclaredConstructor().newInstance();
Field[] fields = targetClass.getDeclaredFields();
for (Field f : fields) {
f.setAccessible(true);
System.out.println(f.getName() + ": " + f.get(instance));
}
}
}
Object Instantiation Strategies
Java provides multiple mechanisms for creating objects:
- new Keyword: Direct instantiation
- Class.newInstance(): Deprecated, uses no-arg constructor
- Constructor.newInstance(): Supports parameterized constructors
- clone(): Creates copies via
Cloneableinterface - Deserialization: Reconstructs objects from byte streams using
ObjectInputStream
Collection Framework Distinctions
List: Ordered sequence allowing duplicates; indexed access
Set: Unordered collection prohibiting duplicates; uniqueness determined by equals() and hashCode()
Map: Key-value associations with unique keys; not a true Collection
Thread Deadlock Conditions
Deadlock arises when four conditions simultaneously exist:
- Mutual Exclusion: Resources are non-shareable
- Hold and Wait: Processes retain resources while requesting additional ones
- No Preemption: Resources cannot be forcibly reclaimed
- Circular Wait: Chain of processes where each waits for resources held by the next
Prevention strategies include lock ordering, timeout mechanisms, and resource allocation graphs.
Thread Coordination: sleep() vs. wait()
sleep() maintains lock ownership during the pause, suitable for timing delays. wait() releases the monitor, enabling inter-thread communication through notify() or notifyAll(). Wait-based coordination requires synchronization blocks and typically uses while loops to guard against spurious wakeups.
public class ThreadCoordinator {
private boolean flag = false;
public synchronized void awaitCondition() throws InterruptedException {
while (!flag) {
wait();
}
// Proceed with execution
}
public synchronized void signalCondition() {
flag = true;
notifyAll();
}
}
ConcurrentHashMap Evolution
Java 7 employed segment-based locking (16 segments by default), reducing contention compared to Hashtable. Java 8 abandoned segments in favor of CAS operations and synchronized node-level locking, with treeification for high-collision scenarios.
Thread Sequencing Techniques
To enforce execution order (T1 → T2 → T3):
Approach 1: join()
Thread worker1 = new Thread(() -> { /* T1 logic */ });
Thread worker2 = new Thread(() -> { /* T2 logic */ });
Thread worker3 = new Thread(() -> { /* T3 logic */ });
worker1.start();
worker1.join(); // Wait for T1 completion
worker2.start();
worker2.join(); // Wait for T2 completion
worker3.start();
Approach 2: CountDownLatch
CountDownLatch latch1 = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);
new Thread(() -> {
// T1 work
latch1.countDown();
}).start();
new Thread(() -> {
try {
latch1.await();
// T2 work
latch2.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
new Thread(() -> {
try {
latch2.await();
// T3 work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
Advanced Java Concepts
Constructor Characteristics
Constructors initialize object state and share the class name without return types. They support overloading (parameter variation) but not overriding (subclasses invoke parent constructors via super()). Abstract classes may define constructors for subclass initialization.
String Immutability Rationale
String maintains immutability through:
finalclass declaration preventing extension- Private
final byte[](Java 9+) orchar[](pre-Java 9) storage - Absence of mutator methods; operations return new instances
This design enables string interning, hash code caching, and thread safety without synchronization.
String Manipulation Classes
StringBuilder: Non-synchronized, high-performance string concatenation for single-threaded contexts StringBuffer: Thread-safe via synchronization, suitable for shared mutable strings String: Immutable, optimal for constant values and string pool utilization
Thread Lifecycle States
- NEW: Instantiated but not started
- RUNNABLE: Executing or ready for execution
- BLOCKED: Waiting for monitor lock acquisition
- WAITING: Indefinite wait for notification
- TIMED_WAITING: Bounded wait (sleep, timeout)
- TERMINATED: Execution completed
Final Keyword Applications
Variables: Assignment occurs exactly once; primitives become constants, references cannot be reassigned Methods: Prevents overriding in subclasses; enables compiler inlining optimizations Classes: Prohibits inheritance; all methods implicitly final
Selective Serialization
The transient keyword excludes fields from serialization, useful for sensitive data, cached values, or derived attributes. Upon deserialization, transient fields receive default values (null, 0, false).
Input Handling Mechanisms
Scanner: Token-based parsing with delimiter support
Scanner scanner = new Scanner(System.in);
String line = scanner.nextLine();
BufferedReader: Efficient line-oriented reading
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String input = reader.readLine();
Stream Processing Choices
Byte Streams (InputStream/OutputStream): Handle raw binary data (images, audio, serialized objects) without encoding translation
Character Streams (Reader/Writer): Manage text with automatic charset encoding/decoding, preventing manual byte-to-character conversion overhead
Logical vs. Bitwise Operators
&& implements short-circuit evaluation (second operand skipped if first is false). & evaluates both operands unconditionally and serves as the bitwise AND when applied to integral types.
// Short-circuit prevents NullPointerException
if (obj != null && obj.isValid()) { }
// Bitwise operation
int result = 0x31 & 0x0F; // Results in 0x01
Collection Utilities
Collection is the root interface for single-element containers (List, Set, Queue). Collections provides static utility methods: sorting, searching, synchronization wrappers, and unmodifiable views.
Final, Finally, Finalize Distinction
final: Access modifier for constants, unchangeable methods, and non-extendable classes finally: Exception handling block guaranteeing execution (barring JVM termination) finalize(): Deprecated cleanup callback invoked by garbage collector; unreliable for resource management
List Implementation Comparison
ArrayList: Dynamic array backing; O(1) random access, O(n) insertion/deletion (except at end); 50% growth on expansion LinkedList: Doubly-linked structure; O(n) search, O(1) insertion/deletion given node reference; no capacity constraints
Enterprise Java Development
HTTP Method Semantics
GET: Idempotent retrieval via URL parameters; limited length; cacheable; safe for bookmarks POST: Entity submission in request body; no size restrictions; non-idempotent; supports complex data structures
Request Dispatching Patterns
Forward: Server-internal transfer preserving request attributes; client unaware of destination change; single request-response cycle Redirect: Client-side re-request via status code (302/307); updates browser address bar; loses request scope data
Servlet and JSP Relationship
Servlets are Java classes handling HTTP requests programmatically. JSPs are text-based templates compiled into Servlets, mixing HTML with Java scriptlets. Modern applications prefer Servlet controllers with JSP views (MVC pattern).
Session Management
Cookies: Client-side storage (4KB limit); store session identifiers and preferences Sessions: Server-side storage mapped via session ID (typically cookie-based); store authentication state and user-specific data
JDBC Database Access
Standard workflow:
- Load driver class (legacy requirement)
- Establish
ConnectionviaDriverManager - Create
StatementorPreparedStatement - Execute query/update; process
ResultSet - Close resources (prefer try-with-resources)
Statement Safety
Statement: Concatenates SQL strings; vulnerable to injection
PreparedStatement: Pre-compiles SQL with parameterized placeholders (?); prevents injection through proper escaping; improves execution plan caching
// Secure parameterized query
String sql = "SELECT * FROM accounts WHERE user_id = ? AND status = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, userId);
ps.setString(2, "ACTIVE");
ResultSet rs = ps.executeQuery();
}
Connection Pooling
Pools maintain reusable connection instances, reducing the overhead of repeated TCP handshakes and authentication. Key parameters include maximum pool size, connection timeout, and idle timeout. Implementations include HikariCP, C3P0, and Tomcat JDBC Pool.
Transaction ACID Properties
Atomicity: All operations succeed or all rollback Consistency: Database invariants maintained pre and post transaction Isolation: Concurrent transactions execute as if sequential Durability: Committed changes persist despite system failures
Data Removal Commands
DELETE: Row-level removal with transaction logging; supports WHERE clauses; can be rolled back TRUNCATE: Table-level fast clearance; minimal logging; resets identity counters; non-transactional in some engines DROP: Schema object destruction; removes structure and data entirely
MyBatis Parameter Binding
#{}: Prepared statement placeholders; type-safe; automatic escaping; prevents SQL injection **${}`: String substitution; useful for dynamic table/column names; requires manual validation to prevent injection
Lazy Loading Implementation
MyBatis defers association loading until first access using CGLIB proxies. When invoking a getter on an unloaded property, the proxy intercepts the call, executes the mapped query, populates the field, and returns the result.
Batch Processing
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
Mapper mapper = session.getMapper(Mapper.class);
for (Entity item : largeDataset) {
mapper.insert(item);
}
session.commit();
} finally {
session.close();
}
SQL Execution Sequence
Logical processing order differs from syntax:
- FROM (including JOINs)
- WHERE (row filtering)
- GROUP BY (aggregation grouping)
- HAVING (group filtering)
- SELECT (column projection)
- DISTINCT (deduplication)
- ORDER BY (sorting)
- LIMIT (result restriction)
Concurrency Architecture
Process vs. Thread
Processes: Independent address spaces; resource allocation units; communicate via IPC (pipes, shared memory, sockets) Threads: Lightweight execution units within processes; share heap memory; possess independent stacks and program counters; require synchronization for shared data access
Context Switching Overhead
When the OS scheduler preempts a thread, it preserves the execution context (register values, program counter, stack pointer) and loads another thread's context. Frequent switching incurs performance penalties due to cache invalidation and kernel mode transitions.
Synchronization Mechanisms
synchronized: Intrinsic locks via JVM monitors; automatic release on exception; limited flexibility ReentrantLock: Explicit lock/unlock; supports fairness policies, interruptible lock acquisition, and try-lock timeouts; requires finally-block release
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionalWait() throws InterruptedException {
lock.lock();
try {
while (!conditionMet) {
condition.await();
}
// Critical section
} finally {
lock.unlock();
}
}
Spring Framework
Core Concepts
Spring provides lightweight container-managed IoC (Inversion of Control) and AOP (Aspect-Oriented Programming). IoC delegates object lifecycle management to the container, while AOP modularizes cross-cutting concerns (logging, security, transactions) without core code modification.
Dependency Injection Patterns
Components declare dependencies through constructors, setters, or field annotations (@Autowired). The container resolves and injects required beans at runtime, promoting loose coupling and testability.
Aspect-Oriented Programming
Aspect: Module encapsulating cross-cutting logic Join Point: Execution point (method invocation, exception throw) Pointcut: Expression matching specific join points Advice: Action taken at join points (Before, After, Around, AfterReturning, AfterThrowing) Weaving: Integration of aspects into target objects
@Aspect
@Component
public class AuditAspect {
@Around("execution(* com.service.*.*(..))")
public Object logExecution(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
return pjp.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
System.out.println(pjp.getSignature() + " took " + duration + "ms");
}
}
}
Proxy Mechanisms
Spring AOP defaults to JDK dynamic proxies for interface-based targets. For concrete classes, CGLIB generates bytecode subclasses. Configuration can force CGLIB usage via @EnableAspectJAutoProxy(proxyTargetClass = true).
Global Exception Handling
@ControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseBody
public ResponseEntity<ErrorResponse> handleNotFound(
HttpServletRequest req, ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
req.getRequestURI()
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
}
Essential Annotations
Core: @Component, @Service, @Repository, @Controller, @RestController
Injection: @Autowired, @Qualifier, @Value, @Resource
Configuration: @Configuration, @Bean, @ComponentScan, @PropertySource
Web: @RequestMapping, @GetMapping, @PostMapping, @PathVariable, @RequestParam, @RequestBody, @ResponseBody
Transaction: @Transactional, @EnableTransactionManagement
Async: @Async, @EnableAsync
Scheduling: @Scheduled, @EnableScheduling
Spring MVC Request Flow
- DispatcherServlet receives HTTP request
- HandlerMapping identifies controller method
- HandlerAdapter invokes the handler
- Controller processes logic; returns
ModelAndViewor data - ViewResolver maps logical view names to physical templates
- View renders response with model data
- DispatcherServlet returns HTTP response
@RestController
@RequestMapping("/api/v1")
public class ProductController {
private final ProductService service;
public ProductController(ProductService service) {
this.service = service;
}
@GetMapping("/products/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
return ResponseEntity.ok(service.findById(id));
}
}