Understand composition as an alternative to inheritance and learn how to design flexible and maintainable object relationships.
Here are some examples demonstrating key composition patterns:
import java.util.ArrayList; import java.util.Collection; // This class implements a read-only list that only exposes // a subset of ArrayList functionality public class ReadOnlyList<E> { // Composition: has-a relationship with ArrayList private final ArrayList<E> list; public ReadOnlyList(Collection<E> items) { this.list = new ArrayList<>(items); } // We only expose get and size methods public E get(int index) { return list.get(index); } public int size() { return list.size(); } public boolean contains(E element) { return list.contains(element); } // Notice we don't expose add, remove, or other mutating methods public static void main(String[] args) { ArrayList<String> source = new ArrayList<>(); source.add("Apple"); source.add("Banana"); source.add("Cherry"); ReadOnlyList<String> readOnly = new ReadOnlyList<>(source); System.out.println("Element at index 1: " + readOnly.get(1)); System.out.println("Size: " + readOnly.size()); System.out.println("Contains Banana? " + readOnly.contains("Banana")); // This would cause a compilation error: // readOnly.add("Date"); } }
import java.util.ArrayList; import java.util.List; // This class extends ArrayList functionality with additional features public class EnhancedList<E> { // Composition: has-a relationship with ArrayList private final List<E> list; public EnhancedList() { this.list = new ArrayList<>(); } // Original ArrayList functionality public boolean add(E element) { return list.add(element); } public E get(int index) { return list.get(index); } public int size() { return list.size(); } // Enhanced functionality: get first element public E getFirst() { if (list.isEmpty()) { throw new IllegalStateException("List is empty"); } return list.get(0); } // Enhanced functionality: get last element public E getLast() { if (list.isEmpty()) { throw new IllegalStateException("List is empty"); } return list.get(list.size() - 1); } // Enhanced functionality: add multiple elements at once public boolean addAll(E... elements) { boolean changed = false; for (E element : elements) { changed |= list.add(element); } return changed; } public static void main(String[] args) { EnhancedList<String> enhanced = new EnhancedList<>(); // Using original ArrayList functionality enhanced.add("Apple"); enhanced.add("Banana"); // Using enhanced functionality enhanced.addAll("Cherry", "Date", "Elderberry"); System.out.println("First element: " + enhanced.getFirst()); System.out.println("Last element: " + enhanced.getLast()); System.out.println("Total size: " + enhanced.size()); } }
import java.util.HashMap; import java.util.Map; // An example of using composition to create a cache system public class Cache<K, V> { // Composition: uses a HashMap internally private final Map<K, CacheEntry<V>> cacheMap; private final long expiryTimeMs; public Cache(long expiryTimeMs) { this.cacheMap = new HashMap<>(); this.expiryTimeMs = expiryTimeMs; } public void put(K key, V value) { cacheMap.put(key, new CacheEntry<>(value, System.currentTimeMillis())); } public V get(K key) { CacheEntry<V> entry = cacheMap.get(key); if (entry == null) { return null; } // Check if entry has expired if (System.currentTimeMillis() - entry.timestamp > expiryTimeMs) { cacheMap.remove(key); return null; } return entry.value; } public int size() { // Clean expired entries before returning size cacheMap.entrySet().removeIf(entry -> System.currentTimeMillis() - entry.getValue().timestamp > expiryTimeMs); return cacheMap.size(); } // Helper class for storing cache entries with timestamps private static class CacheEntry<V> { private final V value; private final long timestamp; public CacheEntry(V value, long timestamp) { this.value = value; this.timestamp = timestamp; } } public static void main(String[] args) throws InterruptedException { Cache<String, String> cache = new Cache<>(2000); // 2 second expiry cache.put("key1", "value1"); System.out.println("After adding key1: " + cache.get("key1")); // Sleep for 1 second (not expired yet) Thread.sleep(1000); System.out.println("After 1 second: " + cache.get("key1")); // Sleep for 1.5 more seconds (should be expired now) Thread.sleep(1500); System.out.println("After 2.5 seconds: " + cache.get("key1")); } }