Interactive Java Cheatsheet

An interactive guide to Java concepts, data structures, and design patterns.

0. Java Fundamentals

This section covers the foundational concepts of Java programming, essential for building any application.

Imports

Java uses import statements to bring classes and interfaces from other packages into the current scope.

import java.util.ArrayList; // Imports a specific class
import java.io.*; // Imports all classes from the java.io package
import static java.lang.Math.PI; // Imports a static member

Basic Data Types

Java has eight primitive data types.

  • byte: 8-bit integer.
  • short: 16-bit integer.
  • int: 32-bit integer, default for whole numbers.
  • long: 64-bit integer.
  • float: 32-bit floating-point (single precision), e.g., 3.14f.
  • double: 64-bit floating-point (double precision), default for decimal numbers, e.g., 3.14159.
  • char: 16-bit Unicode character, e.g., 'A'.
  • boolean: true or false.

Reference types include String, arrays, and custom classes.

Variables

Variables are named memory locations to store data.

int age = 30; // Declare an int variable 'age'
double price = 19.99; // Declare a double variable 'price'
String name = "Alice"; // Declare a String variable 'name'
boolean isActive = true; // Declare a boolean variable 'isActive'
final double PI = 3.14159; // Declare a constant 'PI' (value cannot be changed)

Input/Output

Interacting with the user via the console using System.out and Scanner.

import java.util.Scanner;

public class IOExample {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.println("Hello, World!"); // Print to console
        System.out.print("Enter your age: ");
        int age = scanner.nextInt(); // Read an integer

        scanner.nextLine(); // Consume newline left-over

        System.out.print("Enter your full name: ");
        String fullName = scanner.nextLine(); // Read a full line

        System.out.println("Your age is: " + age);
        System.out.println("Your name is: " + fullName);

        scanner.close();
    }
}

Operators

Symbols that perform operations on variables and values.

  • **Arithmetic**: +, -, *, /, % (modulo/remainder)
  • **Comparison**: == (equal to), != (not equal to), < (less than), > (greater than), <= (less than or equal to), >= (greater than or equal to)
  • **Logical**: && (AND), || (OR), ! (NOT)
  • **Assignment**: = (assign value), += (add and assign), -= (subtract and assign), etc.
  • **Increment/Decrement**: ++ (increase by 1), -- (decrease by 1)

Control Flow

Statements that control the order in which instructions are executed.

If-Else

int age = 15;
if (age >= 18) {
    System.out.println("You are an adult.");
} else if (age >= 13) {
    System.out.println("You are a teenager.");
} else {
    System.out.println("You are a child.");
}

Switch

int day = 3;
switch (day) {
    case 1:
        System.out.println("Monday");
        break;
    case 2:
        System.out.println("Tuesday");
        break;
    default:
        System.out.println("Other day");
}

For Loop

for (int i = 0; i < 5; i++) {
    System.out.println("Iteration: " + i);
}

Enhanced For Loop (For-Each)

int[] numbers = {10, 20, 30};
for (int num : numbers) {
    System.out.print(num + " ");
}
System.out.println();

While Loop

int count = 0;
while (count < 3) {
    System.out.println("Count: " + count);
    count++;
}

Do-While Loop

int j = 0;
do {
    System.out.println("J: " + j);
    j++;
} while (j < 0); // Condition checked after first execution

Methods

Reusable blocks of code that perform a specific task.

public class MyMath {
    // Method that takes two ints and returns an int
    public int add(int a, int b) {
        return a + b;
    }

    // Method that takes a String and returns nothing (void)
    public void greet(String name) {
        System.out.println("Hello, " + name + "!");
    }

    public static void main(String[] args) {
        MyMath calculator = new MyMath();
        int sum = calculator.add(5, 7);
        System.out.println("Sum: " + sum);
        calculator.greet("Bob");
    }
}

Arrays

Fixed-size collections of elements of the same type.

int[] myArray = {1, 2, 3, 4, 5}; // Declare and initialize an array
System.out.println("First element: " + myArray[0]); // Access using 0-based index
myArray[2] = 99; // Change element value

