JVM (Java Virtual Machine): An abstract machine that provides a runtime environment to execute Java bytecode. It's platform-specific and handles memory management, garbage collection, and bytecode interpretation.
JRE (Java Runtime Environment): Contains JVM along with core libraries and other components needed to run Java applications. It's what end-users need to run Java programs.
JDK (Java Development Kit): A complete development environment that includes JRE plus development tools like compiler (javac), debugger, documentation generator (javadoc), and other utilities needed to develop Java applications.
The main()
method is the entry point of every Java application. When you run a Java program, the JVM looks for this specific method to start execution.
Traditional Syntax:
public static void main(String[] args)
Breakdown:
public
- JVM needs access from outside the classstatic
- Called without creating an object instancevoid
- Doesn't return anythingmain
- JVM specifically looks for this method nameString[] args
- Command-line argumentsExample:
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); if (args.length > 0) { System.out.println("First argument: " + args[0]); } } }
Modern Java (21+) - Simplified for Learning:
// No class declaration needed! void main() { System.out.println("Hello, World!"); } // With command line arguments void main(String[] args) { System.out.println("Hello " + (args.length > 0 ? args[0] : "World")); }
Key Points:
main()
method as entry pointstatic
because JVM calls it before any objects are createdvoid main()
) for educational purposesEncapsulation Example:
public class BankAccount { private double balance; // Encapsulated field public void deposit(double amount) { if (amount > 0) { balance += amount; } } public double getBalance() { return balance; // Controlled access } }
Inheritance Example:
// Parent class class Animal { protected String name; public void eat() { System.out.println(name + " is eating"); } } // Child class class Dog extends Animal { public void bark() { System.out.println(name + " is barking"); } }
Polymorphism Example:
interface Shape { double calculateArea(); } class Circle implements Shape { private double radius; public double calculateArea() { return Math.PI * radius * radius; } } class Rectangle implements Shape { private double length, width; public double calculateArea() { return length * width; } }
public class StringComparison { public static void main(String[] args) { // Different objects with same content String str1 = new String("Hello"); String str2 = new String("Hello"); // Reference comparison System.out.println(str1 == str2); // false - different objects // Content comparison System.out.println(str1.equals(str2)); // true - same content // String literals (same object due to string pool) String str3 = "Hello"; String str4 = "Hello"; System.out.println(str3 == str4); // true - same object in pool System.out.println(str3.equals(str4)); // true - same content } }
Key Points:
==
compares memory addresses/references.equals()
compares actual content==
might return true.equals()
for content comparisonException handling example:
public class ExceptionHandlingExample { public static void main(String[] args) { try { // Checked exception - must be handled FileReader file = new FileReader("nonexistent.txt"); // Unchecked exception - optional to handle int result = 10 / 0; } catch (FileNotFoundException e) { System.out.println("File not found: " + e.getMessage()); } catch (ArithmeticException e) { System.out.println("Arithmetic error: " + e.getMessage()); } finally { System.out.println("Cleanup code here"); } } // Method that throws checked exception public void readFile() throws IOException { throw new IOException("File operation failed"); } }
Key Differences:
The String Pool (also called String Constant Pool) is a special memory area in the Java heap where string literals are stored. It's designed to save memory by reusing string objects.
Key Points:
new String()
creates objects in heap, not in poolintern()
method adds strings to pool and returns referenceArrayList vs LinkedList in Java
Java provides two commonly used List implementations: ArrayList
and LinkedList
. Both implement the List
interface but have different internal structures and performance characteristics.
get(index)
), but inserting or removing elements (except at the end) is slow because elements must be shifted.Performance Comparison Table
Operation | ArrayList | LinkedList |
---|---|---|
Get by index | O(1) | O(n) |
Add at end | O(1) amortized | O(1) |
Add at beginning | O(n) | O(1) |
Add at middle | O(n) | O(n) |
Remove by index | O(n) | O(n) |
Remove at ends | O(1) amortized | O(1) |
Memory overhead | Low | High (extra pointers) |
Example Code:
Abstract Class vs Interface in Java: Key Differences
Feature | Abstract Class | Interface |
---|---|---|
Methods | Abstract & concrete | Abstract (Java 7-), default/static (8+) |
Variables | Any type, any modifier | public static final only |
Constructors | Allowed | Not allowed |
Multiple Inheritance | Not supported | Supported (via interfaces) |
Access Modifiers | Any (public/protected/private) | Only public |
Inheritance | Single (extends) | Multiple (implements) |
Abstraction Level | 0ā100% | 100% (pure) |
Relationship | IS-A | CAN-DO |
Use Case | Base class with shared code | Contract for capabilities |
When to use which?
Abstract Class Example:
// Abstract class - can have concrete and abstract methods abstract class Vehicle { // Concrete method public void startEngine() { System.out.println("Engine started"); } // Abstract method - must be implemented by subclasses public abstract void accelerate(); // Can have fields protected String brand; protected int year; // Can have constructor public Vehicle(String brand, int year) { this.brand = brand; this.year = year; } } // Concrete class extending abstract class class Car extends Vehicle { public Car(String brand, int year) { super(brand, year); } @Override public void accelerate() { System.out.println("Car accelerating..."); } } // Usage Vehicle car = new Car("Toyota", 2023); car.startEngine(); // Inherited concrete method car.accelerate(); // Overridden abstract method
Interface Example:
// Interface - only abstract methods (before Java 8) interface Flyable { void fly(); // Implicitly public abstract } interface Swimmable { void swim(); // Implicitly public abstract } // Class can implement multiple interfaces class Duck implements Flyable, Swimmable { @Override public void fly() { System.out.println("Duck flying..."); } @Override public void swim() { System.out.println("Duck swimming..."); } } // Java 8+ interfaces can have default and static methods interface ModernInterface { // Abstract method void doSomething(); // Default method - provides implementation default void doSomethingElse() { System.out.println("Default implementation"); } // Static method static void utilityMethod() { System.out.println("Static utility method"); } } // Usage Duck duck = new Duck(); duck.fly(); // Interface method duck.swim(); // Interface method ModernInterface.utilityMethod(); // Static method call
Key Differences in Practice:
// Abstract class - single inheritance, can have state abstract class Animal { protected String name; // Can have fields public Animal(String name) { this.name = name; // Can have constructor } public abstract void makeSound(); public void sleep() { // Can have concrete methods System.out.println(name + " is sleeping"); } } // Interface - multiple inheritance, no state (before Java 8) interface Pet { void play(); // Only abstract methods } interface Domestic { void feed(); } // Class can extend one abstract class and implement multiple interfaces class Dog extends Animal implements Pet, Domestic { public Dog(String name) { super(name); } @Override public void makeSound() { System.out.println(name + " barks"); } @Override public void play() { System.out.println(name + " plays fetch"); } @Override public void feed() { System.out.println(name + " eats dog food"); } }
Visual Summary:
Aspect | Method Overloading | Method Overriding |
---|---|---|
Where? | Same class | Subclass (child) |
Signature | Same name, different parameters | Same name & parameters |
Return type | Can be same or different | Must be same or covariant |
Timing | Compile-time (static) | Runtime (dynamic) |
Purpose | Increase method flexibility | Change/inherit behavior |
Method Overloading Example:
public class Calculator { // Method overloading - same name, different parameters public int add(int a, int b) { return a + b; } public double add(double a, double b) { return a + b; } public int add(int a, int b, int c) { return a + b + c; } public String add(String a, String b) { return a + b; } } // Usage Calculator calc = new Calculator(); System.out.println(calc.add(5, 3)); // Output: 8 System.out.println(calc.add(5.5, 3.2)); // Output: 8.7 System.out.println(calc.add(1, 2, 3)); // Output: 6 System.out.println(calc.add("Hello ", "World")); // Output: Hello World
Method Overriding Example:
// Parent class class Animal { public void makeSound() { System.out.println("Some animal sound"); } public void eat() { System.out.println("Animal is eating"); } } // Child class overriding parent methods class Dog extends Animal { @Override public void makeSound() { System.out.println("Woof! Woof!"); } @Override public void eat() { System.out.println("Dog is eating bones"); } } class Cat extends Animal { @Override public void makeSound() { System.out.println("Meow! Meow!"); } @Override public void eat() { System.out.println("Cat is eating fish"); } } // Usage demonstrating runtime polymorphism Animal animal1 = new Dog(); Animal animal2 = new Cat(); animal1.makeSound(); // Output: Woof! Woof! animal2.makeSound(); // Output: Meow! Meow! animal1.eat(); // Output: Dog is eating bones animal2.eat(); // Output: Cat is eating fish
Keyword | Type | Purpose | Usage |
---|---|---|---|
final | Modifier | Makes entities unchangeable | Variables, methods, classes |
finally | Block | Guaranteed execution | Exception handling |
finalize() | Method | Object cleanup before GC | Garbage collection |
final keyword examples:
public class FinalExample { // Final variable - constant public static final double PI = 3.14159; private final List<String> names = new ArrayList<>(); // Final method - cannot be overridden public final void displayInfo() { System.out.println("This method cannot be overridden"); } public void demonstrateFinal() { // Final local variable final int x = 10; // x = 20; // Compilation error - cannot reassign // Final reference - object can be modified, reference cannot change names.add("John"); // This is allowed // names = new ArrayList<>(); // Compilation error } } // Final class - cannot be inherited final class ImmutableClass { private final String value; public ImmutableClass(String value) { this.value = value; } public String getValue() { return value; } } // class ChildClass extends ImmutableClass {} // Compilation error
finally block example:
public class FinallyExample { public void demonstrateFinally() { FileInputStream fis = null; try { fis = new FileInputStream("file.txt"); // Some file operations int result = 10 / 0; // This will throw ArithmeticException } catch (FileNotFoundException e) { System.out.println("File not found: " + e.getMessage()); } catch (ArithmeticException e) { System.out.println("Arithmetic error: " + e.getMessage()); } finally { // This block always executes System.out.println("Finally block executed"); if (fis != null) { try { fis.close(); System.out.println("File closed"); } catch (IOException e) { System.out.println("Error closing file"); } } } } public int finallyWithReturn() { try { return 1; } finally { System.out.println("Finally executed even with return"); // return 2; // This would override the try block return } } }
finalize() method example:
public class FinalizeExample { private String resourceName; public FinalizeExample(String resourceName) { this.resourceName = resourceName; System.out.println("Resource " + resourceName + " created"); } // finalize() method - called by GC before object destruction @Override protected void finalize() throws Throwable { try { System.out.println("Finalizing resource: " + resourceName); // Cleanup code here (not recommended approach) } finally { super.finalize(); } } public static void main(String[] args) { // Create objects FinalizeExample obj1 = new FinalizeExample("Object1"); FinalizeExample obj2 = new FinalizeExample("Object2"); // Remove references obj1 = null; obj2 = null; // Suggest garbage collection (not guaranteed) System.gc(); try { Thread.sleep(1000); // Wait for GC } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Program ends"); } }
Important Notes:
finalize()
is deprecated since Java 9finally
block doesn't execute if JVM exits (System.exit()) or thread is killedFor more details on garbage collection and memory management, you can read more about it here.
final
keyword helps in creating immutable objects and preventing inheritanceSingleton ensures only one instance of a class exists and provides global access to it.
Common Use Cases:
Examples
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } public void doSomething() { System.out.println("Doing something..."); } }
The Java Collections Framework is a set of classes and interfaces that provides built-in data structures to store, manage, and manipulate groups of objects. It includes interfaces like List
, Set
, Queue
, and Map
, along with their standard implementations such as ArrayList
, HashSet
, LinkedList
, and HashMap
. The framework standardizes how collections are handled, making code more reusable, efficient, and easy to maintain.
Collection Framework Examples:
1. List Examples:
import java.util.*; public class ListExamples { public static void main(String[] args) { // ArrayList - Best for random access List<String> arrayList = new ArrayList<>(); arrayList.add("Apple"); arrayList.add("Banana"); arrayList.add("Cherry"); arrayList.add("Apple"); // Duplicates allowed System.out.println("ArrayList: " + arrayList); System.out.println("Element at index 1: " + arrayList.get(1)); // LinkedList - Best for insertions/deletions List<Integer> linkedList = new LinkedList<>(); linkedList.add(10); linkedList.add(20); linkedList.add(0, 5); // Insert at beginning System.out.println("LinkedList: " + linkedList); // Vector - Thread-safe, legacy Vector<String> vector = new Vector<>(); vector.add("Old"); vector.add("School"); System.out.println("Vector: " + vector); } }
2. Set Examples:
import java.util.*; public class SetExamples { public static void main(String[] args) { // HashSet - No order, fast operations Set<String> hashSet = new HashSet<>(); hashSet.add("Red"); hashSet.add("Green"); hashSet.add("Blue"); hashSet.add("Red"); // Duplicate ignored System.out.println("HashSet: " + hashSet); // TreeSet - Sorted order Set<Integer> treeSet = new TreeSet<>(); treeSet.add(30); treeSet.add(10); treeSet.add(20); treeSet.add(10); // Duplicate ignored System.out.println("TreeSet: " + treeSet); // [10, 20, 30] // LinkedHashSet - Insertion order maintained Set<String> linkedHashSet = new LinkedHashSet<>(); linkedHashSet.add("First"); linkedHashSet.add("Second"); linkedHashSet.add("Third"); System.out.println("LinkedHashSet: " + linkedHashSet); } }
3. Map Examples:
import java.util.*; public class MapExamples { public static void main(String[] args) { // HashMap - No order, fast operations Map<String, Integer> hashMap = new HashMap<>(); hashMap.put("Alice", 25); hashMap.put("Bob", 30); hashMap.put("Charlie", 35); System.out.println("HashMap: " + hashMap); System.out.println("Alice's age: " + hashMap.get("Alice")); // TreeMap - Sorted by keys Map<String, String> treeMap = new TreeMap<>(); treeMap.put("Zebra", "Black and White"); treeMap.put("Apple", "Red"); treeMap.put("Banana", "Yellow"); System.out.println("TreeMap: " + treeMap); // Sorted by keys // LinkedHashMap - Insertion order Map<Integer, String> linkedHashMap = new LinkedHashMap<>(); linkedHashMap.put(3, "Three"); linkedHashMap.put(1, "One"); linkedHashMap.put(2, "Two"); System.out.println("LinkedHashMap: " + linkedHashMap); // Iterate through map for (Map.Entry<String, Integer> entry : hashMap.entrySet()) { System.out.println(entry.getKey() + " -> " + entry.getValue()); } } }
4. Queue Examples:
import java.util.*; public class QueueExamples { public static void main(String[] args) { // PriorityQueue - Natural ordering Queue<Integer> priorityQueue = new PriorityQueue<>(); priorityQueue.offer(30); priorityQueue.offer(10); priorityQueue.offer(20); System.out.println("PriorityQueue polling: "); while (!priorityQueue.isEmpty()) { System.out.println(priorityQueue.poll()); // 10, 20, 30 } // ArrayDeque - Double-ended queue Deque<String> deque = new ArrayDeque<>(); deque.addFirst("First"); deque.addLast("Last"); deque.addFirst("New First"); System.out.println("Deque: " + deque); System.out.println("Remove first: " + deque.removeFirst()); System.out.println("Remove last: " + deque.removeLast()); } }
Performance Comparison:
Collection | Get | Add | Remove | Contains |
---|---|---|---|---|
ArrayList | O(1) | O(1)* | O(n) | O(n) |
LinkedList | O(n) | O(1) | O(1)** | O(n) |
HashSet | N/A | O(1) | O(1) | O(1) |
TreeSet | N/A | O(log n) | O(log n) | O(log n) |
HashMap | O(1) | O(1) | O(1) | O(1) |
TreeMap | O(log n) | O(log n) | O(log n) | O(log n) |
HashMap vs ConcurrentHashMap Examples:
1. HashMap (Not Thread-Safe):
import java.util.*; import java.util.concurrent.*; public class HashMapExample { public static void main(String[] args) throws InterruptedException { Map<Integer, String> hashMap = new HashMap<>(); // Single thread - works fine hashMap.put(1, "One"); hashMap.put(2, "Two"); hashMap.put(3, "Three"); System.out.println("HashMap: " + hashMap); // Multi-threaded scenario - dangerous! demonstrateThreadUnsafety(); } public static void demonstrateThreadUnsafety() throws InterruptedException { Map<Integer, Integer> map = new HashMap<>(); // Multiple threads modifying HashMap concurrently ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 100; i++) { final int key = i; executor.submit(() -> { map.put(key, key * 2); // Potential data corruption }); } executor.shutdown(); executor.awaitTermination(1, TimeUnit.SECONDS); System.out.println("HashMap size (should be 100): " + map.size()); // Size might be less than 100 due to data corruption } }
2. ConcurrentHashMap (Thread-Safe):
import java.util.concurrent.*; import java.util.*; public class ConcurrentHashMapExample { public static void main(String[] args) throws InterruptedException { // Thread-safe map ConcurrentHashMap<Integer, String> concurrentMap = new ConcurrentHashMap<>(); concurrentMap.put(1, "One"); concurrentMap.put(2, "Two"); concurrentMap.put(3, "Three"); System.out.println("ConcurrentHashMap: " + concurrentMap); // Safe concurrent operations demonstrateThreadSafety(); // Atomic operations demonstrateAtomicOperations(); } public static void demonstrateThreadSafety() throws InterruptedException { ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>(); ExecutorService executor = Executors.newFixedThreadPool(10); // Multiple threads safely modifying ConcurrentHashMap for (int i = 0; i < 1000; i++) { final int key = i; executor.submit(() -> { map.put(key, key * 2); // Thread-safe operation }); } executor.shutdown(); executor.awaitTermination(1, TimeUnit.SECONDS); System.out.println("ConcurrentHashMap size: " + map.size()); // Always 1000 } public static void demonstrateAtomicOperations() { ConcurrentHashMap<String, Integer> counter = new ConcurrentHashMap<>(); // Atomic increment operations counter.put("count", 0); // Using compute methods for atomic updates counter.compute("count", (key, val) -> val == null ? 1 : val + 1); counter.computeIfAbsent("newKey", key -> 100); counter.computeIfPresent("count", (key, val) -> val * 2); // Merge operation counter.merge("count", 10, Integer::sum); System.out.println("Counter: " + counter); // Bulk operations (Java 8+) counter.forEach((key, value) -> System.out.println(key + " -> " + value)); // Parallel operations long sum = counter.reduceValues(1, Integer::sum); System.out.println("Sum of all values: " + sum); } }
Key Differences Summary:
Feature | HashMap | ConcurrentHashMap |
---|---|---|
Thread Safety | ā Not thread-safe | ā Thread-safe |
Performance (Single) | ā Faster | ā Slightly slower |
Performance (Multi) | ā Poor with sync | ā Excellent |
Null Values | ā Allows | ā Not allowed |
Locking | ā External sync needed | ā Internal segment locks |
Iterators | ā Fail-fast | ā Weakly consistent |
Memory | ā Lower overhead | ā Higher overhead |
Since Version | Java 1.2 | Java 1.5 |
Answer:
Diagram:
Code Example:
public class MemoryDemo { public static void main(String[] args) { int a = 10; // Stack MyObj obj = new MyObj(); // obj ref in Stack, object in Heap } } class MyObj {}
For more details on JVM memory architecture and troubleshooting, you can read more about it here.
Answer:
Diagram:
Code Example:
public class ThreadDemo extends Thread { public void run() { System.out.println("Thread running"); } public static void main(String[] args) { ThreadDemo t1 = new ThreadDemo(); t1.start(); } }
For more details refer this blog.
Answer:
The transient
keyword prevents fields from being serialized. When an object is serialized, transient fields are skipped.
Code Example:
class User implements Serializable { private String name; private transient String password; // Not serialized }
Answer:
volatile
ensures that changes to a variable are visible to all threads. It prevents caching of variables in threads.
Code Example:
class Shared { private volatile boolean flag = false; // ... }
Answer: The Java Memory Model (JMM) is a specification that describes how the Java virtual machine works with computer memory, especially in the context of multithreaded programs. It defines how and when changes made by one thread to shared variables become visible to other threads, and what kinds of low-level memory operations (such as reordering or caching) are allowed by the JVM and hardware.
JMM ensures three key properties:
The JMM is crucial for writing correct concurrent code in Java. It explains why you need to use synchronization, volatile
, or other concurrency constructs to ensure that threads see consistent and up-to-date values of shared variables. Without proper synchronization, threads may see stale or inconsistent data due to caching or reordering by the JVM or CPU.
For more details on JVM memory architecture and troubleshooting, you can read more about it here.
Answer:
For more details on exception handling and testing, you can read more about it here.
Code Example:
private void methodThrowingUncheckedException() { throw new NullPointerException(); } private void methodThrowingCheckedException() throws IOException { throw new IOException(); } public static void main(String[] args) { try { methodThrowingCheckedException(); // Need to handle the exception and add throws IOException in the method signature } catch (IOException e) { // Handle the exception } methodThrowingUncheckedException(); // No need to handle the exception }
Answer:
super
refers to the immediate parent class object. Used to access parent class methods, variables, and constructors.
Code Example:
class Parent { void show() { System.out.println("Parent"); } } class Child extends Parent { void show() { super.show(); // Calls Parent's show System.out.println("Child"); } }
Answer: If a subclass defines a static method with the same signature as a static method in the parent class, the method in the child hides the one in the parent.
Code Example:
class A { static void display() { System.out.println("A"); } } class B extends A { static void display() { System.out.println("B"); } }
Answer:
Code Example:
// Shallow copy List<String> list1 = new ArrayList<>(); List<String> list2 = list1; // Deep copy List<String> list3 = new ArrayList<>(list1);
Answer:
this
refers to the current object instance. Used to resolve ambiguity between instance variables and parameters.
Code Example:
class Demo { int x; Demo(int x) { this.x = x; } }
Answer:
==
compares references.equals()
compares values.hashCode()
returns an integer for hashing.Code Example:
String a = new String("test"); String b = new String("test"); System.out.println(a == b); // false System.out.println(a.equals(b)); // true System.out.println(a.hashCode() == b.hashCode()); // true
Answer: Checks if an object is an instance of a specific class or interface.
Code Example:
if (obj instanceof String) { // obj is a String }
In modern Java you can use instanceof
with pattern matching to simplify the code. You can read more about it Java 23 Pattern Matching.
Answer:
Code Example:
Integer a = 5; // Autoboxing int b = a; // Unboxing
Answer: Introduced in Java 8, it processes collections of objects in a functional style.
Code Example:
List<String> list = Arrays.asList("a", "b"); list.stream().forEach(System.out::println);
For more details on Stream API and functional programming, you can read more about it here.
Answer: A container object which may or may not contain a non-null value. Helps avoid NullPointerException.
Code Example:
Optional<String> opt = Optional.of("test"); opt.ifPresent(System.out::println);
For more details on Optional and modern Java features, you can read more about it here.
Answer:
Both Comparable
and Comparator
are used for sorting objects, but they serve different purposes:
compareTo()
method within the class itself.compare()
method in a separate class or lambda expression.Key Differences:
Aspect | Comparable | Comparator |
---|---|---|
Package | java.lang.Comparable | java.util.Comparator |
Method | compareTo(T o) | compare(T o1, T o2) |
Implementation | Inside the class | Separate class or lambda |
Natural Order | Defines natural ordering | Defines custom ordering |
Modification | Requires class modification | No class modification needed |
Multiple Orders | Only one natural order | Multiple custom orders |
Usage | Collections.sort(list) | Collections.sort(list, comparator) |
1. Comparable Example - Natural Ordering:
// Student class with natural ordering by ID public class Student implements Comparable<Student> { private int id; private String name; private double gpa; public Student(int id, String name, double gpa) { this.id = id; this.name = name; this.gpa = gpa; } // Natural ordering: by ID (ascending) @Override public int compareTo(Student other) { return Integer.compare(this.id, other.id); } // Getters public int getId() { return id; } public String getName() { return name; } public double getGpa() { return gpa; } @Override public String toString() { return "Student{id=" + id + ", name='" + name + "', gpa=" + gpa + "}"; } } // Usage public class ComparableExample { public static void main(String[] args) { List<Student> students = Arrays.asList( new Student(3, "Alice", 3.8), new Student(1, "Bob", 3.5), new Student(2, "Charlie", 3.9) ); System.out.println("Before sorting: " + students); // Natural sorting (by ID) Collections.sort(students); System.out.println("After sorting by ID: " + students); // Also works with TreeSet TreeSet<Student> studentSet = new TreeSet<>(students); System.out.println("TreeSet (sorted by ID): " + studentSet); } }
2. Comparator Examples - Custom Ordering:
// Multiple comparators for different sorting criteria public class StudentComparators { // Comparator for sorting by name public static class NameComparator implements Comparator<Student> { @Override public int compare(Student s1, Student s2) { return s1.getName().compareTo(s2.getName()); } } // Comparator for sorting by GPA (descending) public static class GpaComparator implements Comparator<Student> { @Override public int compare(Student s1, Student s2) { return Double.compare(s2.getGpa(), s1.getGpa()); // Descending order } } // Comparator for sorting by GPA then by name public static class GpaThenNameComparator implements Comparator<Student> { @Override public int compare(Student s1, Student s2) { int gpaComparison = Double.compare(s2.getGpa(), s1.getGpa()); if (gpaComparison != 0) { return gpaComparison; } return s1.getName().compareTo(s2.getName()); } } } // Usage with different comparators public class ComparatorExample { public static void main(String[] args) { List<Student> students = Arrays.asList( new Student(3, "Alice", 3.8), new Student(1, "Bob", 3.5), new Student(2, "Charlie", 3.9), new Student(4, "Alice", 3.8) // Same name and GPA as first Alice ); System.out.println("Original list: " + students); // Sort by name Collections.sort(students, new StudentComparators.NameComparator()); System.out.println("Sorted by name: " + students); // Sort by GPA (descending) Collections.sort(students, new StudentComparators.GpaComparator()); System.out.println("Sorted by GPA (descending): " + students); // Sort by GPA then by name Collections.sort(students, new StudentComparators.GpaThenNameComparator()); System.out.println("Sorted by GPA then name: " + students); } }
3. Comparator Usage with Lambda Expressions:
public class ModernComparatorExample { public static void main(String[] args) { List<Student> students = Arrays.asList( new Student(3, "Alice", 3.8), new Student(1, "Bob", 3.5), new Student(2, "Charlie", 3.9) ); // Lambda expressions for comparators Comparator<Student> byName = (s1, s2) -> s1.getName().compareTo(s2.getName()); Comparator<Student> byGpaDesc = (s1, s2) -> Double.compare(s2.getGpa(), s1.getGpa()); Comparator<Student> byId = (s1, s2) -> Integer.compare(s1.getId(), s2.getId()); // Method references Comparator<Student> byNameMethodRef = Comparator.comparing(Student::getName); Comparator<Student> byGpaMethodRef = Comparator.comparing(Student::getGpa); Comparator<Student> byGpaDescMethodRef = Comparator.comparing(Student::getGpa).reversed(); // Chaining comparators Comparator<Student> byGpaThenName = Comparator.comparing(Student::getGpa) .reversed() .thenComparing(Student::getName); // Null-safe comparators Comparator<Student> nullSafeByName = Comparator.comparing( Student::getName, Comparator.nullsLast(Comparator.naturalOrder()) ); // Usage examples System.out.println("Original: " + students); students.sort(byName); System.out.println("By name (lambda): " + students); students.sort(byGpaDesc); System.out.println("By GPA descending (lambda): " + students); students.sort(byGpaThenName); System.out.println("By GPA then name (chained): " + students); } }
Key Points to Remember:
Use Comparable when:
Use Comparator when:
Best Practices:
compareTo()
consistent with equals()
for ComparableComparator.comparing()
and thenComparing()
for complex sortingTreeSet
and TreeMap
use natural ordering by defaultFor more details on sorting and collections, you can read more about it here.
Answer: Allows interfaces to have default method implementations.
Code Example:
interface MyIntf { default void show() { System.out.println("default"); } }
For more details on interface features and modern Java, you can read more about it here.
Answer: Type information is removed at compile time. Generics are implemented via type erasure.
Code Example:
List<String> list = new ArrayList<>(); // At runtime, it's just List
For more details on generics and Java features, you can read more about it here.
Answer: Introduced in Java 7, it allows type inference for generic instance creation.
Code Example:
List<String> list = new ArrayList<>(); // No need to specify the type in new ArrayList<>()
For more details on Java features and generics, you can read more about it here.
Answer:
For more details on collections and concurrency, you can read more about it here.
Answer: Allows inspection and modification of classes, methods, and fields at runtime.
Code Example:
Class<?> clazz = Class.forName("java.lang.String"); Method[] methods = clazz.getMethods();
For more details on reflection and advanced Java features, you can read more about it here.
Answer: A form of metadata that provides data about a program but is not part of the program itself.
Code Example:
@Override public String toString() { return "test"; }
Answer:
For more details on concurrency and thread safety, you can read more about it here.
Answer: Both composition and aggregation are types of associations between classes, but they differ in the strength and lifecycle of the relationship:
Key Differences:
Aspect | Composition | Aggregation |
---|---|---|
Relationship Type | Strong association | Weak association |
Lifecycle | Child cannot exist without parent | Child can exist independently |
Ownership | Parent owns the child | Parent uses the child |
Destruction | Child is destroyed when parent is destroyed | Child survives parent destruction |
Multiplicity | Usually 1-to-1 or 1-to-many | Can be many-to-many |
Dependency | Child is tightly coupled to parent | Child is loosely coupled to parent |
1. Composition Example - Strong Relationship:
// Composition: Car and Engine - Engine cannot exist without Car public class Engine { private String type; private int horsepower; public Engine(String type, int horsepower) { this.type = type; this.horsepower = horsepower; } public void start() { System.out.println("Engine " + type + " starting with " + horsepower + " HP"); } public String getType() { return type; } public int getHorsepower() { return horsepower; } } public class Car { private String brand; private String model; private Engine engine; // Composition: Car owns the Engine public Car(String brand, String model, String engineType, int horsepower) { this.brand = brand; this.model = model; // Engine is created when Car is created this.engine = new Engine(engineType, horsepower); } public void startCar() { System.out.println("Starting " + brand + " " + model); engine.start(); } public Engine getEngine() { return engine; // Returns the owned engine } // Engine cannot be replaced or removed from Car // Engine is destroyed when Car is destroyed } // Usage public class CompositionExample { public static void main(String[] args) { Car car = new Car("Toyota", "Camry", "V6", 300); car.startCar(); // Engine is part of the Car, cannot exist independently Engine engine = car.getEngine(); System.out.println("Engine type: " + engine.getType()); // When car goes out of scope, engine is also destroyed } }
2. Aggregation Example - Weak Relationship:
// Aggregation: University and Student - Student can exist without University public class Student { private String name; private int studentId; private String major; public Student(String name, int studentId, String major) { this.name = name; this.studentId = studentId; this.major = major; } public void study() { System.out.println(name + " is studying " + major); } public String getName() { return name; } public int getStudentId() { return studentId; } public String getMajor() { return major; } @Override public String toString() { return "Student{name='" + name + "', id=" + studentId + ", major='" + major + "'}"; } } public class University { private String name; private List<Student> students; // Aggregation: University has students public University(String name) { this.name = name; this.students = new ArrayList<>(); } public void enrollStudent(Student student) { students.add(student); System.out.println(student.getName() + " enrolled in " + name); } public void removeStudent(Student student) { students.remove(student); System.out.println(student.getName() + " removed from " + name); } public void listStudents() { System.out.println("Students at " + name + ":"); for (Student student : students) { System.out.println(" - " + student); } } public List<Student> getStudents() { return new ArrayList<>(students); // Return copy to prevent external modification } } // Usage public class AggregationExample { public static void main(String[] args) { // Students can exist independently Student alice = new Student("Alice", 1001, "Computer Science"); Student bob = new Student("Bob", 1002, "Mathematics"); Student charlie = new Student("Charlie", 1003, "Physics"); // University aggregates students University university = new University("Tech University"); // Students can be added to university university.enrollStudent(alice); university.enrollStudent(bob); university.enrollStudent(charlie); university.listStudents(); // Students can be removed from university university.removeStudent(bob); university.listStudents(); // Students still exist independently bob.study(); // Bob can still study even after leaving university // Students can be transferred to another university University anotherUniversity = new University("Another University"); anotherUniversity.enrollStudent(bob); anotherUniversity.listStudents(); } }
Answer: JIT (Just-In-Time) compilation is a technique used by the JVM to improve the performance of Java applications by compiling frequently executed bytecode into native machine code at runtime.
JIT Compilation Process:
JIT Compiler Types:
For more details on JVM memory management and garbage collection, you can read more about it here.
Answer: ClassLoader is a crucial component of the JVM that is responsible for loading Java classes into memory during runtime. It follows a hierarchical delegation model to ensure class loading security and consistency.
Java ClassLoader Hierarchy:
java.lang.*
)Delegation Model: When loading a class, each ClassLoader first asks its parent. If the parent can't find the class, the current loader tries to load it. This prevents core Java classes from being overridden.
For more details on JVM internals and memory management, you can read more about it here.
Answer: Try-with-resources is a feature introduced in Java 7 that automatically manages resources by ensuring they are properly closed after use, even if an exception occurs. It eliminates the need for explicit resource cleanup in finally blocks.
Key Features:
Code Examples:
1. Basic Try-with-Resources:
import java.io.*; public class TryWithResourcesExample { // Traditional approach (verbose) public void readFileTraditional(String filename) { FileInputStream fis = null; try { fis = new FileInputStream(filename); int data = fis.read(); while (data != -1) { System.out.print((char) data); data = fis.read(); } } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { System.err.println("Error closing file: " + e.getMessage()); } } } } // Try-with-resources approach (clean) public void readFileModern(String filename) { try (FileInputStream fis = new FileInputStream(filename)) { int data = fis.read(); while (data != -1) { System.out.print((char) data); data = fis.read(); } } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); } // fis is automatically closed here } }
2. Multiple Resources:
import java.io.*; import java.sql.*; public class MultipleResourcesExample { public void copyFile(String source, String destination) { // Multiple resources in single try-with-resources try (FileInputStream fis = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(destination); BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos)) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = bis.read(buffer)) != -1) { bos.write(buffer, 0, bytesRead); } } catch (IOException e) { System.err.println("Error copying file: " + e.getMessage()); } // All resources automatically closed in reverse order } public void databaseOperation() { try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "user", "pass"); PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users"); ResultSet rs = stmt.executeQuery()) { while (rs.next()) { System.out.println("User: " + rs.getString("name")); } } catch (SQLException e) { System.err.println("Database error: " + e.getMessage()); } // Connection, Statement, and ResultSet all closed automatically } }
3. Custom AutoCloseable Resources:
public class CustomResourceExample { // Custom resource implementing AutoCloseable static class DatabaseConnection implements AutoCloseable { private String url; private boolean isOpen = true; public DatabaseConnection(String url) { this.url = url; System.out.println("Connected to: " + url); } public void executeQuery(String query) { if (!isOpen) { throw new IllegalStateException("Connection is closed"); } System.out.println("Executing: " + query); } @Override public void close() throws Exception { if (isOpen) { isOpen = false; System.out.println("Closing connection to: " + url); } } } // Custom resource implementing Closeable static class FileLogger implements Closeable { private PrintWriter writer; public FileLogger(String filename) throws IOException { this.writer = new PrintWriter(new FileWriter(filename, true)); } public void log(String message) { writer.println(message); writer.flush(); } @Override public void close() throws IOException { if (writer != null) { writer.close(); System.out.println("Logger closed"); } } } public void useCustomResources() { try (DatabaseConnection db = new DatabaseConnection("jdbc:mysql://localhost:3306/test"); FileLogger logger = new FileLogger("app.log")) { db.executeQuery("SELECT * FROM users"); logger.log("Query executed successfully"); } catch (Exception e) { System.err.println("Error: " + e.getMessage()); } // Both resources automatically closed } }
Answer: Records are a feature introduced in Java 14 (preview) and finalized in Java 16 that provide a concise way to create immutable data-carrying classes. They automatically generate constructors, getters, equals(), hashCode(), and toString() methods.
Key Features:
Code Example - Records vs Traditional Classes:
Traditional Class (Lots of Boilerplate):
public class Person { private final String name; private final int age; private final String email; public Person(String name, int age, String email) { this.name = name; this.age = age; this.email = email; } public String getName() { return name; } public int getAge() { return age; } public String getEmail() { return email; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Person person = (Person) obj; return age == person.age && Objects.equals(name, person.name) && Objects.equals(email, person.email); } @Override public int hashCode() { return Objects.hash(name, age, email); } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", email='" + email + '\'' + '}'; } }
Record (Minimal Boilerplate):
public record Person(String name, int age, String email) { }
For more details on records, you can read more about it here.
Answer: Ensures consistent floating-point calculations across platforms.
Code Example:
strictfp class Calc { }
Answer: An interface with exactly one abstract method. Used in lambda expressions.
Code Example:
@FunctionalInterface interface MyFunc { void doWork(); }
Answer: A concise way to represent an anonymous function.
Code Example:
MyFunc f = () -> System.out.println("Work");
For more details on lambda expressions and functional programming in Java, you can read more about it here.
š Blog: https://codewiz.info
š YouTube: https://www.youtube.com/@Code.Wizzard
š LinkedIn: https://www.linkedin.com/in/code-wiz-740370302/
š Medium: https://medium.com/@code.wizzard01
š Github: https://github.com/CodeWizzard01
Get instant AI-powered summaries of YouTube videos and websites. Save time while enhancing your learning experience.