Java is a cornerstone language in software development and remains a top choice for backend and enterprise solutions. Whether you're aspiring to become a Java developer, software engineer, or system architect, Stark.ai equips you with the tools to ace your Java job interviews with curated questions, real-world examples, and expert guidance.
JDK (Java Development Kit) is a full-featured software development kit for Java, including the compiler and other tools. JRE (Java Runtime Environment) provides libraries, Java Virtual Machine (JVM), and other components to run Java applications. JVM is an abstract machine that executes Java bytecode.
The key object-oriented principles in Java are encapsulation, inheritance, polymorphism, and abstraction. Encapsulation bundles data and methods, inheritance allows classes to derive from others, polymorphism enables dynamic method invocation, and abstraction provides simplified interfaces.
The `final` keyword in Java is used to declare constants (variables that can't be changed), prevent method overriding (methods can't be overridden in subclasses), and prevent inheritance (classes can't be subclassed).
`==` compares object references, checking if both objects point to the same memory location. The `equals()` method compares the actual content of objects, checking for logical equality. By default, `equals()` is inherited from `Object` and behaves like `==` unless overridden.
A constructor is a special method in Java used to initialize objects. It has the same name as the class and does not return any value. Constructors are called when an instance of the class is created and can be overloaded to provide multiple ways to initialize objects.
ArrayList is based on a dynamic array and allows fast random access but slow insertion and deletion (except at the end). LinkedList is based on a doubly linked list, offering fast insertions and deletions, but slower random access.
`HashMap` is not synchronized and allows one `null` key and multiple `null` values, whereas `HashTable` is synchronized (thread-safe) and doesn't allow `null` keys or values.
The `Iterator` interface provides a way to iterate over a collection of objects, allowing removal of elements during iteration. It has methods `hasNext()`, `next()`, and `remove()`. It's part of Java's `java.util` package.
The `Comparator` interface provides a method to define custom ordering of objects. It can be used to sort collections of objects based on specific attributes by overriding the `compare()` method.
`Set` is a collection that does not allow duplicate elements and is unordered. `List` allows duplicates and maintains the order of elements. Examples of `Set` include `HashSet` and `TreeSet`, while `List` includes `ArrayList` and `LinkedList`.
Multithreading in Java refers to concurrent execution of two or more threads. Java supports multithreading through the `Thread` class and `Runnable` interface, allowing multiple threads to run concurrently, improving performance for tasks like network operations, database queries, etc.
A thread in Java is a lightweight process. Each thread has its own call stack, and multiple threads can exist within the same program. Threads can be created by extending the `Thread` class or implementing the `Runnable` interface.
A `synchronized` method locks the entire method for a thread, ensuring only one thread can execute it at a time. A `synchronized` block locks only a specific block of code, providing a finer level of control over thread synchronization.
The `volatile` keyword in Java indicates that a variable's value will be modified by different threads. It ensures visibility and ordering of changes made to the variable across threads, preventing caching of variable values by threads.
A deadlock in Java occurs when two or more threads are blocked forever, waiting for each other to release resources. Deadlocks occur when threads acquire locks in different orders. Avoiding deadlocks requires careful design of locking mechanisms.
Java is a high-level, class-based, object-oriented programming language. Its key features include platform independence (Write Once Run Anywhere), object-oriented nature, automatic memory management (garbage collection), rich standard library, security features, and multithreading support. It compiles to bytecode that runs on the Java Virtual Machine (JVM).
Primitive data types (byte, short, int, long, float, double, boolean, char) are basic data types that store actual values in stack memory. Reference types (classes, interfaces, arrays) store references to objects in heap memory. Primitives have fixed sizes and can't be null, while reference types can be null and have methods associated with them.
Type casting is converting one data type to another. There are two types: 1) Implicit casting (widening) happens automatically when converting smaller to larger data types (int to long). 2) Explicit casting (narrowing) requires manual conversion for larger to smaller types (long to int) and may result in data loss. Reference type casting involves inheritance relationships.
The '==' operator compares object references (memory addresses) for reference types and actual values for primitive types. The '.equals()' method compares the content/values of objects. For proper object comparison, classes should override the equals() method inherited from Object class to provide meaningful value comparison.
Variable scope defines where a variable is accessible in code. Class variables (static) have class scope, instance variables have object scope, local variables have method scope, and block variables are only accessible within their block. Variable lifetime refers to how long they exist in memory - class variables exist for program duration, instance variables for object lifetime, and local variables until method/block execution ends.
Java operators include arithmetic (+, -, *, /, %), assignment (=, +=, -=), comparison (==, !=, >, <, >=, <=), logical (&&, ||, !), bitwise (&, |, ^, ~, <<, >>), and conditional (?:). Operator precedence determines evaluation order: unary > multiplicative > additive > shift > relational > equality > bitwise > logical > conditional > assignment.
Type promotion occurs when mixing different data types in expressions. Java automatically promotes smaller types to larger ones following rules: byte/short/char are promoted to int, if one operand is long/float/double, the other is promoted to match. The final result type is the promoted type. This ensures accurate calculations without data loss.
Variables can be initialized through: 1) Direct initialization at declaration (int x = 10), 2) Static initialization blocks for static variables, 3) Instance initialization blocks for instance variables, 4) Constructor initialization, 5) Lazy initialization (first time use), 6) Default initialization (automatically by Java). The choice depends on requirements like timing and initialization complexity.
break terminates the current loop or switch statement, continue skips the rest of the current iteration and starts the next one, while return exits the entire method with or without a value. break and continue affect control flow within loops, while return affects method execution. break can also include labels to break outer loops.
Method overloading occurs in the same class with methods having the same name but different parameters (number, type, or order). Method overriding occurs in inheritance relationships where a subclass provides a specific implementation of a method defined in its superclass, requiring the same signature and compatible return type.
Traditional switch statements work with byte, short, int, char, enum, and String (Java 7+), requiring break to prevent fallthrough. Modern Java (12+) introduced switch expressions with arrow syntax (->), multiple case labels, yield keyword, and pattern matching. These enhancements make switch more concise and safer by eliminating accidental fallthrough.
Variable shadowing occurs when a variable declared within a narrower scope has the same name as a variable in a wider scope, temporarily hiding the outer variable. This can happen with local variables shadowing instance variables, or parameters shadowing class fields. It can lead to confusion and bugs if not properly managed using 'this' keyword or better naming.
Type inference (var keyword, introduced in Java 10) allows local variable types to be inferred by the compiler based on the initialization value. Limitations include: must be initialized at declaration, can't be used for fields/parameters/return types, can't be initialized to null, and type must be clearly inferrable. It improves code readability while maintaining type safety.
Bitwise operators (&, |, ^, ~, <<, >>, >>>) manipulate individual bits in integer types. Common uses include: flags and permissions (using bitmasks), optimization for multiplication/division by powers of 2, low-level data manipulation, and memory-efficient data storage. Understanding two's complement representation is crucial for proper usage.
Numeric overflow occurs when a calculation exceeds the maximum value for a data type, wrapping around to the minimum value (and vice versa for underflow). Java doesn't automatically detect these conditions. They should be handled by: using larger data types, checking bounds before operations, using Math.addExact() for checked arithmetic, or BigInteger/BigDecimal for precise calculations.
String interning is the process of storing only one copy of each distinct string value in a pool. When strings are interned (using String.intern()), identical string literals reference the same memory location. It's useful for memory optimization when dealing with many duplicate strings, but should be used carefully as interning has overhead and can impact garbage collection.
Floating-point arithmetic in Java can lead to precision issues due to binary representation limitations. Issues include: inexact decimal representations, rounding errors accumulation, and comparison problems. For precise decimal calculations, use BigDecimal. Special values like NaN and Infinity need careful handling. Understanding IEEE 754 standard helps in managing these issues.
Labeled statements allow targeting specific outer loops/blocks with break/continue statements. While useful for breaking out of nested loops or controlling complex flow, they should be used sparingly as they can make code harder to understand. Better alternatives often include extracting methods or restructuring logic to avoid deep nesting.
Java offers multiple string formatting options: concatenation (+), StringBuilder, String.format(), printf(), and text blocks (Java 15+). Each has trade-offs: concatenation is simple but inefficient for multiple operations, StringBuilder is efficient but verbose, String.format() is flexible but slower, text blocks improve multiline string readability but are limited to literal text.
The enhanced for-loop (for-each) internally uses Iterator/Array indexing. It's syntactic sugar that simplifies iteration but has limitations: can't modify collection size during iteration, can't track index position, can't iterate multiple collections simultaneously, and requires Iterable implementation. Understanding these limitations helps choose between traditional and enhanced for-loops.
Compile-time constants (static final primitives/strings initialized with constant expressions) are inlined by the compiler, improving performance by eliminating field access. They must be initialized at declaration and can't be changed. This optimization affects class loading and versioning - changes require recompilation of dependent classes.
The four main principles are: 1) Encapsulation: implemented through access modifiers (private, protected, public) and getter/setter methods, 2) Inheritance: achieved using 'extends' keyword for class inheritance and 'implements' for interfaces, 3) Polymorphism: supported through method overriding and method overloading, 4) Abstraction: implemented using abstract classes and interfaces.
Abstract classes can have both abstract and concrete methods, instance variables, constructors, and can provide default method implementations. Interfaces (before Java 8) could only have abstract methods and constants. Since Java 8, interfaces can have default and static methods, and since Java 9, private methods. A class can implement multiple interfaces but extend only one abstract class.
Method overriding occurs when a subclass provides a specific implementation of a method declared in its superclass. The method must have the same signature and a return type that is covariant with the parent's return type. The @Override annotation helps catch errors by ensuring the method actually overrides a superclass method. It's a good practice to always use @Override when intending to override methods.
The 'super' keyword refers to the parent class object. It's used to: 1) Call parent class constructor (super()), 2) Access parent class methods when overridden in child class (super.methodName()), 3) Access parent class fields when hidden by child class fields. It must be the first statement in constructor if used for constructor calling.
Encapsulation combines data and methods that operate on that data within a single unit (class) and restricts access to internal details. Benefits include: 1) Better control over data access and modification, 2) Ability to change implementation without affecting code using the class, 3) Prevention of unauthorized access, 4) Support for validation when setting values. Implemented using private fields with public getter/setter methods.
Composition creates a 'has-a' relationship where one class contains objects of another class. Inheritance creates an 'is-a' relationship where one class is a specialized version of another. Composition is preferred when you want to reuse code without creating tight coupling, more flexible for runtime behavior changes, and follows 'favor composition over inheritance' principle. Inheritance is appropriate when subclass is truly a specialized version of superclass.
Method binding is the process of linking a method call to its definition. Static binding (compile-time) occurs with private, static, and final methods where the compiler knows which method to call. Dynamic binding (runtime) occurs with virtual methods that can be overridden, where the actual object type determines which method is called. Dynamic binding enables polymorphism in Java.
Marker interfaces are empty interfaces used to mark or tag a class as having certain properties (e.g., Serializable, Cloneable). They don't contain methods but provide runtime type information through instanceof operator. While annotations now provide similar functionality, marker interfaces have advantages: compile-time type checking and the ability to create type hierarchies.
Singleton ensures a class has only one instance and provides global access to it. Implemented using private constructor and static instance method. Limitations include: making unit testing difficult, potentially violating single responsibility principle, creating global state, and threading issues. Modern alternatives include dependency injection and proper object lifecycle management.
The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of its subclasses without affecting program correctness. In Java, this means subclass methods should: accept parameters of same/wider types, return same/narrower types, throw same/fewer exceptions, and maintain superclass invariants. Violations indicate poor inheritance hierarchy design.
Java supports four types of inner classes: 1) Regular inner classes (non-static nested classes) with access to outer class members, 2) Static nested classes without outer class instance access, 3) Local classes defined in methods, 4) Anonymous classes for one-time use. Inner classes can access private members of outer class and are useful for encapsulation and callback implementations.
Object cloning creates a copy of an object using the clone() method from Object class. Classes must implement Cloneable interface and override clone(). Shallow cloning copies object references, while deep cloning creates new instances of referenced objects. Considerations include: handling circular references, maintaining invariants, and dealing with final fields. Alternative approaches include copy constructors or static factory methods.
The diamond problem occurs in multiple inheritance when a class inherits from two classes that have a common ancestor, creating ambiguity about which version of inherited methods to use. Java avoids this by not supporting multiple class inheritance, only allowing multiple interface inheritance. With interfaces, the most specific default method implementation is used, or compilation fails if ambiguous.
The instanceof operator checks if an object is an instance of a class, superclass, or interface. It returns true for any class in the inheritance chain or implemented interfaces. Since Java 16, pattern matching with instanceof allows direct casting and variable declaration. Useful for type-safe downcasting but overuse can indicate poor design or violation of polymorphism principles.
Sealed classes (Java 17+) restrict which classes can inherit from them using 'permits' clause. They provide more control over class hierarchy, ensuring only intended subclasses exist. Subclasses must be declared 'final', 'sealed', or 'non-sealed'. Useful for domain modeling, API design, and pattern matching exhaustiveness checking.
Method dispatch determines which method implementation to call. For overloaded methods, selection is based on compile-time types of arguments (static dispatch). For overridden methods, selection is based on runtime type of object (dynamic dispatch). Understanding this is crucial for polymorphic behavior and avoiding subtle bugs with method resolution.
Constructors initialize object state during creation. In inheritance, superclass constructors are called before subclass constructors. If no constructor is defined, Java provides default no-arg constructor. Constructor chaining using this() and super() must follow specific rules: can't call both in same constructor, must be first statement. Proper constructor design is crucial for object initialization and invariant maintenance.
Abstract methods in interfaces declare method signatures without implementation, requiring implementing classes to provide implementation. Default methods (Java 8+) provide default implementation in interfaces, allowing interface evolution without breaking existing implementations. Default methods can be overridden, and conflicts must be resolved when implementing multiple interfaces.
SOLID principles are: Single Responsibility (classes have one reason to change), Open-Closed (open for extension, closed for modification), Liskov Substitution (subtypes must be substitutable), Interface Segregation (clients shouldn't depend on unused methods), Dependency Inversion (depend on abstractions). Implementation involves proper interface design, inheritance hierarchies, and dependency management.
Cohesion measures how strongly related and focused the responsibilities of a class are. Coupling measures the degree of interdependence between classes. High cohesion (class does one thing well) and loose coupling (minimal dependencies) are desired. Achieved through proper encapsulation, interface-based design, and dependency injection. Important for maintainability and reusability.
The Java Collections Framework is a unified architecture for representing and manipulating collections. The hierarchy starts with the Collection interface, which branches into List (ordered), Set (unique elements), and Queue (ordered for processing) interfaces. Map interface is separate but related. Each interface has multiple implementations like ArrayList, HashSet, LinkedList, HashMap, etc., offering different performance characteristics and functionalities.
ArrayList uses a dynamic array with fast random access (O(1)) but slower insertions/deletions in the middle (O(n)). LinkedList uses doubly-linked list with O(1) insertions/deletions at known positions but O(n) random access. Use ArrayList for frequent random access and rare modifications, LinkedList for frequent insertions/deletions and sequential access. ArrayList is generally more memory-efficient due to less overhead per element.
HashSet offers O(1) operations using hash table, doesn't maintain insertion order. LinkedHashSet maintains insertion order using doubly-linked list while keeping O(1) operations. TreeSet maintains natural order or custom comparator order using Red-Black tree with O(log n) operations. Use HashSet for fastest performance, LinkedHashSet when order matters, TreeSet when sorted order is needed.
Main differences: 1) HashMap is non-synchronized while Hashtable is synchronized, 2) HashMap allows null keys and values, Hashtable doesn't, 3) HashMap is generally faster. In modern Java, HashMap is preferred with Collections.synchronizedMap() or ConcurrentHashMap for thread-safety. Hashtable is legacy and shouldn't be used in new code.
equals() determines object equality while hashCode() provides the hash value for hash-based collections. They must follow the contract: equal objects must have equal hash codes, but equal hash codes don't guarantee equal objects. When overriding one, always override both. Implementation should be consistent, use significant fields, and maintain symmetry and transitivity in equals().
ConcurrentHashMap uses segment-level locking (pre-Java 8) or node-level locking (Java 8+) allowing multiple threads to write simultaneously to different segments/nodes. Synchronized HashMap locks the entire map for each operation. ConcurrentHashMap provides better scalability, doesn't block reads, and offers atomic operations like putIfAbsent(). It never throws ConcurrentModificationException during iteration.
Fail-fast iterators (like in ArrayList, HashMap) throw ConcurrentModificationException if collection is modified while iterating, except through iterator's methods. Fail-safe iterators (like in CopyOnWriteArrayList, ConcurrentHashMap) work on a copy of collection, allowing modifications but potentially showing stale data. Fail-fast ensures data consistency, fail-safe prioritizes non-blocking iteration.
NavigableMap extends SortedMap to provide navigation methods: floorKey, ceilingKey, higherKey, lowerKey, etc. TreeMap implements it. Use cases include: range queries, finding closest matches, maintaining ordered key-value pairs with efficient navigation. Useful for applications like price ranges, time series data, or any ordered mapping requiring nearest-match queries.
PriorityQueue is a heap-based implementation maintaining elements in natural or custom order, offering O(log n) insertion and O(1) peek/poll of highest/lowest priority element. Deque (interface implemented by ArrayDeque, LinkedList) is a double-ended queue allowing insertion/removal at both ends in O(1). Use PriorityQueue for priority-based processing, Deque for stack/queue operations.
Methods include: 1) for-each loop (clean, preferred for simple iteration), 2) Iterator (allows removal, required for Map), 3) ListIterator (bidirectional for Lists), 4) traditional for loop (when index needed), 5) streams (functional approach, parallel processing). Performance varies: traditional for loop fastest for ArrayList, Iterator best for LinkedList, streams overhead justified by parallel processing or complex operations.
Load factor determines when HashMap resizes (default 0.75). Lower value means less collisions but more memory, higher means better memory usage but more collisions. Resize operation is expensive (O(n)). Change default when: 1) Memory critical (increase), 2) Many collisions expected (decrease), 3) Performance critical with known size (adjust initial capacity instead).
WeakHashMap allows keys to be garbage collected when no strong references exist. Entry is removed automatically during next operation. Use cases: 1) Implementing caches that shouldn't prevent garbage collection, 2) Storing metadata about objects without memory leaks, 3) Maintaining mappings that shouldn't affect object lifecycle. Important for memory management in long-running applications.
Immutable collections are unmodifiable after creation. Creation methods: 1) Collections.unmodifiableXXX() (wrapper, backing collection still mutable), 2) List.of(), Set.of(), Map.of() (Java 9+, truly immutable), 3) Collectors.toUnmodifiableXXX() (for streams), 4) ImmutableList etc. in Guava. Benefits include thread safety, security, and preventing accidental modifications.
BlockingQueue adds blocking operations to Queue: put() blocks when full, take() blocks when empty. Implementations (ArrayBlockingQueue, LinkedBlockingQueue) are thread-safe. Used in producer-consumer pattern where producers add items (blocking if full) and consumers remove items (blocking if empty). Provides built-in synchronization and eliminates need for explicit coordination.
Collections.sort() uses modified mergesort (Timsort since Java 7) for Objects, and quicksort for primitives. Time complexity O(n log n), space complexity O(n) for objects (due to merge sort's need for temporary array), O(log n) for primitives. Stable sort for objects, unstable for primitives. Elements must be Comparable or a Comparator must be provided.
Map provides three views: 1) keySet() returns Set of keys, 2) values() returns Collection of values, 3) entrySet() returns Set of key-value pairs. Views are backed by map - modifications reflect in original map. Useful for iteration, bulk operations, and stream operations. Performance varies by implementation (e.g., HashMap vs TreeMap).
EnumSet is specialized Set implementation for enum types using bit vector internally. Offers constant time operations, very memory efficient (one long per 64 enum constants). All operations are thread-safe due to atomic bit operations. Can't use with null values. Preferred over HashSet when working with enums due to superior performance and memory usage.
Concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList, ConcurrentLinkedQueue) are thread-safe collections designed for concurrent access. Use when: 1) Multiple threads access collection, 2) Need better scalability than synchronized collections, 3) Want to avoid explicit synchronization. Trade-offs include slightly lower single-thread performance and possibly inconsistent iterations.
Custom objects must either implement Comparable or be used with a Comparator, otherwise ClassCastException is thrown. TreeSet/TreeMap use natural ordering (Comparable) or custom ordering (Comparator) to maintain sorted order. Comparison must be consistent with equals() to prevent unexpected behavior. Important for maintaining the collection's invariants.
Spliterator (Java 8+) is used for traversing and partitioning elements, especially in parallel streams. Provides estimates of size, characteristics (ORDERED, SORTED, etc.), and splitting capabilities. Each collection provides specialized implementation optimized for its structure. Important for parallel processing and stream operations performance.
Checked exceptions (extending Exception) must be declared in method signature or handled in try-catch blocks, representing recoverable conditions (e.g., IOException). Unchecked exceptions (extending RuntimeException) don't require explicit handling, representing programming errors (e.g., NullPointerException). Error class and its subclasses are also unchecked but represent unrecoverable conditions.
try block contains code that might throw exceptions. catch blocks handle specific exceptions, ordered from most specific to most general. finally block always executes (except for System.exit() or JVM crash), used for cleanup. Execution order: try → catch (if exception occurs) → finally. Multiple catch blocks are allowed, but finally block is optional and must be last.
Custom exceptions are created by extending Exception (checked) or RuntimeException (unchecked). Best practices include: meaningful name ending with 'Exception', constructors for different initialization options, proper message and cause handling, and maintaining serialization compatibility. Use throw keyword to raise exception. Should include appropriate documentation and usage guidelines.
try-with-resources (Java 7+) automatically closes resources that implement AutoCloseable interface. Resources are closed in reverse order of creation after try block completes or if exception occurs. Improves code by eliminating explicit finally blocks for resource cleanup, handling multiple resources, and properly managing suppressed exceptions. More concise and less error-prone than traditional try-finally.
Overriding methods can't throw broader checked exceptions than overridden method (covariant exception types allowed). Can throw fewer or narrower checked exceptions. Unchecked exceptions aren't restricted. If superclass method doesn't throw exceptions, overriding method can only throw unchecked exceptions. This maintains LSP (Liskov Substitution Principle) and type safety.
Suppressed exceptions occur when exception is thrown in try block and another occurs in finally or during resource closing (try-with-resources). Original exception is thrown with suppressed exceptions accessible via getSuppressed(). Java 7+ automatically handles suppressed exceptions in try-with-resources. Use addSuppressed() method to manually add suppressed exceptions.
Exception chaining (wrapping) preserves original exception as cause of new exception using initCause() or constructor with cause parameter. Used when: converting checked to unchecked exceptions, maintaining abstraction layers, preserving exception context. Access cause via getCause(). Helps in debugging by maintaining complete exception history.
Multi-catch (Java 7+) allows catching multiple exception types in single catch block using | operator. Restrictions: caught exception types must not have subclass relationship, exception parameter is implicitly final. Reduces code duplication when handling multiple exceptions same way. Type of catch parameter is intersection type of all alternatives.
Exception handling has overhead: stack trace creation, exception object creation, and unwinding stack. Best practices: 1) Use exceptions for exceptional conditions, not flow control, 2) Catch exceptions at appropriate level, 3) Avoid empty catch blocks, 4) Clear and reset exception state if rethrowing, 5) Consider exception pooling for high-performance systems.
throw is used to explicitly throw an exception within method (throw new Exception()). throws declares that method might throw exceptions, listed in method signature (throws Exception1, Exception2). throw is followed by exception instance, throws by exception types. throws required for checked exceptions not handled within method, throw used for actual exception creation.
Multi-threaded exception handling considerations: 1) Uncaught exceptions terminate thread but not JVM, use UncaughtExceptionHandler, 2) Exceptions don't propagate across thread boundaries, 3) ExecutorService tasks should handle exceptions or use Future.get(), 4) Consider thread pool behavior on exceptions, 5) Use appropriate synchronization when logging/handling exceptions.
Best practices include: 1) Handle specific exceptions before general ones, 2) Only catch exceptions you can handle meaningfully, 3) Document exceptions in Javadoc, 4) Include appropriate context in exception messages, 5) Use custom exceptions for domain-specific errors, 6) Clean up resources in finally block or use try-with-resources, 7) Log exceptions appropriately, 8) Don't swallow exceptions without good reason.
Lambda exceptions handling options: 1) Surround lambda body with try-catch, 2) Create wrapper method with try-catch, 3) Use Optional for potential nulls, 4) Use stream.filter() to skip problematic elements. For checked exceptions in streams, either wrap in unchecked exception or use specialized functional interfaces that can throw exceptions.
Stack trace provides execution path when exception occurred. Can be: 1) Accessed via getStackTrace(), 2) Filtered using setStackTrace(), 3) Truncated for performance, 4) Enhanced with custom information. Useful for debugging but capturing full trace has performance impact. Consider security implications when exposing stack traces in production.
Retry logic implementation: 1) Use loop with counter/timeout, 2) Exponential backoff between retries, 3) Catch specific exceptions that warrant retry, 4) Consider maximum retry attempts, 5) Log retry attempts, 6) Handle permanent failures appropriately. Important for distributed systems, network operations, and resilient applications.
Assertions (assert keyword) check program internal correctness, disabled by default (-ea to enable). Unlike exceptions, assertions: 1) Are development/testing tool, not error handling mechanism, 2) Should never have side effects, 3) Check internal invariants, not public API preconditions, 4) Can be completely removed by JVM. Use for debugging and testing, not production error handling.
Constructor exceptions: throw to indicate object creation failure, ensure proper cleanup, consider factory methods for alternative handling. Static initializer exceptions wrapped in ExceptionInInitializerError, can cause class initialization failure. Both cases require careful handling as they can affect object/class usability. Consider initialization-on-demand pattern for static initialization.
Logging and exception handling best practices: 1) Log at appropriate level (ERROR for exceptions), 2) Include contextual information, 3) Consider log volume in catch blocks, 4) Avoid logging and rethrowing unless adding context, 5) Use appropriate logging framework features (MDC, structured logging). Balance between debugging needs and performance/storage impact.
Exceptions in finalizers are caught by JVM and printed to error stream, but object finalization continues. Finalizer errors don't prevent garbage collection but may leave resources unclosed. Best practices: 1) Avoid finalizers, use try-with-resources, 2) Never throw exceptions from finalizers, 3) Handle all exceptions within finalizer, 4) Consider using Cleaner class (Java 9+) instead.
REST exception handling considerations: 1) Map exceptions to appropriate HTTP status codes, 2) Provide meaningful error responses in consistent format, 3) Handle both application and framework exceptions, 4) Consider security in error messages, 5) Implement global exception handler, 6) Include correlation IDs for tracking, 7) Handle validation errors consistently. Balance between client usability and security.
JVM memory is divided into several areas: 1) Heap: object storage, divided into young/old generation, 2) Stack: thread-specific, stores method frames and primitives, 3) Metaspace: class metadata (replaced PermGen in Java 8+), 4) Code Cache: JIT compiled code, 5) Native Memory: direct buffers and JNI. Each area has different lifecycle and garbage collection characteristics.
Garbage collection (GC) automatically reclaims memory from objects no longer referenced by program. Process: 1) Mark: identify live objects by tracing references, 2) Sweep: remove unreferenced objects, 3) Compact: optionally reorganize memory to reduce fragmentation. Uses generational hypothesis: most objects die young, focusing collection efforts on younger generations.
Stack: thread-specific, LIFO structure, stores method frames and primitives, automatically managed, fixed size, faster access. Heap: shared across threads, stores objects, garbage collected, dynamic size, slower access. Stack memory is automatically reclaimed when method returns, while heap memory requires garbage collection. Stack overflow occurs from excessive recursion, heap from object accumulation.
Young generation consists of Eden space and two Survivor spaces (S0, S1). New objects allocated in Eden. During Minor GC: surviving objects moved to empty Survivor space, age incremented. Objects surviving multiple GCs promoted to Old generation (tenuring). Uses copying collection for efficiency. Eden space larger than Survivor spaces based on assumption most objects die young.
Memory leaks occur when objects remain referenced but unused, preventing garbage collection. Common causes: 1) Unclosed resources, 2) Static collections/fields holding references, 3) Inner class references, 4) ThreadLocal variables, 5) Cache implementations without eviction. Prevention: proper resource cleanup, weak references where appropriate, monitoring object lifecycles, regular profiling, and avoiding long-lived object references.
Reference types: 1) Strong: normal references, prevent GC, 2) Soft: cleared when memory needed, good for caches, 3) Weak: cleared during next GC cycle, used for non-crucial caches, 4) Phantom: for resource cleanup tracking, never accessible. Each type provides different object reachability and GC behavior. Reference queues notify when references cleared.
Major collectors: 1) Serial: single-thread, small heaps/applications, 2) Parallel: multiple threads, throughput priority, 3) G1: large heaps, balanced latency/throughput, default since Java 9, 4) ZGC: ultra-low latency, large heaps, 5) Shenandoah: similar to ZGC, different implementation. Choice depends on application requirements: latency, throughput, heap size, and available resources.
String literals stored in String Pool (special memory area), reused when same literal used multiple times. String objects created with 'new' stored in heap. String.intern() adds heap String to pool. Since Java 7, String Pool moved from PermGen to heap. Pool reduces memory usage through sharing but can cause memory issues if many unique strings interned.
Key parameters: 1) -Xmx: maximum heap size, 2) -Xms: initial heap size, 3) -XX:NewRatio: young/old generation ratio, 4) -XX:SurvivorRatio: Eden/Survivor space ratio, 5) -XX:MetaspaceSize: Metaspace size. Best practices: set -Xms=-Xmx to avoid resizing, tune generation sizes based on object lifetime patterns, monitor GC logs for optimization.
G1 divides heap into equal-sized regions, each can be Eden, Survivor, Old. Works incrementally by collecting regions with most garbage first. Features: predictable pause times, concurrent marking, automatic region sizing, mixed collections. Advantages: reduced fragmentation, better predictability, suitable for large heaps. Requires more CPU resources than Parallel collector.
Metaspace (Java 8+) stores class metadata in native memory, replacing PermGen. Differences: 1) Automatically grows by default, 2) Native memory instead of JVM heap, 3) Better memory deallocation, 4) Less likely to cause OutOfMemoryError. Still requires monitoring and tuning, especially for applications loading many classes or using reflection heavily.
Diagnosis steps: 1) Analyze heap dumps (using tools like MAT), 2) Enable GC logging, 3) Monitor memory usage patterns, 4) Use profilers. Resolution: identify memory leaks, tune GC parameters, optimize object creation/retention, consider caching strategies, implement proper resource cleanup. Prevention: regular monitoring, load testing, code reviews focusing on memory usage.
Escape analysis determines if object allocation can be eliminated. If object doesn't 'escape' method scope, JVM can: 1) Allocate on stack instead of heap, 2) Perform scalar replacement (replace object with its fields), 3) Eliminate synchronization. These optimizations reduce GC pressure and improve performance. Enabled by default but requires proper conditions to trigger.
DirectByteBuffers allocate memory outside JVM heap (native memory) for better I/O performance. Implications: 1) Not subject to garbage collection, 2) Manual memory management needed, 3) Higher allocation/deallocation cost, 4) Risk of native memory leaks. Best used for long-lived buffers with significant I/O operations. Requires careful capacity planning and cleanup.
Card table is data structure tracking references from old to young generation, optimizing garbage collection. Divides heap into cards, marks cards when references updated (write barrier). Enables efficient young generation collection by scanning only relevant old generation areas. Critical for generational GC performance but adds slight overhead to reference updates.
Object pooling reuses objects instead of creating new ones. Implementation: maintain pool of pre-allocated objects, checkout/return methods, size limits, cleanup strategies. Appropriate for: expensive object creation, memory pressure reduction, connection management. Drawbacks: complexity, potential memory leaks, synchronization overhead. Modern JVM optimizations often make pooling unnecessary except for specific cases.
Memory barriers ensure memory operation ordering in concurrent programs. Types: LoadLoad, StoreStore, LoadStore, StoreLoad barriers. Impact performance by preventing CPU/compiler optimizations. Used in volatile variables, synchronization, concurrent collections. Understanding crucial for high-performance concurrent code. JMM (Java Memory Model) defines when barriers required.
Class loading stores class metadata in Metaspace, bytecode in code cache. Impacts: 1) Memory footprint increases with loaded classes, 2) Class unloading only possible during full GC, 3) Dynamic class loading can cause memory issues. Best practices: avoid unnecessary class loading, use appropriate classloader hierarchy, monitor Metaspace usage.
Thread stack size (-Xss parameter) determines memory per thread stack. Default varies by platform. Tuning needed when: 1) Deep recursion used, 2) Many threads created, 3) StackOverflowError occurs, 4) Memory optimization required. Smaller stacks allow more threads but risk overflow, larger stacks consume more memory but handle deep call chains.
Finalization delays object reclamation, requires two GC cycles. Reference processing (Soft/Weak/Phantom) adds overhead to GC. Both can cause memory leaks if not handled properly. Best practices: avoid finalization (use try-with-resources), carefully manage reference queues, understand reference processing order. Modern Java favors Cleaner over finalizers.
A thread is a lightweight unit of execution within a process. Two main ways to create threads: 1) Extending Thread class, 2) Implementing Runnable interface (preferred for better flexibility). Creation methods include using anonymous classes, lambda expressions, or ExecutorService. Thread lifecycle: New, Runnable, Blocked, Waiting, Timed Waiting, Terminated.
synchronized methods lock entire method using 'this' (instance methods) or Class object (static methods). synchronized blocks allow finer-grained locking on specific objects. Blocks are more flexible: can choose lock object, reduce lock duration, improve concurrency. Both provide mutual exclusion and establish happens-before relationship between threads.
volatile ensures variable updates are immediately visible to all threads by preventing CPU caching. Use when: 1) One thread writes, others read, 2) No atomic operations needed besides assignment, 3) Variable doesn't depend on its current value. Provides visibility guarantee but not atomicity. Common in flag variables for thread coordination.
ExecutorService manages thread pools and task execution. Benefits: 1) Reuses threads, reducing creation overhead, 2) Controls number of concurrent threads, 3) Provides task queuing and scheduling, 4) Supports different execution policies, 5) Offers Future for async results. Types include: Fixed, Cached, Scheduled thread pools. Proper shutdown handling required to prevent resource leaks.
Race conditions occur when multiple threads access shared data concurrently and at least one modifies it. Prevention methods: 1) Synchronization (synchronized, locks), 2) Atomic classes, 3) Immutable objects, 4) Thread confinement, 5) Concurrent collections. Identification through code review, testing (stress tests), and thread analysis tools. Design for thread-safety from start.
Deadlock occurs when threads wait indefinitely for each other's locks. Prevention strategies: 1) Lock ordering (acquire locks in consistent order), 2) Lock timeouts, 3) Try-lock instead of blocking locks, 4) Avoid nested locks when possible, 5) Use higher-level concurrency utilities. Detection through thread dumps and monitoring. Recovery may require application restart.
BlockingQueue implementations (ArrayBlockingQueue, LinkedBlockingQueue) provide thread-safe operations with blocking behavior. put() blocks when full, take() when empty. Used in producer-consumer pattern, thread pools, work queues. Different implementations offer trade-offs between throughput, ordering, capacity. Support fairness option at cost of throughput.
Fork/Join framework (Java 7+) implements divide-and-conquer parallelism. Uses work-stealing algorithm: idle threads steal tasks from busy ones. Components: ForkJoinPool, RecursiveTask (returns result), RecursiveAction (void). Best for CPU-intensive tasks that can be broken into smaller subtasks. Integrates with Stream API for parallel operations.
Atomic variables (AtomicInteger, AtomicReference, etc.) use Compare-And-Swap (CAS) operations for lock-free thread-safety. More efficient than synchronization for single variables. Use when: 1) Single variable updates need atomicity, 2) High contention scenarios, 3) Performance critical code. Limited to single variable operations, complex operations need different approaches.
Happens-before relationship defines memory visibility rules between operations. Key relationships: 1) Program order within thread, 2) Monitor lock/unlock, 3) volatile write/read, 4) Thread start/join. Essential for understanding when updates become visible between threads. Forms basis for Java Memory Model's consistency guarantees. Critical for writing correct concurrent code.
ReentrantLock advantages over synchronized: 1) Trylock with timeout, 2) Interruptible locking, 3) Fair queueing option, 4) Condition variables, 5) Non-block-structured locking. Disadvantages: explicit unlock required, more complex, easy to misuse. Use when advanced features needed, otherwise prefer synchronized for simplicity and automatic release.
Memory consistency errors occur when threads have inconsistent views of shared memory due to caching/reordering. Prevention: 1) Proper synchronization, 2) volatile for visibility, 3) final fields for initialization safety, 4) immutable objects, 5) happens-before relationships. Java Memory Model defines when updates must become visible. Tools like JCStress help detect issues.
CompletableFuture extends Future with composition, chaining, combining operations. Features: 1) Async computation pipelines, 2) Exception handling, 3) Timeout management, 4) Multiple completion stages, 5) Customizable execution. Simplifies async programming compared to callbacks or raw threads. Integrates with reactive programming patterns. Supports both async and sync operations.
Thread pools maintain worker threads for task execution. Sizing considerations: 1) CPU cores (CPU-bound tasks: cores+1), 2) I/O waiting time (I/O-bound tasks: higher), 3) Memory constraints, 4) Task characteristics, 5) System resources. Monitor queue size, response time, rejection rate. Dynamic sizing possible but complex. Consider separate pools for different task types.
Concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList) designed for concurrent access. Differences: 1) Better scalability through fine-grained locking, 2) Fail-safe iteration, 3) Atomic compound operations, 4) No explicit synchronization needed. Trade-offs: higher memory usage, slightly slower single-thread performance. Choose based on concurrent access patterns.
ThreadLocal provides thread-isolated variables. Use cases: 1) Per-thread context (transaction, security), 2) Thread-safe caches/buffers, 3) Per-thread counters/IDs. Memory leak risk if not properly cleaned in thread pools. InheritableThreadLocal passes values to child threads. Consider weak references for automatic cleanup.
Phaser is flexible synchronization barrier supporting dynamic party registration. Features: 1) Multiple advance phases, 2) Tree structure for scalability, 3) Arrival/completion monitoring, 4) Termination condition. Use cases: complex phased operations, dynamic thread groups, parallel decomposition. More flexible than CyclicBarrier/CountDownLatch but more complex.
Lock striping divides single lock into multiple locks for different hashcode ranges (used in ConcurrentHashMap). Lock coarsening combines adjacent synchronized blocks for performance. JVM optimizations reduce synchronization overhead. Trade-off between contention and overhead. Monitor JVM behavior to verify optimizations.
Interruption is cooperative cancellation mechanism. Proper handling: 1) Check/clear interrupted status, 2) Throw InterruptedException or restore flag, 3) Clean up resources, 4) Propagate interruption. Common in blocking operations, long-running tasks. Challenges: third-party code handling, partial results, cleanup. Design for cancellation from start.
Disruptor is high-performance inter-thread messaging library. Features: 1) Ring buffer for bounded queue, 2) Lock-free design, 3) Cache-friendly, 4) Multiple producer/consumer support. Use for high-throughput scenarios: financial trading, logging, event processing. Requires understanding of memory barriers, cache effects. Complex but highly efficient.
Key differences: 1) I/O is stream-oriented, NIO is buffer-oriented, 2) I/O operations are blocking, NIO supports non-blocking mode, 3) NIO uses channels and selectors for multiplexing, 4) NIO provides better performance for large files through memory mapping, 5) NIO offers better file system APIs through Path interface. Old I/O simpler for small files and straightforward operations.
InputStream/OutputStream handle binary data (bytes), while Reader/Writer handle character data with encoding/decoding support. Reader/Writer use character encodings (UTF-8, etc.), making them suitable for text processing. InputStreamReader/OutputStreamWriter bridge between byte and character streams. Choose based on data type: binary (streams) or text (readers/writers).
Java handles cross-platform paths through: 1) File.separator for OS-specific separator, 2) Path interface normalizing paths, 3) Paths.get() factory methods accepting variable arguments, 4) FileSystem abstraction for custom providers. Best practices: use Path interface, avoid hardcoded separators, use relative paths when possible, handle case sensitivity differences.
Buffering reduces system calls by reading/writing larger chunks. BufferedReader/BufferedWriter add buffering to character streams, improving performance. Use when: 1) Reading/writing text files line by line, 2) Frequent small reads/writes, 3) Network I/O. Buffer size affects performance - default usually sufficient. Remember to close (preferably with try-with-resources).
Memory-mapped files (MappedByteBuffer) map file content directly to memory. Advantages: 1) Faster access for large files, 2) OS-level optimization, 3) Direct memory access. Best for: large files, random access patterns, shared memory between processes. Limitations: file size constraints, resource management needed. Use FileChannel.map() to create mapping.
Serialization converts objects to byte streams for storage/transmission. Requires Serializable interface. Limitations: 1) Performance overhead, 2) Security risks, 3) Version compatibility issues, 4) All referenced objects must be serializable, 5) Transient fields not serialized. Alternatives: JSON/XML serialization, custom serialization, externalization. Consider security implications when deserializing.
FileChannel provides direct file access with features: 1) Memory-mapped files, 2) Direct buffer access, 3) File locking, 4) Scatter/gather operations. NIO Buffers offer: 1) Direct memory access, 2) Bulk data operations, 3) Buffer pooling. Performance benefits from: reduced copying, system calls, and better memory usage. Suitable for high-performance I/O.
Best practices: 1) Use try-with-resources for automatic closure, 2) Close resources in finally block if not using try-with-resources, 3) Close in reverse order of creation, 4) Handle exceptions during close, 5) Use appropriate buffer sizes, 6) Don't suppress exceptions. Consider using utility libraries (Apache Commons IO, Guava) for simplified resource management.
WatchService monitors directory for changes (create, modify, delete). Features: 1) Asynchronous notification, 2) Multiple directory monitoring, 3) Platform-specific optimizations. Implementation: register Path with WatchService, process WatchEvents in loop. Considerations: event coalescing, overflow handling, platform differences in sensitivity.
Methods: 1) Properties class load()/store(), 2) ResourceBundle for internationalization, 3) XML format with loadFromXML()/storeToXML(). Best practices: use proper encoding (UTF-8), handle missing properties, consider hierarchical properties, escape special characters. Properties files useful for configuration, externalized strings, application settings.
Strategies: 1) Memory-mapped files for random access, 2) Buffered streams for sequential access, 3) Stream API for processing, 4) Chunked reading/writing, 5) NIO channels for better performance. Consider: memory constraints, access patterns, threading model. Monitor memory usage, use profiling tools. Handle exceptions and cleanup properly.
Character encodings convert between bytes and characters. Handling: 1) Specify explicit encoding in Reader/Writer, 2) Use StandardCharsets constants, 3) Handle encoding errors (replacement, reporting), 4) Consider BOM (Byte Order Mark). Common issues: platform default encoding, corrupted data, encoding mismatch. Always specify encoding explicitly.
AsynchChannel enables non-blocking I/O operations. Benefits: 1) Improved scalability, 2) Better resource utilization, 3) Reduced thread overhead. Completion handling through: CompletionHandler, Future, or callback. Suitable for: network I/O, many concurrent operations. Requires careful error handling and completion state management.
Attributes include: basic (size, times, permissions), DOS, POSIX, ACL, user-defined. Access through: Files.getAttribute(), Files.readAttributes(), FileAttributeView. Platform-specific attributes handled through specific views. Security considerations: privilege requirements, symbolic link handling. Remember platform differences.
Custom serialization through private readObject()/writeObject() methods. Uses: 1) Control serialization format, 2) Handle sensitive data, 3) Maintain invariants, 4) Version compatibility. Implementation must handle: all fields, superclass state, validation, security. Consider readResolve()/writeReplace() for object replacement.
Absolute paths: complete path from root. Relative paths: relative to current directory. Canonical paths: absolute with symbolic links resolved, redundancies removed. Path.normalize() removes redundancies but doesn't resolve links. Canonical paths useful for comparison, security checks. Consider platform differences in path handling.
Methods: Files.createTempFile(), Files.createTempDirectory(). Best practices: 1) Use try-with-resources, 2) Set appropriate permissions, 3) Clean up on exit, 4) Handle name collisions, 5) Consider security implications. Use deleteOnExit() cautiously. Remember platform differences in temp directory location and cleanup.
FileVisitor interface enables traversal of file trees. Methods: preVisitDirectory(), visitFile(), visitFileFailed(), postVisitDirectory(). Uses: recursive operations, filtering, attribute access. SimpleFileVisitor provides default implementations. Handle: cycles, permissions, errors. Consider performance for large directories.
File locking through FileChannel: shared (read) or exclusive (write) locks. Considerations: 1) Lock granularity, 2) Cross-process coordination, 3) Deadlock prevention, 4) Platform differences. FileLock must be released explicitly. Useful for concurrent access control. Remember: some platforms don't enforce mandatory locking.
RandomAccessFile: traditional API, synchronized methods, simpler interface. FileChannel: modern API, better performance, more features (memory mapping, locks, etc.). FileChannel advantages: non-blocking operations, bulk transfers, direct buffers. Choose based on: requirements, compatibility needs, performance needs.
Generics enable types (classes and interfaces) to be parameters when defining classes, interfaces, and methods. They solve problems of: 1) Type safety by catching errors at compile time rather than runtime, 2) Elimination of type casting, 3) Enable implementation of generic algorithms. Introduced in Java 5 to provide compile-time type checking and remove risk of ClassCastException.
Type erasure is the process where compiler removes all type parameters at runtime for backward compatibility. Effects: 1) Generic type information only available at compile time, 2) Raw types at runtime, 3) Type parameters replaced with bounds or Object, 4) Bridge methods generated for inheritance. Limitations: can't create generic arrays, no runtime type information, no static fields of type parameters.
Upper bounded wildcard <? extends T> allows reading from collection but not writing (producer). Lower bounded wildcard <? super T> allows writing to collection but limited reading (consumer). PECS principle: Producer-Extends, Consumer-Super. Upper bound useful for read-only operations, lower bound for write-only operations. Unbounded wildcard <?> when both type parameters are unknown.
Generic methods declared with type parameter before return type: <T> T methodName(T arg). Can have different type parameters than containing class. Type inference often allows omitting explicit type arguments. Static generic methods possible unlike static fields. Multiple type parameters allowed. Bounded type parameters supported. Generic constructors also possible.
Bounded type parameters restrict what types can be used with a generic class/method. Single bound: <T extends UpperBound>, multiple bounds: <T extends Class & Interface1 & Interface2>. Uses: 1) Accessing methods of bound type, 2) Ensuring type compatibility, 3) Expressing relationships between types. Class must be first in multiple bounds. Improves API design and type safety.
Type erasure can cause method signature conflicts: List<String> and List<Integer> become same signature after erasure. Bridge methods generated for covariant returns in inheritance. Cannot overload methods differing only in generic type parameters. Solutions: use different method names, add additional parameters, use type tokens. Important for API design and inheritance hierarchies.
Reifiable types maintain runtime information: primitives, non-generic types, raw types, unbounded wildcards. Non-reifiable types lost during type erasure: generic types with parameters, bounded wildcards. Implications: can't create generic arrays, instanceof limitations, method overloading restrictions. Important for understanding generic type system limitations.
Generic singleton challenges: 1) Type parameter not available for static methods/fields, 2) Multiple type parameters create multiple instances. Solutions: 1) Generic static factory method, 2) Enum-based implementation, 3) Type token approach. Consider thread safety, serialization. Best practices: limit type parameters, document limitations, consider builder pattern alternative.
PECS: Producer Extends, Consumer Super. Use <? extends T> when reading values (producing), <? super T> when writing values (consuming). Examples: copy(List<? extends T> src, List<? super T> dest). Improves API flexibility and type safety. Related to covariance and contravariance. Essential for collection framework design.
Generic type inheritance rules: 1) Box<Integer> not subtype of Box<Number> even if Integer extends Number, 2) Generic class inheritance uses type parameters in extends clause, 3) Raw types bypass generic type checking. Bridge methods handle inheritance with different type parameters. Consider bounded type parameters for hierarchies.
Cannot create arrays of generic types due to type erasure. Workarounds: 1) Create Object array and cast, 2) Use ArrayList instead, 3) Pass array creation to method parameter, 4) Use reflection (Array.newInstance). Each approach has trade-offs in type safety, performance, complexity. Consider collections as alternative to generic arrays.
Type tokens pass Class<T> objects to maintain runtime type information. Uses: 1) Generic factory methods, 2) Type-safe heterogeneous containers, 3) Reflection with generics. Implementation using Class.cast(), instanceof checks. Super type tokens (Neal Gafter) for preserving generic type information. Consider performance implications.
Recursive type bound: <T extends Comparable<T>>. Uses: 1) Implementing comparable types, 2) Builder pattern with fluent interface, 3) Self-referential generic types. Examples in natural ordering, comparable collections. Ensures type safety in comparison operations. Common in API design for fluent interfaces and type-safe comparisons.
Variance handled through wildcards: covariance (? extends T), contravariance (? super T), invariance (T). Use cases: 1) Collection APIs, 2) Method parameters, 3) Return types. Related to Liskov Substitution Principle. Important for API design and collection framework usage. Consider PECS principle for proper usage.
Raw types are generic types without type parameters (List instead of List<String>). Problems: 1) Loss of type safety, 2) Potential runtime errors, 3) Mixing generic/non-generic code. Used for backward compatibility with pre-generics code. Modern Java should always use parameterized types. Exception: Class literals must use raw types.
Type constraints implemented through: 1) Bounded type parameters, 2) Multiple bounds, 3) Recursive type bounds, 4) Wildcards with bounds. Uses: ensuring method availability, type relationships, API contracts. Consider: readability, maintainability, type hierarchy complexity. Document constraints clearly. Balance flexibility and type safety.
Heap pollution: variable of parameterized type refers to object not of that type. Causes: 1) Mixing raw and generic types, 2) Unchecked warnings, 3) Array covariance with generics. Prevention: 1) Address unchecked warnings, 2) Avoid raw types, 3) Use @SafeVarargs when appropriate. Can lead to runtime failures. Important for type safety.
Reflection with generics challenges: 1) Type erasure limits runtime information, 2) Getting/setting generic types, 3) Creating generic arrays. Solutions: Type tokens, ParameterizedType interface, GenericArrayType. Consider performance impact, maintainability. Useful for frameworks, dependency injection. Document reflection usage clearly.
Type witnesses explicitly specify type parameters when type inference fails. Required when: 1) Diamond operator ambiguity, 2) Generic method calls, 3) Anonymous inner classes. Syntax: ClassName.<Type>methodName(args) or new ClassName<Type>(). Improves code clarity, prevents inference errors. Consider readability vs verbosity trade-off.
Generic API design principles: 1) Make declarations as general as possible, 2) Use bounded wildcards for flexibility, 3) Consider type parameter naming conventions, 4) Document type parameters, 5) Provide raw type compatibility, 6) Consider builder pattern for multiple type parameters. Balance usability, type safety, and maintainability. Address backward compatibility.
Main JDBC components: 1) DriverManager: manages database drivers, 2) Connection: represents database connection, 3) Statement/PreparedStatement/CallableStatement: execute SQL, 4) ResultSet: holds query results, 5) DataSource: for connection pooling and distributed transactions. Each component serves specific purpose in database interaction. Modern applications typically use DataSource instead of DriverManager.
Statement: basic SQL execution, no pre-compilation. PreparedStatement: pre-compiled SQL, prevents SQL injection, better performance for repeated execution, supports parameters. CallableStatement: extends PreparedStatement for stored procedure calls. PreparedStatement preferred for most use cases due to security and performance benefits. Use Statement only for one-time, dynamic SQL.
Transaction handling: 1) Connection.setAutoCommit(false) to start transaction, 2) Execute SQL statements, 3) commit() on success, rollback() on failure, 4) Use try-with-resources and finally for proper cleanup. Best practices: appropriate isolation level, transaction boundaries, error handling. Consider using transaction management frameworks for complex scenarios.
Connection pooling maintains cache of database connections for reuse, improving performance. Implementation: 1) Use DataSource implementation (C3P0, HikariCP, etc.), 2) Configure pool size, timeout, validation, 3) Handle connection leaks. Benefits: reduced overhead, better resource utilization, connection management. Important settings: max pool size, idle timeout, validation query.
SQL injection prevention: 1) Use PreparedStatement with parameterized queries, 2) Never concatenate user input into SQL strings, 3) Validate and sanitize input, 4) Use stored procedures when appropriate, 5) Implement proper access controls. Additional measures: escape special characters, use proper permissions, regular security audits. Critical for application security.
ResultSet types: TYPE_FORWARD_ONLY (default, forward-only cursor), TYPE_SCROLL_INSENSITIVE (scrollable, snapshot), TYPE_SCROLL_SENSITIVE (scrollable, reflects changes). Concurrency modes: CONCUR_READ_ONLY (default), CONCUR_UPDATABLE (allows updates). Holdability: HOLD_CURSORS_OVER_COMMIT, CLOSE_CURSORS_AT_COMMIT. Choose based on requirements and database support.
Large object handling: 1) Use streaming methods (setBlob/setClob) for writing, 2) Stream in chunks when reading, 3) Consider performance and memory implications, 4) Properly close resources. Best practices: use try-with-resources, appropriate buffer sizes, transaction management. Consider file system storage for very large objects.
Batch updates allow multiple SQL statements to be executed in single database round trip. Implementation: addBatch() to queue statements, executeBatch() to execute. Benefits: improved performance for multiple operations, reduced network traffic. Use cases: bulk inserts, multiple related updates. Consider batch size, error handling, and transaction boundaries.
Database metadata accessed through DatabaseMetaData interface. Uses: 1) Query database capabilities, 2) Get table/column information, 3) Discover supported features, 4) Schema introspection. Important for: dynamic SQL generation, database-independent code, tool development. Consider caching metadata for performance. Handle database-specific variations.
Isolation levels: READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE. Each level addresses different concurrency issues: dirty reads, non-repeatable reads, phantom reads. Higher isolation provides more consistency but reduces concurrency. Choose based on requirements: data consistency needs vs performance/scalability.
HikariCP implementation: 1) Configure HikariConfig with pool properties, 2) Create HikariDataSource, 3) Manage connection lifecycle. Key settings: maximum pool size, connection timeout, idle timeout, minimum idle. Best practices: proper sizing, connection testing, metrics monitoring. Popular choice due to performance and reliability.
Exception handling practices: 1) Use appropriate exception types (SQLException hierarchy), 2) Properly close resources in finally block or try-with-resources, 3) Log relevant error information, 4) Implement proper retry logic, 5) Wrap in domain-specific exceptions. Consider: transaction rollback, connection state, cleanup procedures. Balance between recovery and failure propagation.
Database vendor handling: 1) Use vendor-neutral SQL, 2) Abstract vendor-specific code, 3) Use DatabaseMetaData for feature detection, 4) Implement dialect patterns for SQL differences. Consider: pagination differences, data type mappings, feature support. Use database abstraction layers or JPA for better portability.
NULL handling: 1) Use setNull() for parameters, 2) wasNull() for primitive type reads, 3) Handle null in result set appropriately, 4) Consider database-specific NULL handling. Best practices: proper null checks, default values where appropriate, consistent null handling strategy. Important for data integrity and application robustness.
Connection retry/failover: 1) Implement retry logic with exponential backoff, 2) Handle different failure types appropriately, 3) Use connection pools with test-on-borrow, 4) Implement circuit breaker pattern. Consider: timeout settings, maximum retry attempts, failover nodes. Balance between availability and response time.
Savepoints create points within transaction to roll back to, without rolling back entire transaction. Usage: 1) Complex transactions with partial rollback needs, 2) Nested transaction-like behavior, 3) Error recovery within transaction. Considerations: performance impact, database support, proper release. Use sparingly due to complexity.
Performance optimization: 1) Use connection pooling, 2) Batch updates for bulk operations, 3) Appropriate fetch size for ResultSet, 4) Proper transaction boundaries, 5) Prepared statement caching, 6) Index usage awareness. Monitor: connection usage, query performance, resource utilization. Balance between performance and maintainability.
Type mapping approaches: 1) Standard JDBC type mappings, 2) Custom type mappings using SQLData interface, 3) Handle vendor-specific types. Considerations: precision/scale for numbers, timezone for dates, character encoding. Important for data integrity and application correctness. Document mapping decisions.
Connection validation: 1) Test-on-borrow/return in connection pool, 2) Validation query configuration, 3) Connection timeout settings, 4) Idle connection testing. Importance: preventing stale connections, ensuring reliability. Balance validation frequency with performance impact. Consider database-specific validation queries.
DataSource benefits: 1) Connection pooling support, 2) Distributed transaction integration, 3) Better resource management, 4) Container management capability. Implementation: JNDI lookup, programmatic configuration. Important for enterprise applications. Prefer over DriverManager for production use. Consider security integration.
Solve Java coding challenges designed for real-world relevance.
Learn MoreUnderstand Java fundamentals like OOP, multithreading, and memory management.
Use platforms like Stark.ai to solve coding challenges.
Showcase your experience with Java applications in interviews.
Be ready to explain how Java helped you solve specific problems.
Join thousands of successful candidates who prepared with Stark.ai. Start practicing Java questions, mock interviews, and more to secure your dream role.
Start Preparing now