Strings

Sequences of characters. Immutable in Java.

String greeting = "Hello";
String name = "Java";
String message = greeting + ", " + name + "!"; // Concatenation
System.out.println(message); // Output: Hello, Java!
System.out.println(message.length()); // Length
System.out.println(message.toUpperCase()); // To uppercase

Basic OOP (Object-Oriented Programming)

Classes are blueprints, objects are instances. Encapsulation, Inheritance, Polymorphism.

Classes & Objects

public class Dog {
    // Instance variables (attributes)
    String name;
    int age;

    // Constructor
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Method (behavior)
    public void bark() {
        System.out.println(name + " says Woof!");
    }

    public static void main(String[] args) {
        Dog myDog = new Dog("Buddy", 3); // Create an object
        myDog.bark(); // Call a method
        System.out.println("Dog's name: " + myDog.name);
    }
}

Inheritance

class Animal {
    void eat() { System.out.println("Animal eats"); }
}

class Cat extends Animal { // Cat inherits from Animal
    void meow() { System.out.println("Cat meows"); }
}

public class InheritanceExample {
    public static void main(String[] args) {
        Cat myCat = new Cat();
        myCat.eat(); // Inherited method
        myCat.meow(); // Cat's own method
    }
}

Polymorphism (Method Overriding)

class Vehicle {
    void run() { System.out.println("Vehicle is running"); }
}

class Car extends Vehicle {
    @Override // Annotation indicating method overrides superclass method
    void run() { System.out.println("Car is running safely"); }
}

public class PolymorphismExample {
    public static void main(String[] args) {
        Vehicle v = new Car(); // Polymorphic reference
        v.run(); // Calls Car's run() method
    }
}

Interfaces

A blueprint of a class. It has static constants and abstract methods.

interface Drawable {
    void draw(); // Abstract method
}

class Circle implements Drawable {
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

public class InterfaceExample {
    public static void main(String[] args) {
        Drawable d = new Circle();
        d.draw();
    }
}

1. Generics

Generics enable you to write a single method or class declaration that can be used with a set of different types.

public class Box<T> { // Type parameter T
    private T content;

    public void setContent(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }

    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<>();
        integerBox.setContent(10);
        System.out.println("Integer content: " + integerBox.getContent());

        Box<String> stringBox = new Box<>();
        stringBox.setContent("Hello Generics");
        System.out.println("String content: " + stringBox.getContent());
    }
}

2. Collections Framework

The Java Collections Framework provides a unified architecture for representing and manipulating collections, allowing them to be manipulated independently of their implementation details.

ArrayList (Dynamic Array)

  • Description: A resizable array implementation of the List interface. Elements are stored contiguously (conceptually).
  • Performance: Access (by index): $O(1)$, Add (end): $O(1)$ amortized, Insert/Delete (middle/beginning): $O(N)$.
  • Thread Safety: Not thread-safe.
import java.util.ArrayList;
import java.util.Collections; // For sorting

ArrayList<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
System.out.println(names.get(0)); // Access
Collections.sort(names); // Sort
System.out.println(names);

LinkedList (Doubly-Linked List)

  • Description: A doubly-linked list implementation of the List and Deque interfaces.
  • Performance: Access (by index): $O(N)$, Add/Remove (anywhere, with iterator): $O(1)$, Add/Remove (by value): $O(N)$.
  • Thread Safety: Not thread-safe.
import java.util.LinkedList;

LinkedList<Integer> numbers = new LinkedList<>();
numbers.add(10);
numbers.addFirst(5); // Add to front
numbers.addLast(20); // Add to end
System.out.println(numbers.getFirst());
numbers.remove(1); // Remove by index

HashSet (Unordered Set)

  • Description: An implementation of the Set interface that stores unique elements. Uses a hash table for storage. No guaranteed order.
  • Performance: Add/Remove/Contains: $O(1)$ on average, $O(N)$ worst case (hash collisions).
  • Thread Safety: Not thread-safe.
import java.util.HashSet;

