Generics
Generics
Type-Safe Containers
Your Box class stores an Object. When you get it out, you cast to String - but
what if someone put an Integer in? Generics let you say Box<String> - the
compiler ensures only Strings go in, no casting needed.
Generic class
Define a class with a type parameter.
public class GenericClass {
static class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
public boolean hasValue() {
return value != null;
}
}
public static void main(String[] args) {
System.out.println("Generic class:\n");
// Box for Integer
Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(" Integer box: " + intBox.get());
// Box for String
Box<String> strBox = new Box<>();
strBox.set("Hello");
System.out.println(" String box: " + strBox.get());
// Box for Double
Box<Double> doubleBox = new Box<>();
doubleBox.set(3.14);
System.out.println(" Double box: " + doubleBox.get());
// T is a type parameter (placeholder for actual type)
// Specified at instantiation: Box<Integer>
// Provides compile-time type safety
// No casting needed when retrieving value
System.out.println("\nType safety:");
Box<String> box = new Box<>();
box.set("Text");
// box.set(123); // Compile error - type mismatch
String value = box.get(); // No cast needed
System.out.println(" Value: " + value);
System.out.println("\nWithout generics (old way):");
class ObjectBox {
private Object value;
public void set(Object value) {
this.value = value;
}
public Object get() {
return value;
}
}
ObjectBox oldBox = new ObjectBox();
oldBox.set("Text");
String str = (String) oldBox.get(); // Cast required
System.out.println(" Value: " + str);
// Runtime error possible:
oldBox.set(123);
// String wrong = (String) oldBox.get(); // ClassCastException at runtime
System.out.println("\nMultiple type uses:");
Box<Integer> scores = new Box<>();
scores.set(95);
Box<String> name = new Box<>();
name.set("Alice");
Box<Boolean> active = new Box<>();
active.set(true);
System.out.println(" Score: " + scores.get());
System.out.println(" Name: " + name.get());
System.out.println(" Active: " + active.get());
}
}
class Box<T> - T is a placeholder for any type. Specified when used.
Generic with methods
Methods using the type parameter.
public class GenericWithMethods {
static class Container<T> {
private T item;
public void store(T item) {
this.item = item;
System.out.println(" Stored: " + item);
}
public T retrieve() {
System.out.println(" Retrieved: " + item);
return item;
}
public boolean isEmpty() {
return item == null;
}
public void clear() {
item = null;
System.out.println(" Cleared");
}
public String getInfo() {
if (item == null) {
return "Empty container";
}
return "Container holding: " + item.getClass().getSimpleName();
}
}
public static void main(String[] args) {
System.out.println("Generic methods:\n");
Container<String> strContainer = new Container<>();
strContainer.store("Hello");
String value = strContainer.retrieve();
System.out.println(" Info: " + strContainer.getInfo());
// Methods in generic class can use type parameter T
// Parameters and return types can be T
// Type is consistent throughout the instance
System.out.println("\nInteger container:");
Container<Integer> intContainer = new Container<>();
System.out.println(" Empty: " + intContainer.isEmpty());
intContainer.store(42);
System.out.println(" Empty: " + intContainer.isEmpty());
int num = intContainer.retrieve();
System.out.println(" Value: " + num);
System.out.println("\nPair class:");
class Pair<T> {
private T first;
private T second;
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void swap() {
T temp = first;
first = second;
second = temp;
}
public void display() {
System.out.println(" First: " + first + ", Second: " + second);
}
}
Pair<String> namePair = new Pair<>("Alice", "Bob");
namePair.display();
namePair.swap();
System.out.println(" After swap:");
namePair.display();
System.out.println("\nPoint class:");
class Point<T extends Number> {
private T x;
private T y;
public Point(T x, T y) {
this.x = x;
this.y = y;
}
public T getX() { return x; }
public T getY() { return y; }
public double distance() {
double dx = x.doubleValue();
double dy = y.doubleValue();
return Math.sqrt(dx * dx + dy * dy);
}
public void display() {
System.out.println(" Point(" + x + ", " + y + ")");
}
}
int pointX = ;
Point<Integer> intPoint = new Point<>(pointX, 4);
intPoint.display();
System.out.println(" Distance: " + intPoint.distance());
Point<Double> doublePoint = new Point<>(1.5, 2.5);
doublePoint.display();
System.out.println(" Distance: " + doublePoint.distance());
}
}
public class GenericWithMethods {
static class Container<T> {
private T item;
public void store(T item) {
this.item = item;
System.out.println(" Stored: " + item);
}
public T retrieve() {
System.out.println(" Retrieved: " + item);
return item;
}
public boolean isEmpty() {
return item == null;
}
public void clear() {
item = null;
System.out.println(" Cleared");
}
public String getInfo() {
if (item == null) {
return "Empty container";
}
return "Container holding: " + item.getClass().getSimpleName();
}
}
public static void main(String[] args) {
System.out.println("Generic methods:\n");
Container<String> strContainer = new Container<>();
strContainer.store("Hello");
String value = strContainer.retrieve();
System.out.println(" Info: " + strContainer.getInfo());
// Methods in generic class can use type parameter T
// Parameters and return types can be T
// Type is consistent throughout the instance
System.out.println("\nInteger container:");
Container<Integer> intContainer = new Container<>();
System.out.println(" Empty: " + intContainer.isEmpty());
intContainer.store(42);
System.out.println(" Empty: " + intContainer.isEmpty());
int num = intContainer.retrieve();
System.out.println(" Value: " + num);
System.out.println("\nPair class:");
class Pair<T> {
private T first;
private T second;
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void swap() {
T temp = first;
first = second;
second = temp;
}
public void display() {
System.out.println(" First: " + first + ", Second: " + second);
}
}
Pair<String> namePair = new Pair<>("Alice", "Bob");
namePair.display();
namePair.swap();
System.out.println(" After swap:");
namePair.display();
System.out.println("\nPoint class:");
class Point<T extends Number> {
private T x;
private T y;
public Point(T x, T y) {
this.x = x;
this.y = y;
}
public T getX() { return x; }
public T getY() { return y; }
public double distance() {
double dx = x.doubleValue();
double dy = y.doubleValue();
return Math.sqrt(dx * dx + dy * dy);
}
public void display() {
System.out.println(" Point(" + x + ", " + y + ")");
}
}
int pointX = ;
Point<Integer> intPoint = new Point<>(pointX, 4);
intPoint.display();
System.out.println(" Distance: " + intPoint.distance());
Point<Double> doublePoint = new Point<>(1.5, 2.5);
doublePoint.display();
System.out.println(" Distance: " + doublePoint.distance());
}
}
public class GenericWithMethods {
static class Container<T> {
private T item;
public void store(T item) {
this.item = item;
System.out.println(" Stored: " + item);
}
public T retrieve() {
System.out.println(" Retrieved: " + item);
return item;
}
public boolean isEmpty() {
return item == null;
}
public void clear() {
item = null;
System.out.println(" Cleared");
}
public String getInfo() {
if (item == null) {
return "Empty container";
}
return "Container holding: " + item.getClass().getSimpleName();
}
}
public static void main(String[] args) {
System.out.println("Generic methods:\n");
Container<String> strContainer = new Container<>();
strContainer.store("Hello");
String value = strContainer.retrieve();
System.out.println(" Info: " + strContainer.getInfo());
// Methods in generic class can use type parameter T
// Parameters and return types can be T
// Type is consistent throughout the instance
System.out.println("\nInteger container:");
Container<Integer> intContainer = new Container<>();
System.out.println(" Empty: " + intContainer.isEmpty());
intContainer.store(42);
System.out.println(" Empty: " + intContainer.isEmpty());
int num = intContainer.retrieve();
System.out.println(" Value: " + num);
System.out.println("\nPair class:");
class Pair<T> {
private T first;
private T second;
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void swap() {
T temp = first;
first = second;
second = temp;
}
public void display() {
System.out.println(" First: " + first + ", Second: " + second);
}
}
Pair<String> namePair = new Pair<>("Alice", "Bob");
namePair.display();
namePair.swap();
System.out.println(" After swap:");
namePair.display();
System.out.println("\nPoint class:");
class Point<T extends Number> {
private T x;
private T y;
public Point(T x, T y) {
this.x = x;
this.y = y;
}
public T getX() { return x; }
public T getY() { return y; }
public double distance() {
double dx = x.doubleValue();
double dy = y.doubleValue();
return Math.sqrt(dx * dx + dy * dy);
}
public void display() {
System.out.println(" Point(" + x + ", " + y + ")");
}
}
int pointX = ;
Point<Integer> intPoint = new Point<>(pointX, 4);
intPoint.display();
System.out.println(" Distance: " + intPoint.distance());
Point<Double> doublePoint = new Point<>(1.5, 2.5);
doublePoint.display();
System.out.println(" Distance: " + doublePoint.distance());
}
}
Type parameter T available in fields, methods, and return types.
Multiple type parameters
Use more than one type parameter.
import java.util.*;
public class MultipleTypeParams {
static class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void display() {
System.out.println(" " + key + " -> " + value);
}
}
public static void main(String[] args) {
System.out.println("Multiple type parameters:\n");
Pair<String, Integer> agePair = new Pair<>("Alice", 30);
agePair.display();
Pair<Integer, String> namePair = new Pair<>(1, "Bob");
namePair.display();
Pair<String, Double> pricePair = new Pair<>("Coffee", 3.50);
pricePair.display();
// Multiple type parameters separated by commas: <K, V>
// Each can be a different type
// Common in key-value data structures
System.out.println("\nSimple map:");
class SimpleMap<K, V> {
private List<K> keys = new ArrayList<>();
private List<V> values = new ArrayList<>();
public void put(K key, V value) {
keys.add(key);
values.add(value);
}
public V get(K key) {
int index = keys.indexOf(key);
return index >= 0 ? values.get(index) : null;
}
public void display() {
for (int i = 0; i < keys.size(); i++) {
System.out.println(" " + keys.get(i) + " = " + values.get(i));
}
}
}
SimpleMap<String, Integer> scores = new SimpleMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Charlie", 92);
scores.display();
String lookupName = ;
System.out.println(" " + lookupName + "'s score: " + scores.get(lookupName));
System.out.println("\nTriple class:");
class Triple<A, B, C> {
private A first;
private B second;
private C third;
public Triple(A first, B second, C third) {
this.first = first;
this.second = second;
this.third = third;
}
public A getFirst() { return first; }
public B getSecond() { return second; }
public C getThird() { return third; }
public void display() {
System.out.println(" (" + first + ", " + second + ", " + third + ")");
}
}
Triple<String, Integer, Double> studentRecord =
new Triple<>("Alice", 20, 3.8);
studentRecord.display();
Triple<Integer, String, Boolean> config =
new Triple<>(8080, "localhost", true);
config.display();
System.out.println("\nEntry class:");
class Entry<K, V> {
private K key;
private V value;
private long timestamp;
public Entry(K key, V value) {
this.key = key;
this.value = value;
this.timestamp = 1000L;
}
public K getKey() { return key; }
public V getValue() { return value; }
public void setValue(V value) {
this.value = value;
this.timestamp = 1001L;
}
public void display() {
System.out.println(" " + key + ": " + value +
" (ts: " + timestamp + ")");
}
}
Entry<String, String> config1 = new Entry<>("host", "localhost");
config1.display();
Entry<Integer, List<String>> userTags =
new Entry<>(101, Arrays.asList("admin", "active"));
userTags.display();
}
}
import java.util.*;
public class MultipleTypeParams {
static class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void display() {
System.out.println(" " + key + " -> " + value);
}
}
public static void main(String[] args) {
System.out.println("Multiple type parameters:\n");
Pair<String, Integer> agePair = new Pair<>("Alice", 30);
agePair.display();
Pair<Integer, String> namePair = new Pair<>(1, "Bob");
namePair.display();
Pair<String, Double> pricePair = new Pair<>("Coffee", 3.50);
pricePair.display();
// Multiple type parameters separated by commas: <K, V>
// Each can be a different type
// Common in key-value data structures
System.out.println("\nSimple map:");
class SimpleMap<K, V> {
private List<K> keys = new ArrayList<>();
private List<V> values = new ArrayList<>();
public void put(K key, V value) {
keys.add(key);
values.add(value);
}
public V get(K key) {
int index = keys.indexOf(key);
return index >= 0 ? values.get(index) : null;
}
public void display() {
for (int i = 0; i < keys.size(); i++) {
System.out.println(" " + keys.get(i) + " = " + values.get(i));
}
}
}
SimpleMap<String, Integer> scores = new SimpleMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Charlie", 92);
scores.display();
String lookupName = ;
System.out.println(" " + lookupName + "'s score: " + scores.get(lookupName));
System.out.println("\nTriple class:");
class Triple<A, B, C> {
private A first;
private B second;
private C third;
public Triple(A first, B second, C third) {
this.first = first;
this.second = second;
this.third = third;
}
public A getFirst() { return first; }
public B getSecond() { return second; }
public C getThird() { return third; }
public void display() {
System.out.println(" (" + first + ", " + second + ", " + third + ")");
}
}
Triple<String, Integer, Double> studentRecord =
new Triple<>("Alice", 20, 3.8);
studentRecord.display();
Triple<Integer, String, Boolean> config =
new Triple<>(8080, "localhost", true);
config.display();
System.out.println("\nEntry class:");
class Entry<K, V> {
private K key;
private V value;
private long timestamp;
public Entry(K key, V value) {
this.key = key;
this.value = value;
this.timestamp = 1000L;
}
public K getKey() { return key; }
public V getValue() { return value; }
public void setValue(V value) {
this.value = value;
this.timestamp = 1001L;
}
public void display() {
System.out.println(" " + key + ": " + value +
" (ts: " + timestamp + ")");
}
}
Entry<String, String> config1 = new Entry<>("host", "localhost");
config1.display();
Entry<Integer, List<String>> userTags =
new Entry<>(101, Arrays.asList("admin", "active"));
userTags.display();
}
}
import java.util.*;
public class MultipleTypeParams {
static class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void display() {
System.out.println(" " + key + " -> " + value);
}
}
public static void main(String[] args) {
System.out.println("Multiple type parameters:\n");
Pair<String, Integer> agePair = new Pair<>("Alice", 30);
agePair.display();
Pair<Integer, String> namePair = new Pair<>(1, "Bob");
namePair.display();
Pair<String, Double> pricePair = new Pair<>("Coffee", 3.50);
pricePair.display();
// Multiple type parameters separated by commas: <K, V>
// Each can be a different type
// Common in key-value data structures
System.out.println("\nSimple map:");
class SimpleMap<K, V> {
private List<K> keys = new ArrayList<>();
private List<V> values = new ArrayList<>();
public void put(K key, V value) {
keys.add(key);
values.add(value);
}
public V get(K key) {
int index = keys.indexOf(key);
return index >= 0 ? values.get(index) : null;
}
public void display() {
for (int i = 0; i < keys.size(); i++) {
System.out.println(" " + keys.get(i) + " = " + values.get(i));
}
}
}
SimpleMap<String, Integer> scores = new SimpleMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Charlie", 92);
scores.display();
String lookupName = ;
System.out.println(" " + lookupName + "'s score: " + scores.get(lookupName));
System.out.println("\nTriple class:");
class Triple<A, B, C> {
private A first;
private B second;
private C third;
public Triple(A first, B second, C third) {
this.first = first;
this.second = second;
this.third = third;
}
public A getFirst() { return first; }
public B getSecond() { return second; }
public C getThird() { return third; }
public void display() {
System.out.println(" (" + first + ", " + second + ", " + third + ")");
}
}
Triple<String, Integer, Double> studentRecord =
new Triple<>("Alice", 20, 3.8);
studentRecord.display();
Triple<Integer, String, Boolean> config =
new Triple<>(8080, "localhost", true);
config.display();
System.out.println("\nEntry class:");
class Entry<K, V> {
private K key;
private V value;
private long timestamp;
public Entry(K key, V value) {
this.key = key;
this.value = value;
this.timestamp = 1000L;
}
public K getKey() { return key; }
public V getValue() { return value; }
public void setValue(V value) {
this.value = value;
this.timestamp = 1001L;
}
public void display() {
System.out.println(" " + key + ": " + value +
" (ts: " + timestamp + ")");
}
}
Entry<String, String> config1 = new Entry<>("host", "localhost");
config1.display();
Entry<Integer, List<String>> userTags =
new Entry<>(101, Arrays.asList("admin", "active"));
userTags.display();
}
}
class Pair<K, V> - K for key, V for value. Like Map entries.
Generic interface
Define interfaces with type parameters.
import java.util.*;
public class GenericInterface {
interface Container<T> {
void add(T item);
T get(int index);
int size();
boolean isEmpty();
}
static class ListContainer<T> implements Container<T> {
private List<T> items = new ArrayList<>();
@Override
public void add(T item) {
items.add(item);
System.out.println(" Added: " + item);
}
@Override
public T get(int index) {
return items.get(index);
}
@Override
public int size() {
return items.size();
}
@Override
public boolean isEmpty() {
return items.isEmpty();
}
}
public static void main(String[] args) {
System.out.println("Generic interface:\n");
Container<String> strContainer = new ListContainer<>();
strContainer.add("Apple");
strContainer.add("Banana");
strContainer.add("Cherry");
System.out.println(" Size: " + strContainer.size());
System.out.println(" First: " + strContainer.get(0));
// Interfaces can have type parameters
// Implementing class specifies type or remains generic
// Same type safety benefits as generic classes
System.out.println("\nStorage interface:");
interface Storage<T> {
void store(T item);
T retrieve();
void clear();
}
class MemoryStorage<T> implements Storage<T> {
private T item;
@Override
public void store(T item) {
this.item = item;
System.out.println(" Stored: " + item);
}
@Override
public T retrieve() {
return item;
}
@Override
public void clear() {
item = null;
System.out.println(" Cleared");
}
}
Storage<Integer> intStorage = new MemoryStorage<>();
intStorage.store(42);
int value = intStorage.retrieve();
System.out.println(" Retrieved: " + value);
System.out.println("\nConcrete implementation:");
class StringContainer implements Container<String> {
private List<String> items = new ArrayList<>();
@Override
public void add(String item) {
items.add(item.toUpperCase());
System.out.println(" Added (upper): " + item);
}
@Override
public String get(int index) {
return items.get(index);
}
@Override
public int size() {
return items.size();
}
@Override
public boolean isEmpty() {
return items.isEmpty();
}
}
Container<String> concrete = new StringContainer();
concrete.add("hello");
concrete.add("world");
System.out.println(" First: " + concrete.get(0));
System.out.println("\nComparable-like:");
interface Comparable<T> {
int compareTo(T other);
}
class Person implements Comparable<Person> {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Bob", 25);
int result = p1.compareTo(p2);
System.out.println(" " + p1 + " vs " + p2 + ": " + result);
System.out.println("\nRepository pattern:");
interface Repository<T, ID> {
void save(T entity);
T findById(ID id);
}
class UserRepository implements Repository<String, Integer> {
private Map<Integer, String> users = new HashMap<>();
@Override
public void save(String user) {
int id = users.size() + 1;
users.put(id, user);
System.out.println(" Saved user: " + user + " (id=" + id + ")");
}
@Override
public String findById(Integer id) {
return users.get(id);
}
}
Repository<String, Integer> repo = new UserRepository();
repo.save("Alice");
repo.save("Bob");
int lookupId = ;
String user = repo.findById(lookupId);
System.out.println(" User " + lookupId + ": " + user);
}
}
import java.util.*;
public class GenericInterface {
interface Container<T> {
void add(T item);
T get(int index);
int size();
boolean isEmpty();
}
static class ListContainer<T> implements Container<T> {
private List<T> items = new ArrayList<>();
@Override
public void add(T item) {
items.add(item);
System.out.println(" Added: " + item);
}
@Override
public T get(int index) {
return items.get(index);
}
@Override
public int size() {
return items.size();
}
@Override
public boolean isEmpty() {
return items.isEmpty();
}
}
public static void main(String[] args) {
System.out.println("Generic interface:\n");
Container<String> strContainer = new ListContainer<>();
strContainer.add("Apple");
strContainer.add("Banana");
strContainer.add("Cherry");
System.out.println(" Size: " + strContainer.size());
System.out.println(" First: " + strContainer.get(0));
// Interfaces can have type parameters
// Implementing class specifies type or remains generic
// Same type safety benefits as generic classes
System.out.println("\nStorage interface:");
interface Storage<T> {
void store(T item);
T retrieve();
void clear();
}
class MemoryStorage<T> implements Storage<T> {
private T item;
@Override
public void store(T item) {
this.item = item;
System.out.println(" Stored: " + item);
}
@Override
public T retrieve() {
return item;
}
@Override
public void clear() {
item = null;
System.out.println(" Cleared");
}
}
Storage<Integer> intStorage = new MemoryStorage<>();
intStorage.store(42);
int value = intStorage.retrieve();
System.out.println(" Retrieved: " + value);
System.out.println("\nConcrete implementation:");
class StringContainer implements Container<String> {
private List<String> items = new ArrayList<>();
@Override
public void add(String item) {
items.add(item.toUpperCase());
System.out.println(" Added (upper): " + item);
}
@Override
public String get(int index) {
return items.get(index);
}
@Override
public int size() {
return items.size();
}
@Override
public boolean isEmpty() {
return items.isEmpty();
}
}
Container<String> concrete = new StringContainer();
concrete.add("hello");
concrete.add("world");
System.out.println(" First: " + concrete.get(0));
System.out.println("\nComparable-like:");
interface Comparable<T> {
int compareTo(T other);
}
class Person implements Comparable<Person> {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Bob", 25);
int result = p1.compareTo(p2);
System.out.println(" " + p1 + " vs " + p2 + ": " + result);
System.out.println("\nRepository pattern:");
interface Repository<T, ID> {
void save(T entity);
T findById(ID id);
}
class UserRepository implements Repository<String, Integer> {
private Map<Integer, String> users = new HashMap<>();
@Override
public void save(String user) {
int id = users.size() + 1;
users.put(id, user);
System.out.println(" Saved user: " + user + " (id=" + id + ")");
}
@Override
public String findById(Integer id) {
return users.get(id);
}
}
Repository<String, Integer> repo = new UserRepository();
repo.save("Alice");
repo.save("Bob");
int lookupId = ;
String user = repo.findById(lookupId);
System.out.println(" User " + lookupId + ": " + user);
}
}
import java.util.*;
public class GenericInterface {
interface Container<T> {
void add(T item);
T get(int index);
int size();
boolean isEmpty();
}
static class ListContainer<T> implements Container<T> {
private List<T> items = new ArrayList<>();
@Override
public void add(T item) {
items.add(item);
System.out.println(" Added: " + item);
}
@Override
public T get(int index) {
return items.get(index);
}
@Override
public int size() {
return items.size();
}
@Override
public boolean isEmpty() {
return items.isEmpty();
}
}
public static void main(String[] args) {
System.out.println("Generic interface:\n");
Container<String> strContainer = new ListContainer<>();
strContainer.add("Apple");
strContainer.add("Banana");
strContainer.add("Cherry");
System.out.println(" Size: " + strContainer.size());
System.out.println(" First: " + strContainer.get(0));
// Interfaces can have type parameters
// Implementing class specifies type or remains generic
// Same type safety benefits as generic classes
System.out.println("\nStorage interface:");
interface Storage<T> {
void store(T item);
T retrieve();
void clear();
}
class MemoryStorage<T> implements Storage<T> {
private T item;
@Override
public void store(T item) {
this.item = item;
System.out.println(" Stored: " + item);
}
@Override
public T retrieve() {
return item;
}
@Override
public void clear() {
item = null;
System.out.println(" Cleared");
}
}
Storage<Integer> intStorage = new MemoryStorage<>();
intStorage.store(42);
int value = intStorage.retrieve();
System.out.println(" Retrieved: " + value);
System.out.println("\nConcrete implementation:");
class StringContainer implements Container<String> {
private List<String> items = new ArrayList<>();
@Override
public void add(String item) {
items.add(item.toUpperCase());
System.out.println(" Added (upper): " + item);
}
@Override
public String get(int index) {
return items.get(index);
}
@Override
public int size() {
return items.size();
}
@Override
public boolean isEmpty() {
return items.isEmpty();
}
}
Container<String> concrete = new StringContainer();
concrete.add("hello");
concrete.add("world");
System.out.println(" First: " + concrete.get(0));
System.out.println("\nComparable-like:");
interface Comparable<T> {
int compareTo(T other);
}
class Person implements Comparable<Person> {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Bob", 25);
int result = p1.compareTo(p2);
System.out.println(" " + p1 + " vs " + p2 + ": " + result);
System.out.println("\nRepository pattern:");
interface Repository<T, ID> {
void save(T entity);
T findById(ID id);
}
class UserRepository implements Repository<String, Integer> {
private Map<Integer, String> users = new HashMap<>();
@Override
public void save(String user) {
int id = users.size() + 1;
users.put(id, user);
System.out.println(" Saved user: " + user + " (id=" + id + ")");
}
@Override
public String findById(Integer id) {
return users.get(id);
}
}
Repository<String, Integer> repo = new UserRepository();
repo.save("Alice");
repo.save("Bob");
int lookupId = ;
String user = repo.findById(lookupId);
System.out.println(" User " + lookupId + ": " + user);
}
}
interface Container<T> - implementations specify the type.
Diamond operator
Let compiler infer type arguments.
import java.util.*;
public class DiamondOperator {
static class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public static void main(String[] args) {
System.out.println("Diamond operator:\n");
// Before Java 7 (verbose)
Box<String> box1 = new Box<String>("Hello");
System.out.println(" Old way: " + box1.getValue());
// Java 7+ (diamond operator)
Box<String> box2 = new Box<>("World");
System.out.println(" Diamond: " + box2.getValue());
// Diamond operator <> infers type from left side
// Available since Java 7
// Reduces verbosity while maintaining type safety
// Compiler infers the type parameter
System.out.println("\nWith collections:");
// Verbose
List<String> list1 = new ArrayList<String>();
list1.add("Apple");
// Diamond
List<String> list2 = new ArrayList<>();
list2.add("Banana");
Map<String, Integer> map = new LinkedHashMap<>();
map.put("Alice", 30);
map.put("Bob", 25);
System.out.println(" List: " + list2);
System.out.println(" Map: " + map);
System.out.println("\nNested generics:");
// Before Java 7 (very verbose)
Map<String, List<Integer>> map1 =
new HashMap<String, List<Integer>>();
// With diamond (cleaner)
Map<String, List<Integer>> map2 = new LinkedHashMap<>();
map2.put("scores", Arrays.asList(85, 90, 95));
map2.put("ages", Arrays.asList(20, 21, 22));
System.out.println(" Nested map: " + map2);
System.out.println("\nCustom classes:");
class Pair<K, V> {
K key;
V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public String toString() {
return key + "=" + value;
}
}
// Diamond inference
Pair<String, Integer> pair1 = new Pair<>("age", 30);
Pair<Integer, String> pair2 = new Pair<>(1, "first");
System.out.println(" " + pair1);
System.out.println(" " + pair2);
System.out.println("\nReturn type inference:");
class Factory {
static <T> Box<T> createBox(T value) {
return new Box<>(value); // Diamond infers T
}
}
Box<String> strBox = Factory.createBox("Hello");
Box<Integer> intBox = Factory.createBox(42);
System.out.println(" String box: " + strBox.getValue());
System.out.println(" Integer box: " + intBox.getValue());
System.out.println("\nComplex example:");
class Container<T> {
List<T> items;
Container() {
this.items = new ArrayList<>(); // Diamond
}
void add(T item) {
items.add(item);
}
List<T> getItems() {
return items;
}
}
Container<String> container = new Container<>(); // Diamond
container.add("A");
container.add("B");
container.add("C");
System.out.println(" Items: " + container.getItems());
// Nested
List<Container<Integer>> containers = new ArrayList<>();
Container<Integer> c1 = new Container<>();
c1.add(1);
c1.add(2);
containers.add(c1);
System.out.println(" Nested: " + containers.get(0).getItems());
}
}
new Box<>() - compiler infers type from variable declaration.
Exercise: Practical.java
Build a type-safe stack using generics