Generics
Generic Methods
Method-Level Type Parameters
Your utility class needs a swap method that works with any type. You don't want a generic class - just one method. Generic methods declare their own type parameters, independent of any class generics.
Basic generic method
Declare type parameter before return type.
import java.util.*;
public class Basic {
public static <T> void print(T item) {
System.out.println(" Item: " + item);
}
public static <T> void printArray(T[] array) {
System.out.print(" Array: [");
for (int i = 0; i < array.length; i++) {
if (i > 0) System.out.print(", ");
System.out.print(array[i]);
}
System.out.println("]");
}
public static void main(String[] args) {
System.out.println("Basic generic methods:\n");
print(42);
print("Hello");
print(3.14);
Integer[] ints = {1, 2, 3, 4, 5};
String[] strs = {"a", "b", "c"};
printArray(ints);
printArray(strs);
// Generic methods have type parameters: <T>
// Type parameter comes before return type
// Can be used in parameters and return type
// Java infers type from arguments
System.out.println("\nIdentity function:");
class Utils {
public static <T> T identity(T value) {
return value;
}
}
Integer num = Utils.identity(42);
String text = Utils.identity("Hello");
System.out.println(" Number: " + num);
System.out.println(" Text: " + text);
System.out.println("\nSwap elements:");
class ArrayOps {
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
Integer[] numbers = {1, 2, 3, 4, 5};
System.out.println(" Before: " + Arrays.toString(numbers));
ArrayOps.swap(numbers, 0, 4);
System.out.println(" After: " + Arrays.toString(numbers));
System.out.println("\nGet first:");
class Getter {
public static <T> T getFirst(T[] array) {
return array.length > 0 ? array[0] : null;
}
public static <T> T getFirst(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
}
System.out.println(" First int: " + Getter.getFirst(ints));
System.out.println(" First string: " + Getter.getFirst(strs));
System.out.println("\nCreate pair:");
class Pair<K, V> {
K key;
V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
public String toString() {
return "(" + key + ", " + value + ")";
}
}
class PairFactory {
public static <K, V> Pair<K, V> create(K key, V value) {
return new Pair<>(key, value);
}
}
Pair<String, Integer> p1 = PairFactory.create("age", 30);
Pair<Integer, String> p2 = PairFactory.create(1, "first");
System.out.println(" Pair 1: " + p1);
System.out.println(" Pair 2: " + p2);
System.out.println("\nContains check:");
class Checker {
public static <T> boolean contains(T[] array, T item) {
for (T element : array) {
if (element.equals(item)) {
return true;
}
}
return false;
}
}
int searchNumber = ;
System.out.println(" ints contains " + searchNumber + ": " +
Checker.contains(ints, searchNumber));
System.out.println(" strs contains 'd': " + Checker.contains(strs, "d"));
}
}
import java.util.*;
public class Basic {
public static <T> void print(T item) {
System.out.println(" Item: " + item);
}
public static <T> void printArray(T[] array) {
System.out.print(" Array: [");
for (int i = 0; i < array.length; i++) {
if (i > 0) System.out.print(", ");
System.out.print(array[i]);
}
System.out.println("]");
}
public static void main(String[] args) {
System.out.println("Basic generic methods:\n");
print(42);
print("Hello");
print(3.14);
Integer[] ints = {1, 2, 3, 4, 5};
String[] strs = {"a", "b", "c"};
printArray(ints);
printArray(strs);
// Generic methods have type parameters: <T>
// Type parameter comes before return type
// Can be used in parameters and return type
// Java infers type from arguments
System.out.println("\nIdentity function:");
class Utils {
public static <T> T identity(T value) {
return value;
}
}
Integer num = Utils.identity(42);
String text = Utils.identity("Hello");
System.out.println(" Number: " + num);
System.out.println(" Text: " + text);
System.out.println("\nSwap elements:");
class ArrayOps {
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
Integer[] numbers = {1, 2, 3, 4, 5};
System.out.println(" Before: " + Arrays.toString(numbers));
ArrayOps.swap(numbers, 0, 4);
System.out.println(" After: " + Arrays.toString(numbers));
System.out.println("\nGet first:");
class Getter {
public static <T> T getFirst(T[] array) {
return array.length > 0 ? array[0] : null;
}
public static <T> T getFirst(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
}
System.out.println(" First int: " + Getter.getFirst(ints));
System.out.println(" First string: " + Getter.getFirst(strs));
System.out.println("\nCreate pair:");
class Pair<K, V> {
K key;
V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
public String toString() {
return "(" + key + ", " + value + ")";
}
}
class PairFactory {
public static <K, V> Pair<K, V> create(K key, V value) {
return new Pair<>(key, value);
}
}
Pair<String, Integer> p1 = PairFactory.create("age", 30);
Pair<Integer, String> p2 = PairFactory.create(1, "first");
System.out.println(" Pair 1: " + p1);
System.out.println(" Pair 2: " + p2);
System.out.println("\nContains check:");
class Checker {
public static <T> boolean contains(T[] array, T item) {
for (T element : array) {
if (element.equals(item)) {
return true;
}
}
return false;
}
}
int searchNumber = ;
System.out.println(" ints contains " + searchNumber + ": " +
Checker.contains(ints, searchNumber));
System.out.println(" strs contains 'd': " + Checker.contains(strs, "d"));
}
}
import java.util.*;
public class Basic {
public static <T> void print(T item) {
System.out.println(" Item: " + item);
}
public static <T> void printArray(T[] array) {
System.out.print(" Array: [");
for (int i = 0; i < array.length; i++) {
if (i > 0) System.out.print(", ");
System.out.print(array[i]);
}
System.out.println("]");
}
public static void main(String[] args) {
System.out.println("Basic generic methods:\n");
print(42);
print("Hello");
print(3.14);
Integer[] ints = {1, 2, 3, 4, 5};
String[] strs = {"a", "b", "c"};
printArray(ints);
printArray(strs);
// Generic methods have type parameters: <T>
// Type parameter comes before return type
// Can be used in parameters and return type
// Java infers type from arguments
System.out.println("\nIdentity function:");
class Utils {
public static <T> T identity(T value) {
return value;
}
}
Integer num = Utils.identity(42);
String text = Utils.identity("Hello");
System.out.println(" Number: " + num);
System.out.println(" Text: " + text);
System.out.println("\nSwap elements:");
class ArrayOps {
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
Integer[] numbers = {1, 2, 3, 4, 5};
System.out.println(" Before: " + Arrays.toString(numbers));
ArrayOps.swap(numbers, 0, 4);
System.out.println(" After: " + Arrays.toString(numbers));
System.out.println("\nGet first:");
class Getter {
public static <T> T getFirst(T[] array) {
return array.length > 0 ? array[0] : null;
}
public static <T> T getFirst(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
}
System.out.println(" First int: " + Getter.getFirst(ints));
System.out.println(" First string: " + Getter.getFirst(strs));
System.out.println("\nCreate pair:");
class Pair<K, V> {
K key;
V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
public String toString() {
return "(" + key + ", " + value + ")";
}
}
class PairFactory {
public static <K, V> Pair<K, V> create(K key, V value) {
return new Pair<>(key, value);
}
}
Pair<String, Integer> p1 = PairFactory.create("age", 30);
Pair<Integer, String> p2 = PairFactory.create(1, "first");
System.out.println(" Pair 1: " + p1);
System.out.println(" Pair 2: " + p2);
System.out.println("\nContains check:");
class Checker {
public static <T> boolean contains(T[] array, T item) {
for (T element : array) {
if (element.equals(item)) {
return true;
}
}
return false;
}
}
int searchNumber = ;
System.out.println(" ints contains " + searchNumber + ": " +
Checker.contains(ints, searchNumber));
System.out.println(" strs contains 'd': " + Checker.contains(strs, "d"));
}
}
<T> T method(T arg) - T is scoped to this method only.
Bounded generic methods
Restrict type parameter to certain types.
import java.util.*;
public class Bounds {
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
public static <T extends Comparable<T>> T min(T a, T b) {
return a.compareTo(b) < 0 ? a : b;
}
public static void main(String[] args) {
System.out.println("Bounded generic methods:\n");
System.out.println(" max(5, 10): " + max(5, 10));
System.out.println(" max('a', 'z'): " + max('a', 'z'));
System.out.println(" min(\"apple\", \"banana\"): " + min("apple", "banana"));
// <T extends Type> constrains T to be Type or subclass
// Allows calling Type's methods on T
// Common with Comparable, Number, Serializable
// Can combine multiple bounds with &
System.out.println("\nSum numbers:");
class NumberOps {
public static <T extends Number> double sum(T[] numbers) {
double total = 0;
for (T num : numbers) {
total += num.doubleValue();
}
return total;
}
public static <T extends Number> double average(T[] numbers) {
if (numbers.length == 0) {
return 0;
}
return sum(numbers) / numbers.length;
}
}
Integer[] ints = {1, 2, 3, 4, 5};
Double[] doubles = {1.5, 2.5, 3.5};
System.out.println(" Sum ints: " + NumberOps.sum(ints));
System.out.println(" Sum doubles: " + NumberOps.sum(doubles));
System.out.println(" Average: " + NumberOps.average(ints));
System.out.println("\nFind maximum:");
class Finder {
public static <T extends Comparable<T>> T findMax(T[] array) {
if (array.length == 0) {
return null;
}
T max = array[0];
for (T item : array) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
}
System.out.println(" Max int: " + Finder.findMax(ints));
String[] words = {"apple", "zebra", "banana", "cherry"};
System.out.println(" Max string: " + Finder.findMax(words));
System.out.println("\nSort array:");
class Sorter {
public static <T extends Comparable<T>> void sort(T[] array) {
for (int i = 0; i < array.length - 1; i++) {
for (int j = i + 1; j < array.length; j++) {
if (array[i].compareTo(array[j]) > 0) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
}
Integer[] nums = {5, 2, 8, 1, 9};
System.out.println(" Before: " + Arrays.toString(nums));
Sorter.sort(nums);
System.out.println(" After: " + Arrays.toString(nums));
System.out.println("\nClamp value:");
class RangeOps {
public static <T extends Comparable<T>> T clamp(
T value, T min, T max) {
if (value.compareTo(min) < 0) {
return min;
}
if (value.compareTo(max) > 0) {
return max;
}
return value;
}
}
System.out.println(" clamp(5, 0, 10): " + RangeOps.clamp(5, 0, 10));
System.out.println(" clamp(15, 0, 10): " + RangeOps.clamp(15, 0, 10));
System.out.println(" clamp(-5, 0, 10): " + RangeOps.clamp(-5, 0, 10));
System.out.println("\nCount greater:");
class Counter {
public static <T extends Comparable<T>> int countGreater(
T[] array, T threshold) {
int count = 0;
for (T item : array) {
if (item.compareTo(threshold) > 0) {
count++;
}
}
return count;
}
}
int countThreshold = ;
System.out.println(" Count > " + countThreshold + ": " +
Counter.countGreater(ints, countThreshold));
System.out.println(" Count > 'c': " + Counter.countGreater(words, "c"));
}
}
import java.util.*;
public class Bounds {
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
public static <T extends Comparable<T>> T min(T a, T b) {
return a.compareTo(b) < 0 ? a : b;
}
public static void main(String[] args) {
System.out.println("Bounded generic methods:\n");
System.out.println(" max(5, 10): " + max(5, 10));
System.out.println(" max('a', 'z'): " + max('a', 'z'));
System.out.println(" min(\"apple\", \"banana\"): " + min("apple", "banana"));
// <T extends Type> constrains T to be Type or subclass
// Allows calling Type's methods on T
// Common with Comparable, Number, Serializable
// Can combine multiple bounds with &
System.out.println("\nSum numbers:");
class NumberOps {
public static <T extends Number> double sum(T[] numbers) {
double total = 0;
for (T num : numbers) {
total += num.doubleValue();
}
return total;
}
public static <T extends Number> double average(T[] numbers) {
if (numbers.length == 0) {
return 0;
}
return sum(numbers) / numbers.length;
}
}
Integer[] ints = {1, 2, 3, 4, 5};
Double[] doubles = {1.5, 2.5, 3.5};
System.out.println(" Sum ints: " + NumberOps.sum(ints));
System.out.println(" Sum doubles: " + NumberOps.sum(doubles));
System.out.println(" Average: " + NumberOps.average(ints));
System.out.println("\nFind maximum:");
class Finder {
public static <T extends Comparable<T>> T findMax(T[] array) {
if (array.length == 0) {
return null;
}
T max = array[0];
for (T item : array) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
}
System.out.println(" Max int: " + Finder.findMax(ints));
String[] words = {"apple", "zebra", "banana", "cherry"};
System.out.println(" Max string: " + Finder.findMax(words));
System.out.println("\nSort array:");
class Sorter {
public static <T extends Comparable<T>> void sort(T[] array) {
for (int i = 0; i < array.length - 1; i++) {
for (int j = i + 1; j < array.length; j++) {
if (array[i].compareTo(array[j]) > 0) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
}
Integer[] nums = {5, 2, 8, 1, 9};
System.out.println(" Before: " + Arrays.toString(nums));
Sorter.sort(nums);
System.out.println(" After: " + Arrays.toString(nums));
System.out.println("\nClamp value:");
class RangeOps {
public static <T extends Comparable<T>> T clamp(
T value, T min, T max) {
if (value.compareTo(min) < 0) {
return min;
}
if (value.compareTo(max) > 0) {
return max;
}
return value;
}
}
System.out.println(" clamp(5, 0, 10): " + RangeOps.clamp(5, 0, 10));
System.out.println(" clamp(15, 0, 10): " + RangeOps.clamp(15, 0, 10));
System.out.println(" clamp(-5, 0, 10): " + RangeOps.clamp(-5, 0, 10));
System.out.println("\nCount greater:");
class Counter {
public static <T extends Comparable<T>> int countGreater(
T[] array, T threshold) {
int count = 0;
for (T item : array) {
if (item.compareTo(threshold) > 0) {
count++;
}
}
return count;
}
}
int countThreshold = ;
System.out.println(" Count > " + countThreshold + ": " +
Counter.countGreater(ints, countThreshold));
System.out.println(" Count > 'c': " + Counter.countGreater(words, "c"));
}
}
import java.util.*;
public class Bounds {
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
public static <T extends Comparable<T>> T min(T a, T b) {
return a.compareTo(b) < 0 ? a : b;
}
public static void main(String[] args) {
System.out.println("Bounded generic methods:\n");
System.out.println(" max(5, 10): " + max(5, 10));
System.out.println(" max('a', 'z'): " + max('a', 'z'));
System.out.println(" min(\"apple\", \"banana\"): " + min("apple", "banana"));
// <T extends Type> constrains T to be Type or subclass
// Allows calling Type's methods on T
// Common with Comparable, Number, Serializable
// Can combine multiple bounds with &
System.out.println("\nSum numbers:");
class NumberOps {
public static <T extends Number> double sum(T[] numbers) {
double total = 0;
for (T num : numbers) {
total += num.doubleValue();
}
return total;
}
public static <T extends Number> double average(T[] numbers) {
if (numbers.length == 0) {
return 0;
}
return sum(numbers) / numbers.length;
}
}
Integer[] ints = {1, 2, 3, 4, 5};
Double[] doubles = {1.5, 2.5, 3.5};
System.out.println(" Sum ints: " + NumberOps.sum(ints));
System.out.println(" Sum doubles: " + NumberOps.sum(doubles));
System.out.println(" Average: " + NumberOps.average(ints));
System.out.println("\nFind maximum:");
class Finder {
public static <T extends Comparable<T>> T findMax(T[] array) {
if (array.length == 0) {
return null;
}
T max = array[0];
for (T item : array) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
}
System.out.println(" Max int: " + Finder.findMax(ints));
String[] words = {"apple", "zebra", "banana", "cherry"};
System.out.println(" Max string: " + Finder.findMax(words));
System.out.println("\nSort array:");
class Sorter {
public static <T extends Comparable<T>> void sort(T[] array) {
for (int i = 0; i < array.length - 1; i++) {
for (int j = i + 1; j < array.length; j++) {
if (array[i].compareTo(array[j]) > 0) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
}
Integer[] nums = {5, 2, 8, 1, 9};
System.out.println(" Before: " + Arrays.toString(nums));
Sorter.sort(nums);
System.out.println(" After: " + Arrays.toString(nums));
System.out.println("\nClamp value:");
class RangeOps {
public static <T extends Comparable<T>> T clamp(
T value, T min, T max) {
if (value.compareTo(min) < 0) {
return min;
}
if (value.compareTo(max) > 0) {
return max;
}
return value;
}
}
System.out.println(" clamp(5, 0, 10): " + RangeOps.clamp(5, 0, 10));
System.out.println(" clamp(15, 0, 10): " + RangeOps.clamp(15, 0, 10));
System.out.println(" clamp(-5, 0, 10): " + RangeOps.clamp(-5, 0, 10));
System.out.println("\nCount greater:");
class Counter {
public static <T extends Comparable<T>> int countGreater(
T[] array, T threshold) {
int count = 0;
for (T item : array) {
if (item.compareTo(threshold) > 0) {
count++;
}
}
return count;
}
}
int countThreshold = ;
System.out.println(" Count > " + countThreshold + ": " +
Counter.countGreater(ints, countThreshold));
System.out.println(" Count > 'c': " + Counter.countGreater(words, "c"));
}
}
<T extends Comparable<T>> - T must be comparable to itself.
Static generic methods
Static methods can be generic even in non-generic classes.
import java.util.*;
public class Static {
static class CollectionUtils {
public static <T> List<T> newArrayList() {
return new ArrayList<>();
}
public static <K, V> Map<K, V> newHashMap() {
return new LinkedHashMap<>();
}
public static <T> Set<T> newHashSet() {
return new LinkedHashSet<>();
}
}
public static void main(String[] args) {
System.out.println("Static generic methods:\n");
List<String> list = CollectionUtils.newArrayList();
list.add("a");
list.add("b");
Map<String, Integer> map = CollectionUtils.newHashMap();
map.put("one", 1);
map.put("two", 2);
System.out.println(" List: " + list);
System.out.println(" Map: " + map);
// Static methods can be generic
// Don't need generic class
// Type parameters independent of class
// Common in utility classes
System.out.println("\nCreate from varargs:");
class ListFactory {
@SafeVarargs
public static <T> List<T> of(T... items) {
List<T> list = new ArrayList<>();
for (T item : items) {
list.add(item);
}
return list;
}
}
List<Integer> nums = ListFactory.of(1, 2, 3, 4, 5);
List<String> words = ListFactory.of("a", "b", "c");
System.out.println(" Numbers: " + nums);
System.out.println(" Words: " + words);
System.out.println("\nCopy collections:");
class Copier {
public static <T> void copy(
Collection<T> dest,
Collection<T> src) {
dest.clear();
dest.addAll(src);
}
public static <T> List<T> copyToList(Collection<T> src) {
return new ArrayList<>(src);
}
}
Set<String> source = new LinkedHashSet<>(Arrays.asList("x", "y", "z"));
List<String> dest = new ArrayList<>();
Copier.copy(dest, source);
System.out.println(" Copied: " + dest);
System.out.println("\nReverse:");
class Reverser {
public static <T> List<T> reverse(List<T> list) {
List<T> result = new ArrayList<>();
for (int i = list.size() - 1; i >= 0; i--) {
result.add(list.get(i));
}
return result;
}
}
List<Integer> original = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> reversed = Reverser.reverse(original);
System.out.println(" Original: " + original);
System.out.println(" Reversed: " + reversed);
System.out.println("\nFilter:");
interface Predicate<T> {
boolean test(T item);
}
class Filter {
public static <T> List<T> filter(
Collection<T> collection,
Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : collection) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
}
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int divisor = 2;
List<Integer> evens = Filter.filter(numbers, n -> n % divisor == 0);
System.out.println(" Divisible by " + divisor + ": " + evens);
System.out.println("\nTransform:");
interface Function<T, R> {
R apply(T item);
}
class Transformer {
public static <T, R> List<R> map(
Collection<T> collection,
Function<T, R> mapper) {
List<R> result = new ArrayList<>();
for (T item : collection) {
result.add(mapper.apply(item));
}
return result;
}
}
List<String> strings = Arrays.asList("1", "2", "3");
List<Integer> parsed = Transformer.map(strings, Integer::parseInt);
System.out.println(" Strings: " + strings);
System.out.println(" Parsed: " + parsed);
}
}
public static <T> T identity(T val) - no instance needed.
Type inference
Compiler infers type from arguments.
import java.util.*;
public class Inference {
public static <T> T identity(T value) {
return value;
}
public static <K, V> Map<K, V> createMap(K key, V value) {
Map<K, V> map = new HashMap<>();
map.put(key, value);
return map;
}
public static void main(String[] args) {
System.out.println("Type inference:\n");
// Explicit type argument
String s1 = Inference.<String>identity("hello");
// Inferred type argument (preferred)
String s2 = identity("hello");
System.out.println(" Explicit: " + s1);
System.out.println(" Inferred: " + s2);
// Java can infer type parameters from arguments
// Explicit: ClassName.<Type>method(arg)
// Inferred: ClassName.method(arg) - preferred
// Inference works with return type context
System.out.println("\nInferred from arguments:");
class Pair<K, V> {
K key;
V value;
Pair(K key, V value) {
this.key = key;
this.value = value;
}
public String toString() {
return "(" + key + ", " + value + ")";
}
}
class PairFactory {
public static <K, V> Pair<K, V> make(K key, V value) {
return new Pair<>(key, value);
}
}
// Type inferred from arguments
Pair<String, Integer> p1 = PairFactory.make("age", 30);
Pair<Integer, String> p2 = PairFactory.make(1, "first");
System.out.println(" Pair 1: " + p1);
System.out.println(" Pair 2: " + p2);
System.out.println("\nDiamond operator:");
// Before Java 7 - redundant
Map<String, List<Integer>> old =
new HashMap<String, List<Integer>>();
// Java 7+ - inferred
Map<String, List<Integer>> modern = new HashMap<>();
modern.put("nums", Arrays.asList(1, 2, 3));
System.out.println(" Map: " + modern);
System.out.println("\nInferred return type:");
class Builder {
public static <T> List<T> build(T... items) {
List<T> list = new ArrayList<>();
for (T item : items) {
list.add(item);
}
return list;
}
}
List<String> words = Builder.build("a", "b", "c");
List<Integer> nums = Builder.build(1, 2, 3);
System.out.println(" Words: " + words);
System.out.println(" Numbers: " + nums);
System.out.println("\nComplex inference:");
class Wrapper<T> {
T value;
Wrapper(T value) {
this.value = value;
}
public String toString() {
return "Wrapper(" + value + ")";
}
}
class WrapperFactory {
public static <T> Wrapper<T> wrap(T value) {
return new Wrapper<>(value);
}
public static <T> List<Wrapper<T>> wrapAll(List<T> values) {
List<Wrapper<T>> result = new ArrayList<>();
for (T value : values) {
result.add(wrap(value));
}
return result;
}
}
List<Integer> numbers = Arrays.asList(1, 2, 3);
List<Wrapper<Integer>> wrapped = WrapperFactory.wrapAll(numbers);
System.out.println(" Wrapped: " + wrapped);
System.out.println("\nTarget type:");
class Utils {
public static <T> List<T> emptyList() {
return new ArrayList<>();
}
}
// Type inferred from variable declaration
List<String> empty1 = Utils.emptyList();
List<Integer> empty2 = Utils.emptyList();
System.out.println(" Empty string list: " + empty1);
System.out.println(" Empty int list: " + empty2);
System.out.println("\nMethod chaining:");
class FluentBuilder<T> {
private List<T> items = new ArrayList<>();
public static <T> FluentBuilder<T> create() {
return new FluentBuilder<>();
}
public FluentBuilder<T> add(T item) {
items.add(item);
return this;
}
public List<T> build() {
return items;
}
}
List<String> result = FluentBuilder.<String>create() // Explicit
.add("a")
.add("b")
.add("c")
.build();
List<Integer> result2 = FluentBuilder.<Integer>create() // Also explicit - chains need hint
.add(1)
.add(2)
.add(3)
.build();
System.out.println(" Result 1: " + result);
System.out.println(" Result 2: " + result2);
}
}
Box.create("text") - compiler knows T is String. No explicit <String>.
Generic constructors
Constructors can have their own type parameters.
import java.util.*;
public class Constructors {
static class Box<T> {
private T content;
// Generic constructor - U independent of T
<U> Box(U initialValue, Converter<U, T> converter) {
this.content = converter.convert(initialValue);
}
public T get() {
return content;
}
}
interface Converter<F, T> {
T convert(F from);
}
public static void main(String[] args) {
System.out.println("Generic constructors:\n");
// Create Box<Integer> from String
String rawBoxValue = ;
Box<Integer> box = new Box<>(rawBoxValue, Integer::parseInt);
System.out.println(" Box content: " + box.get());
// Constructors can have their own type parameters
// Type parameters independent of class type parameters
// Useful for type conversion during construction
// Generic constructors enable flexible initialization
System.out.println("\nCollection initialization:");
class Container<T> {
private List<T> items;
// Non-generic constructor
Container() {
this.items = new ArrayList<>();
}
// Generic constructor
<U> Container(Collection<U> source, Converter<U, T> converter) {
this.items = new ArrayList<>();
for (U item : source) {
items.add(converter.convert(item));
}
}
public List<T> getItems() {
return items;
}
}
List<String> strings = Arrays.asList("1", "2", "3", "4", "5");
Container<Integer> numbers = new Container<>(strings, Integer::parseInt);
System.out.println(" Numbers: " + numbers.getItems());
System.out.println("\nBuilder pattern:");
class Person {
private String name;
private int age;
private Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " (" + age + ")";
}
}
interface Factory<T> {
T create(Map<String, Object> props);
}
class Builder<T> {
private Map<String, Object> properties = new LinkedHashMap<>();
<U> Builder<T> set(String key, U value) {
properties.put(key, value);
return this;
}
T build(Factory<T> factory) {
return factory.create(properties);
}
}
Person person = new Builder<Person>()
.set("name", "Alice")
.set("age", 30)
.build(props -> new Person(
(String) props.get("name"),
(Integer) props.get("age")
));
System.out.println(" Person: " + person);
System.out.println("\nCached initialization:");
interface KeyConverter<K> {
String convert(K key);
}
class Cache<T> {
private Map<String, T> cache = new LinkedHashMap<>();
<K> Cache(Map<K, T> initial, KeyConverter<K> converter) {
for (Map.Entry<K, T> entry : initial.entrySet()) {
String key = converter.convert(entry.getKey());
cache.put(key, entry.getValue());
}
}
public T get(String key) {
return cache.get(key);
}
public Set<String> keys() {
return cache.keySet();
}
}
Map<Integer, String> data = new LinkedHashMap<>();
data.put(1, "one");
data.put(2, "two");
data.put(3, "three");
Cache<String> cache = new Cache<>(data, Object::toString);
System.out.println(" Keys: " + cache.keys());
System.out.println(" Value for '1': " + cache.get("1"));
System.out.println("\nMulti-source constructor:");
class Aggregator<T> {
private List<T> all = new ArrayList<>();
@SafeVarargs
<U> Aggregator(Converter<U, T> converter, Collection<U>... sources) {
for (Collection<U> source : sources) {
for (U item : source) {
all.add(converter.convert(item));
}
}
}
public List<T> getAll() {
return all;
}
}
List<String> source1 = Arrays.asList("1", "2", "3");
List<String> source2 = Arrays.asList("4", "5", "6");
Aggregator<Integer> agg = new Aggregator<>(
Integer::parseInt,
source1,
source2
);
System.out.println(" Aggregated: " + agg.getAll());
System.out.println("\nWrapper constructor:");
interface Parser<F, T> {
T parse(F from);
}
class Wrapper<T> {
private T value;
Wrapper(T value) {
this.value = value;
}
<U> Wrapper(U rawValue, Parser<U, T> parser) {
this.value = parser.parse(rawValue);
}
public T getValue() {
return value;
}
}
Wrapper<Integer> w1 = new Wrapper<>(42);
Wrapper<Integer> w2 = new Wrapper<>("123", Integer::parseInt);
System.out.println(" Wrapper 1: " + w1.getValue());
System.out.println(" Wrapper 2: " + w2.getValue());
}
}
import java.util.*;
public class Constructors {
static class Box<T> {
private T content;
// Generic constructor - U independent of T
<U> Box(U initialValue, Converter<U, T> converter) {
this.content = converter.convert(initialValue);
}
public T get() {
return content;
}
}
interface Converter<F, T> {
T convert(F from);
}
public static void main(String[] args) {
System.out.println("Generic constructors:\n");
// Create Box<Integer> from String
String rawBoxValue = ;
Box<Integer> box = new Box<>(rawBoxValue, Integer::parseInt);
System.out.println(" Box content: " + box.get());
// Constructors can have their own type parameters
// Type parameters independent of class type parameters
// Useful for type conversion during construction
// Generic constructors enable flexible initialization
System.out.println("\nCollection initialization:");
class Container<T> {
private List<T> items;
// Non-generic constructor
Container() {
this.items = new ArrayList<>();
}
// Generic constructor
<U> Container(Collection<U> source, Converter<U, T> converter) {
this.items = new ArrayList<>();
for (U item : source) {
items.add(converter.convert(item));
}
}
public List<T> getItems() {
return items;
}
}
List<String> strings = Arrays.asList("1", "2", "3", "4", "5");
Container<Integer> numbers = new Container<>(strings, Integer::parseInt);
System.out.println(" Numbers: " + numbers.getItems());
System.out.println("\nBuilder pattern:");
class Person {
private String name;
private int age;
private Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " (" + age + ")";
}
}
interface Factory<T> {
T create(Map<String, Object> props);
}
class Builder<T> {
private Map<String, Object> properties = new LinkedHashMap<>();
<U> Builder<T> set(String key, U value) {
properties.put(key, value);
return this;
}
T build(Factory<T> factory) {
return factory.create(properties);
}
}
Person person = new Builder<Person>()
.set("name", "Alice")
.set("age", 30)
.build(props -> new Person(
(String) props.get("name"),
(Integer) props.get("age")
));
System.out.println(" Person: " + person);
System.out.println("\nCached initialization:");
interface KeyConverter<K> {
String convert(K key);
}
class Cache<T> {
private Map<String, T> cache = new LinkedHashMap<>();
<K> Cache(Map<K, T> initial, KeyConverter<K> converter) {
for (Map.Entry<K, T> entry : initial.entrySet()) {
String key = converter.convert(entry.getKey());
cache.put(key, entry.getValue());
}
}
public T get(String key) {
return cache.get(key);
}
public Set<String> keys() {
return cache.keySet();
}
}
Map<Integer, String> data = new LinkedHashMap<>();
data.put(1, "one");
data.put(2, "two");
data.put(3, "three");
Cache<String> cache = new Cache<>(data, Object::toString);
System.out.println(" Keys: " + cache.keys());
System.out.println(" Value for '1': " + cache.get("1"));
System.out.println("\nMulti-source constructor:");
class Aggregator<T> {
private List<T> all = new ArrayList<>();
@SafeVarargs
<U> Aggregator(Converter<U, T> converter, Collection<U>... sources) {
for (Collection<U> source : sources) {
for (U item : source) {
all.add(converter.convert(item));
}
}
}
public List<T> getAll() {
return all;
}
}
List<String> source1 = Arrays.asList("1", "2", "3");
List<String> source2 = Arrays.asList("4", "5", "6");
Aggregator<Integer> agg = new Aggregator<>(
Integer::parseInt,
source1,
source2
);
System.out.println(" Aggregated: " + agg.getAll());
System.out.println("\nWrapper constructor:");
interface Parser<F, T> {
T parse(F from);
}
class Wrapper<T> {
private T value;
Wrapper(T value) {
this.value = value;
}
<U> Wrapper(U rawValue, Parser<U, T> parser) {
this.value = parser.parse(rawValue);
}
public T getValue() {
return value;
}
}
Wrapper<Integer> w1 = new Wrapper<>(42);
Wrapper<Integer> w2 = new Wrapper<>("123", Integer::parseInt);
System.out.println(" Wrapper 1: " + w1.getValue());
System.out.println(" Wrapper 2: " + w2.getValue());
}
}
import java.util.*;
public class Constructors {
static class Box<T> {
private T content;
// Generic constructor - U independent of T
<U> Box(U initialValue, Converter<U, T> converter) {
this.content = converter.convert(initialValue);
}
public T get() {
return content;
}
}
interface Converter<F, T> {
T convert(F from);
}
public static void main(String[] args) {
System.out.println("Generic constructors:\n");
// Create Box<Integer> from String
String rawBoxValue = ;
Box<Integer> box = new Box<>(rawBoxValue, Integer::parseInt);
System.out.println(" Box content: " + box.get());
// Constructors can have their own type parameters
// Type parameters independent of class type parameters
// Useful for type conversion during construction
// Generic constructors enable flexible initialization
System.out.println("\nCollection initialization:");
class Container<T> {
private List<T> items;
// Non-generic constructor
Container() {
this.items = new ArrayList<>();
}
// Generic constructor
<U> Container(Collection<U> source, Converter<U, T> converter) {
this.items = new ArrayList<>();
for (U item : source) {
items.add(converter.convert(item));
}
}
public List<T> getItems() {
return items;
}
}
List<String> strings = Arrays.asList("1", "2", "3", "4", "5");
Container<Integer> numbers = new Container<>(strings, Integer::parseInt);
System.out.println(" Numbers: " + numbers.getItems());
System.out.println("\nBuilder pattern:");
class Person {
private String name;
private int age;
private Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + " (" + age + ")";
}
}
interface Factory<T> {
T create(Map<String, Object> props);
}
class Builder<T> {
private Map<String, Object> properties = new LinkedHashMap<>();
<U> Builder<T> set(String key, U value) {
properties.put(key, value);
return this;
}
T build(Factory<T> factory) {
return factory.create(properties);
}
}
Person person = new Builder<Person>()
.set("name", "Alice")
.set("age", 30)
.build(props -> new Person(
(String) props.get("name"),
(Integer) props.get("age")
));
System.out.println(" Person: " + person);
System.out.println("\nCached initialization:");
interface KeyConverter<K> {
String convert(K key);
}
class Cache<T> {
private Map<String, T> cache = new LinkedHashMap<>();
<K> Cache(Map<K, T> initial, KeyConverter<K> converter) {
for (Map.Entry<K, T> entry : initial.entrySet()) {
String key = converter.convert(entry.getKey());
cache.put(key, entry.getValue());
}
}
public T get(String key) {
return cache.get(key);
}
public Set<String> keys() {
return cache.keySet();
}
}
Map<Integer, String> data = new LinkedHashMap<>();
data.put(1, "one");
data.put(2, "two");
data.put(3, "three");
Cache<String> cache = new Cache<>(data, Object::toString);
System.out.println(" Keys: " + cache.keys());
System.out.println(" Value for '1': " + cache.get("1"));
System.out.println("\nMulti-source constructor:");
class Aggregator<T> {
private List<T> all = new ArrayList<>();
@SafeVarargs
<U> Aggregator(Converter<U, T> converter, Collection<U>... sources) {
for (Collection<U> source : sources) {
for (U item : source) {
all.add(converter.convert(item));
}
}
}
public List<T> getAll() {
return all;
}
}
List<String> source1 = Arrays.asList("1", "2", "3");
List<String> source2 = Arrays.asList("4", "5", "6");
Aggregator<Integer> agg = new Aggregator<>(
Integer::parseInt,
source1,
source2
);
System.out.println(" Aggregated: " + agg.getAll());
System.out.println("\nWrapper constructor:");
interface Parser<F, T> {
T parse(F from);
}
class Wrapper<T> {
private T value;
Wrapper(T value) {
this.value = value;
}
<U> Wrapper(U rawValue, Parser<U, T> parser) {
this.value = parser.parse(rawValue);
}
public T getValue() {
return value;
}
}
Wrapper<Integer> w1 = new Wrapper<>(42);
Wrapper<Integer> w2 = new Wrapper<>("123", Integer::parseInt);
System.out.println(" Wrapper 1: " + w1.getValue());
System.out.println(" Wrapper 2: " + w2.getValue());
}
}
<U> MyClass(U value) - U separate from class type parameter.
Exercise: Practical.java
Build utility methods using generic methods