HashSet<String> uniqueWords = new HashSet<>();
uniqueWords.add("apple");
uniqueWords.add("banana");
uniqueWords.add("apple"); // Duplicate, ignored
System.out.println(uniqueWords.contains("banana"));
System.out.println(uniqueWords);

TreeSet (Sorted Set)

  • Description: An implementation of the Set interface that stores unique elements in sorted order. Uses a Red-Black Tree.
  • Performance: Add/Remove/Contains: $O(\log N)$.
  • Thread Safety: Not thread-safe.
import java.util.TreeSet;

TreeSet<Integer> sortedNumbers = new TreeSet<>();
sortedNumbers.add(30);
sortedNumbers.add(10);
sortedNumbers.add(20);
System.out.println(sortedNumbers); // Output: [10, 20, 30]

HashMap (Unordered Map)

  • Description: An implementation of the Map interface that stores key-value pairs. Uses a hash table. No guaranteed order.
  • Performance: Put/Get/Remove: $O(1)$ on average, $O(N)$ worst case (hash collisions).
  • Thread Safety: Not thread-safe.
import java.util.HashMap;

HashMap<String, Integer> scores = new HashMap<>();
scores.put("John", 95);
scores.put("Jane", 88);
System.out.println("John's score: " + scores.get("John"));
scores.put("Jane", 90); // Update value
System.out.println(scores);

TreeMap (Sorted Map)

  • Description: An implementation of the Map interface that stores key-value pairs in sorted order based on keys. Uses a Red-Black Tree.
  • Performance: Put/Get/Remove: $O(\log N)$.
  • Thread Safety: Not thread-safe.
import java.util.TreeMap;

TreeMap<String, Integer> sortedScores = new TreeMap<>();
sortedScores.put("John", 95);
sortedScores.put("Alice", 88);
System.out.println(sortedScores); // Output: {Alice=88, John=95}

ArrayDeque (Double-Ended Queue)

  • Description: A resizable-array implementation of the Deque interface. Can be used as a stack or a queue.
  • Performance: Add/Remove (front/back): $O(1)$ amortized.
  • Thread Safety: Not thread-safe.
import java.util.ArrayDeque;

ArrayDeque<String> tasks = new ArrayDeque<>();
tasks.addLast("Task 1"); // Add to end (queue behavior)
tasks.addFirst("Urgent Task"); // Add to front (stack behavior)
System.out.println(tasks.peekFirst()); // Get first without removing
tasks.removeFirst(); // Remove from front

PriorityQueue (Min-Heap by default)

  • Description: An unbounded priority queue based on a priority heap. Elements are ordered according to their natural ordering or by a Comparator. Retrieves the smallest element first by default.
  • Performance: Add/Remove/Poll: $O(\log N)$, Peek: $O(1)$.
  • Thread Safety: Not thread-safe.
import java.util.PriorityQueue;

PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.add(30);
pq.add(10);
pq.add(50);
System.out.println(pq.peek()); // Output: 10 (smallest)
pq.poll(); // Remove 10
System.out.println(pq.peek()); // Output: 30

Important Note on Thread Safety for Java Collections:

Most standard Java Collection classes (e.g., ArrayList, HashMap, HashSet) are not thread-safe for concurrent modifications. If multiple threads access and at least one modifies the collection, you must use external synchronization (e.g., synchronized blocks, java.util.concurrent package classes) or use synchronized wrappers (e.g., Collections.synchronizedList()) to prevent race conditions and undefined behavior. Concurrent read-only access by multiple threads is generally safe.

Comparison of Java Collections

