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:trueorfalse.
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
Listinterface. 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
ListandDequeinterfaces. - 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
Setinterface 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
Setinterface 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
Mapinterface 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
Mapinterface 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
Dequeinterface. 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.) |
|---|---|---|---|---|---|---|---|---|
ArrayList | Dynamic array | $O(1)$ | $O(1)$ amortized (end), $O(N)$ (middle) | $O(N)$ | $O(N)$ | Yes (insertion order) | No | No (requires external sync) |
LinkedList | Doubly-linked list | $O(N)$ | $O(1)$ | $O(1)$ | $O(N)$ | Yes (insertion order) | No | No (requires external sync) |
HashSet | Unordered set (Hash Table) | N/A | $O(1)$ (avg), $O(N)$ (worst) | $O(1)$ (avg), $O(N)$ (worst) | $O(1)$ (avg), $O(N)$ (worst) | No | Yes | No (requires external sync) |
TreeSet | Sorted set (Red-Black Tree) | N/A | $O(\log N)$ | $O(\log N)$ | $O(\log N)$ | Yes (natural/comparator order) | Yes | No (requires external sync) |
HashMap | Unordered 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) | No | Keys: Yes | No (requires external sync) |
TreeMap | Sorted key-value pairs (Red-Black Tree) | $O(\log N)$ (key access) | $O(\log N)$ | $O(\log N)$ | $O(\log N)$ | Yes (key order) | Keys: Yes | No (requires external sync) |
ArrayDeque | Double-ended queue (array-based) | $O(1)$ (ends) | $O(1)$ amortized (ends) | $O(1)$ amortized (ends) | $O(N)$ | Yes (insertion order) | No | No (requires external sync) |
PriorityQueue | Min-heap | N/A (peek $O(1)$) | $O(\log N)$ | $O(\log N)$ | N/A | No (priority order) | No | No (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
Threadclass. - Implement
Runnableinterface (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);
}
}
}