Collection Type Description Access (Index) Insertion (Avg) Deletion (Avg) Search (Avg) Ordered Unique Thread Safety (Concurrent Mod.)
ArrayListDynamic array$O(1)$$O(1)$ amortized (end), $O(N)$ (middle)$O(N)$$O(N)$Yes (insertion order)NoNo (requires external sync)
LinkedListDoubly-linked list$O(N)$$O(1)$$O(1)$$O(N)$Yes (insertion order)NoNo (requires external sync)
HashSetUnordered set (Hash Table)N/A$O(1)$ (avg), $O(N)$ (worst)$O(1)$ (avg), $O(N)$ (worst)$O(1)$ (avg), $O(N)$ (worst)NoYesNo (requires external sync)
TreeSetSorted set (Red-Black Tree)N/A$O(\log N)$$O(\log N)$$O(\log N)$Yes (natural/comparator order)YesNo (requires external sync)
HashMapUnordered key-value pairs (Hash Table)$O(1)$ (key access)$O(1)$ (avg), $O(N)$ (worst)$O(1)$ (avg), $O(N)$ (worst)$O(1)$ (avg), $O(N)$ (worst)NoKeys: YesNo (requires external sync)
TreeMapSorted key-value pairs (Red-Black Tree)$O(\log N)$ (key access)$O(\log N)$$O(\log N)$$O(\log N)$Yes (key order)Keys: YesNo (requires external sync)
ArrayDequeDouble-ended queue (array-based)$O(1)$ (ends)$O(1)$ amortized (ends)$O(1)$ amortized (ends)$O(N)$Yes (insertion order)NoNo (requires external sync)
PriorityQueueMin-heapN/A (peek $O(1)$)$O(\log N)$$O(\log N)$N/ANo (priority order)NoNo (requires external sync)

3. Lambda Expressions & Stream API

Java 8 introduced Lambda Expressions for concise code and the Stream API for functional-style operations on collections.

Lambda Expressions

A short block of code which takes in parameters and returns a value. They are similar to methods, but they do not need a name and can be implemented right in the body of a method.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class LambdaExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(5);
        numbers.add(1);
        numbers.add(8);

        // Sorting with a lambda expression
        Collections.sort(numbers, (a, b) -> b.compareTo(a));
        System.out.println("Sorted (desc): " + numbers); // Output: [8, 5, 1]

        // Iterating with a lambda expression
        numbers.forEach(n -> System.out.println("Number: " + n));
    }
}

Stream API

Provides a powerful and flexible way to process collections of objects. It supports functional-style operations on streams of elements.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamAPIExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // Filter and map using streams
        List<String> filteredNames = names.stream()
            .filter(name -> name.startsWith("A"))
            .map(String::toUpperCase)
            .collect(Collectors.toList());

        System.out.println("Filtered and Mapped: " + filteredNames); // Output: [ALICE]

        // Count elements
        long count = names.stream().filter(name -> name.length() > 3).count();
        System.out.println("Names longer than 3 chars: " + count);
    }
}

4. Exception Handling

Java uses a try-catch-finally block to handle runtime errors (exceptions).

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class ExceptionHandlingExample {
    public static void main(String[] args) {
        try {
            // Code that might throw an exception
            int result = 10 / 0; // ArithmeticException
            System.out.println(result);
        } catch (ArithmeticException e) {
            // Catch specific exception
            System.err.println("Caught ArithmeticException: " + e.getMessage());
        } catch (Exception e) {
            // Catch any other exception (general catch-all)
            System.err.println("Caught a general Exception: " + e.getMessage());
        } finally {
            // Code that always executes, regardless of exception
            System.out.println("Finally block executed.");
        }

        // Checked exception example (IOException)
        FileReader reader = null;
        try {
            reader = new FileReader(new File("nonexistent.txt"));
        } catch (IOException e) {
            System.err.println("Caught IOException: " + e.getMessage());
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    System.err.println("Error closing reader: " + e.getMessage());
                }
            }
        }
    }
}

5. Concurrency (Threads, Synchronized, Concurrency Utilities)

Java provides robust features for multi-threaded programming.

Creating Threads

  • Extend Thread class.
  • Implement Runnable interface (preferred).
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable thread running: " + Thread.currentThread().getName());
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread class thread running: " + Thread.currentThread().getName());
    }
}

public class ThreadCreationExample {
    public static void main(String[] args) {
        // Using Runnable
        Thread thread1 = new Thread(new MyRunnable(), "RunnableThread");
        thread1.start();

        // Using Thread class
        MyThread thread2 = new MyThread();
        thread2.setName("MyThreadClass");
        thread2.start();
    }
}

Synchronization (synchronized keyword)

Used to control access to shared resources by multiple threads, preventing race conditions.

class Counter {
    int count = 0;

    // Synchronized method
    public synchronized void increment() {
        count++;
    }

    // Synchronized block
    public void decrement() {
        synchronized (this) {
            count--;
        }
    }
}

public class SynchronizationExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);

        t1.start();
        t2.start();

        t1.join(); // Wait for t1 to finish
        t2.join(); // Wait for t2 to finish

        System.out.println("Final count: " + counter.count); // Should be 2000
    }
}

Concurrency Utilities (java.util.concurrent)

Provides higher-level concurrency constructs like Thread Pools, Futures, and Locks.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;

public class ConcurrencyUtilExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(2); // Thread pool

        // Callable returns a result
        Callable<Integer> task = () -> {
            Thread.sleep(1000); // Simulate work
            return 123;
        };

        Future<Integer> future = executor.submit(task); // Submit task
        System.out.println("Future result: " + future.get()); // Get result (blocks)

        executor.shutdown(); // Shut down the executor
    }
}

6. Memory Management (JVM, Garbage Collection)

Java uses automatic memory management (Garbage Collection) via the JVM, abstracting away manual memory allocation/deallocation.

  • **Heap**: Where objects are allocated.
  • **Stack**: Stores method calls, local variables, and primitive data.
  • **Garbage Collector (GC)**: Automatically reclaims memory occupied by objects that are no longer referenced by the program.
  • **No explicit delete**: Unlike C++, Java developers don't manually free memory.

Object Lifecycle & GC

Objects are created with new. When no references point to an object, it becomes eligible for garbage collection.

public class GCMemoryExample {
    public static void main(String[] args) {
        // Object 'obj1' is created
        Object obj1 = new Object();

        // 'obj1' is referenced by 'obj2'
        Object obj2 = obj1;

        // 'obj1' reference is set to null, but object is still reachable via 'obj2'
        obj1 = null;

        // Now, 'obj2' is set to null. The original Object is now eligible for GC.
        obj2 = null;

        // Requesting GC (not guaranteed to run immediately)
        System.gc();
        System.out.println("Objects potentially garbage collected.");
    }
}

Weak References

Allow the garbage collector to collect an object even if there are references to it.

import java.lang.ref.WeakReference;

public class WeakReferenceExample {
    public static void main(String[] args) {
        MyLargeObject largeObject = new MyLargeObject("Data");
        WeakReference<MyLargeObject> weakRef = new WeakReference<>(largeObject);

        System.out.println("Before GC: " + weakRef.get()); // Should print the object

        largeObject = null; // Remove the strong reference

        System.gc(); // Hint to GC to run

        // After GC, weakRef.get() might return null if the object was collected
        System.out.println("After GC: " + weakRef.get()); // Might be null
    }

    static class MyLargeObject {
        String data;
        MyLargeObject(String data) { this.data = data; }
        @Override public String toString() { return "MyLargeObject(" + data + ")"; }
        @Override protected void finalize() throws Throwable {
            System.out.println("MyLargeObject finalized!"); // Called by GC
        }
    }
}

7. Annotations & Reflection

Advanced features for adding metadata to code and inspecting/modifying code at runtime.

Annotations

Provide metadata about the program but do not directly affect program execution. Used by compilers, tools, and runtime libraries.

import java.lang.annotation.*;

// Define a custom annotation
@Retention(RetentionPolicy.RUNTIME) // Available at runtime via reflection
@Target(ElementType.METHOD) // Can be applied to methods
@interface MyAnnotation {
    String value() default "default";
    int count() default 1;
}

public class AnnotationExample {
    @MyAnnotation(value = "hello", count = 5)
    public void annotatedMethod() {
        System.out.println("This method is annotated.");
    }

    @MyAnnotation // Using default values
    public void anotherMethod() {
        System.out.println("Another annotated method.");
    }

    public static void main(String[] args) {
        AnnotationExample obj = new AnnotationExample();
        obj.annotatedMethod();
        obj.anotherMethod();
    }
}

Reflection

The ability of a program to examine or modify its own structure and behavior at runtime.

import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.annotation.Annotation;

public class ReflectionExample {
    private String name = "Reflected Name";
    public int value = 100;

    public void publicMethod() {
        System.out.println("Public method called.");
    }

    private void privateMethod() {
        System.out.println("Private method called.");
    }

    public static void main(String[] args) throws Exception {
        Class<?> clazz = ReflectionExample.class;

        // Get and invoke a public method
        Method publicM = clazz.getMethod("publicMethod");
        publicM.invoke(clazz.newInstance());

        // Get and invoke a private method (requires setting accessible)
        Method privateM = clazz.getDeclaredMethod("privateMethod");
        privateM.setAccessible(true); // Bypass access checks
        privateM.invoke(clazz.newInstance());

        // Get and set a private field
        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true);
        ReflectionExample obj = new ReflectionExample();
        System.out.println("Original name: " + nameField.get(obj));
        nameField.set(obj, "New Name");
        System.out.println("New name: " + nameField.get(obj));

        // Access annotations (requires @Retention(RetentionPolicy.RUNTIME))
        Method annotatedMethod = AnnotationExample.class.getMethod("annotatedMethod");
        if (annotatedMethod.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation annotation = annotatedMethod.getAnnotation(MyAnnotation.class);
            System.out.println("Annotation value: " + annotation.value() + ", count: " + annotation.count());
        }
    }
}

8. Design Patterns

Common solutions to recurring problems in software design, adapted for Java.

Singleton

Ensures a class has only one instance and provides a global point of access to it.

public class Singleton {
    private static Singleton instance;

    // Private constructor to prevent instantiation from outside
    private Singleton() { }

    public static Singleton getInstance() {
        if (instance == null) {
            // Thread-safe for multi-threaded environments (Double-checked locking)
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    public void showMessage() {
        System.out.println("Hello from Singleton!");
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        s1.showMessage();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2); // Output: true (same instance)
    }
}

Factory Method

Provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

// Product Interface
interface Product {
    String getName();
}

// Concrete Products
class ConcreteProductA implements Product {
    @Override
    public String getName() { return "Product A"; }
}

class ConcreteProductB implements Product {
    @Override
    public String getName() { return "Product B"; }
}

// Creator (Factory)
abstract class Creator {
    public abstract Product createProduct();

    public String someOperation() {
        Product product = createProduct();
        return "Creator: The product is " + product.getName();
    }
}

// Concrete Creators
class ConcreteCreatorA extends Creator {
    @Override
    public Product createProduct() { return new ConcreteProductA(); }
}

class ConcreteCreatorB extends Creator {
    @Override
    public Product createProduct() { return new ConcreteProductB(); }
}

public class FactoryMethodExample {
    public static void main(String[] args) {
        Creator creatorA = new ConcreteCreatorA();
        System.out.println(creatorA.someOperation());
        Creator creatorB = new ConcreteCreatorB();
        System.out.println(creatorB.someOperation());
    }
}

Observer

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

import java.util.ArrayList;
import java.util.List;

// Observer Interface
interface Observer {
    void update(String message);
}

// Subject (Observable)
class Subject {
    private List<Observer> observers = new ArrayList<>();

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void detach(Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

// Concrete Observer
class ConcreteObserver implements Observer {
    private String name;

    public ConcreteObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println(name + " received update: " + message);
    }
}

public class ObserverExample {
    public static void main(String[] args) {
        Subject subject = new Subject();
        ConcreteObserver obs1 = new ConcreteObserver("Observer 1");
        ConcreteObserver obs2 = new ConcreteObserver("Observer 2");

        subject.attach(obs1);
        subject.attach(obs2);

        subject.notifyObservers("A new event occurred!");
        subject.detach(obs1);
        subject.notifyObservers("Another event!");
    }
}

Strategy

Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

// Strategy Interface
interface PaymentStrategy {
    void pay(int amount);
}

// Concrete Strategies
class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("Paying " + amount + " using Credit Card.");
    }
}

class PayPalPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("Paying " + amount + " using PayPal.");
    }
}

// Context
class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void checkout(int amount) {
        if (paymentStrategy != null) {
            paymentStrategy.pay(amount);
        } else {
            System.out.println("No payment strategy set.");
        }
    }

    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        cart.setPaymentStrategy(new CreditCardPayment());
        cart.checkout(100);

        cart.setPaymentStrategy(new PayPalPayment());
        cart.checkout(50);
    }
}

Decorator

Attaches additional responsibilities to an object dynamically.

// Component Interface
interface Coffee {
    double getCost();
    String getIngredients();
}

// Concrete Component
class SimpleCoffee implements Coffee {
    @Override
    public double getCost() { return 5.0; }
    @Override
    public String getIngredients() { return "Coffee"; }
}

// Decorator Base Class
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    @Override
    public double getCost() { return decoratedCoffee.getCost(); }
    @Override
    public String getIngredients() { return decoratedCoffee.getIngredients(); }
}

// Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) { super(coffee); }
    @Override
    public double getCost() { return super.getCost() + 1.5; }
    @Override
    public String getIngredients() { return super.getIngredients() + ", Milk"; }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) { super(coffee); }
    @Override
    public double getCost() { return super.getCost() + 0.5; }
    @Override
    public String getIngredients() { return super.getIngredients() + ", Sugar"; }
}

public class DecoratorExample {
    public static void main(String[] args) {
        Coffee myCoffee = new SimpleCoffee();
        System.out.println("Cost: " + myCoffee.getCost() + ", Ingredients: " + myCoffee.getIngredients());

        myCoffee = new MilkDecorator(myCoffee); // Add milk
        System.out.println("Cost: " + myCoffee.getCost() + ", Ingredients: " + myCoffee.getIngredients());

        myCoffee = new SugarDecorator(myCoffee); // Add sugar
        System.out.println("Cost: " + myCoffee.getCost() + ", Ingredients: " + myCoffee.getIngredients());
    }
}

Visitor

Represents an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

import java.util.ArrayList;
import java.util.List;

// Visitor Interface
interface Visitor {
    void visit(ConcreteElementA element);
    void visit(ConcreteElementB element);
}

// Element Interface
interface Element {
    void accept(Visitor visitor);
}

// Concrete Elements
class ConcreteElementA implements Element {
    public String operationA() { return "ConcreteElementA"; }
    @Override
    public void accept(Visitor visitor) { visitor.visit(this); }
}

class ConcreteElementB implements Element {
    public String operationB() { return "ConcreteElementB"; }
    @Override
    public void accept(Visitor visitor) { visitor.visit(this); }
}

// Concrete Visitors
class ConcreteVisitor1 implements Visitor {
    @Override
    public void visit(ConcreteElementA element) {
        System.out.println("Visitor 1 processing " + element.operationA());
    }
    @Override
    public void visit(ConcreteElementB element) {
        System.out.println("Visitor 1 processing " + element.operationB());
    }
}

class ConcreteVisitor2 implements Visitor {
    @Override
    public void visit(ConcreteElementA element) {
        System.out.println("Visitor 2 processing " + element.operationA() + " differently.");
    }
    @Override
    public void visit(ConcreteElementB element) {
        System.out.println("Visitor 2 processing " + element.operationB() + " differently.");
    }
}

public class VisitorExample {
    public static void main(String[] args) {
        List<Element> elements = new ArrayList<>();
        elements.add(new ConcreteElementA());
        elements.add(new ConcreteElementB());

        ConcreteVisitor1 visitor1 = new ConcreteVisitor1();
        for (Element elem : elements) {
            elem.accept(visitor1);
        }

        ConcreteVisitor2 visitor2 = new ConcreteVisitor2();
        for (Element elem : elements) {
            elem.accept(visitor2);
        }
    }
}