Generics
Type Bounds
Constraining Generics
Your generic method needs to call compareTo(). But T could be anything - the
compiler won't allow it. With <T extends Comparable<T>>, you guarantee T has
compareTo(), and the compiler lets you use it.
Upper bounds with extends
Restrict T to a type or its subtypes.
public class UpperBounds {
static class NumberBox<T extends Number> {
private T value;
public NumberBox(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public double getDoubleValue() {
return value.doubleValue(); // Can call Number methods
}
public boolean isPositive() {
return value.doubleValue() > 0;
}
}
public static void main(String[] args) {
System.out.println("Upper bounds:\n");
NumberBox<Integer> intBox = new NumberBox<>(42);
System.out.println(" Integer: " + intBox.getValue());
System.out.println(" As double: " + intBox.getDoubleValue());
System.out.println(" Positive: " + intBox.isPositive());
NumberBox<Double> doubleBox = new NumberBox<>(3.14);
System.out.println(" Double: " + doubleBox.getValue());
System.out.println(" Positive: " + doubleBox.isPositive());
// NumberBox<String> strBox = new NumberBox<>("text"); // Compile error
// <T extends Type> means T must be Type or subclass/implementer
// Enables calling methods from the bounded type
// Number bound allows: intValue(), doubleValue(), etc.
// Compile error if type doesn't satisfy bound
System.out.println("\nComparable bound:");
class Sorter<T extends Comparable<T>> {
public T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
public T min(T a, T b) {
return a.compareTo(b) < 0 ? a : b;
}
}
Sorter<Integer> intSorter = new Sorter<>();
System.out.println(" Max(5, 10): " + intSorter.max(5, 10));
System.out.println(" Min(5, 10): " + intSorter.min(5, 10));
Sorter<String> strSorter = new Sorter<>();
System.out.println(" Max(apple, banana): " + strSorter.max("apple", "banana"));
System.out.println("\nStatistics:");
class Stats<T extends Number> {
private java.util.List<T> numbers;
public Stats(java.util.List<T> numbers) {
this.numbers = numbers;
}
public double average() {
double sum = 0;
for (T num : numbers) {
sum += num.doubleValue();
}
return sum / numbers.size();
}
public double sum() {
double total = 0;
for (T num : numbers) {
total += num.doubleValue();
}
return total;
}
}
Stats<Integer> intStats = new Stats<>(java.util.Arrays.asList(10, 20, 30, 40));
System.out.println(" Average: " + intStats.average());
System.out.println(" Sum: " + intStats.sum());
System.out.println("\nRange:");
class Range<T extends Comparable<T>> {
private T min;
private T max;
public Range(T min, T max) {
this.min = min;
this.max = max;
}
public boolean contains(T value) {
return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
}
public String toString() {
return "[" + min + " - " + max + "]";
}
}
Range<Integer> ageRange = new Range<>(18, 65);
System.out.println(" Range: " + ageRange);
int ageToCheck = ;
System.out.println(" Contains " + ageToCheck + ": " +
ageRange.contains(ageToCheck));
System.out.println(" Contains 70: " + ageRange.contains(70));
Range<String> nameRange = new Range<>("A", "M");
System.out.println(" Name range: " + nameRange);
System.out.println(" Contains Alice: " + nameRange.contains("Alice"));
System.out.println(" Contains Steve: " + nameRange.contains("Steve"));
}
}
public class UpperBounds {
static class NumberBox<T extends Number> {
private T value;
public NumberBox(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public double getDoubleValue() {
return value.doubleValue(); // Can call Number methods
}
public boolean isPositive() {
return value.doubleValue() > 0;
}
}
public static void main(String[] args) {
System.out.println("Upper bounds:\n");
NumberBox<Integer> intBox = new NumberBox<>(42);
System.out.println(" Integer: " + intBox.getValue());
System.out.println(" As double: " + intBox.getDoubleValue());
System.out.println(" Positive: " + intBox.isPositive());
NumberBox<Double> doubleBox = new NumberBox<>(3.14);
System.out.println(" Double: " + doubleBox.getValue());
System.out.println(" Positive: " + doubleBox.isPositive());
// NumberBox<String> strBox = new NumberBox<>("text"); // Compile error
// <T extends Type> means T must be Type or subclass/implementer
// Enables calling methods from the bounded type
// Number bound allows: intValue(), doubleValue(), etc.
// Compile error if type doesn't satisfy bound
System.out.println("\nComparable bound:");
class Sorter<T extends Comparable<T>> {
public T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
public T min(T a, T b) {
return a.compareTo(b) < 0 ? a : b;
}
}
Sorter<Integer> intSorter = new Sorter<>();
System.out.println(" Max(5, 10): " + intSorter.max(5, 10));
System.out.println(" Min(5, 10): " + intSorter.min(5, 10));
Sorter<String> strSorter = new Sorter<>();
System.out.println(" Max(apple, banana): " + strSorter.max("apple", "banana"));
System.out.println("\nStatistics:");
class Stats<T extends Number> {
private java.util.List<T> numbers;
public Stats(java.util.List<T> numbers) {
this.numbers = numbers;
}
public double average() {
double sum = 0;
for (T num : numbers) {
sum += num.doubleValue();
}
return sum / numbers.size();
}
public double sum() {
double total = 0;
for (T num : numbers) {
total += num.doubleValue();
}
return total;
}
}
Stats<Integer> intStats = new Stats<>(java.util.Arrays.asList(10, 20, 30, 40));
System.out.println(" Average: " + intStats.average());
System.out.println(" Sum: " + intStats.sum());
System.out.println("\nRange:");
class Range<T extends Comparable<T>> {
private T min;
private T max;
public Range(T min, T max) {
this.min = min;
this.max = max;
}
public boolean contains(T value) {
return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
}
public String toString() {
return "[" + min + " - " + max + "]";
}
}
Range<Integer> ageRange = new Range<>(18, 65);
System.out.println(" Range: " + ageRange);
int ageToCheck = ;
System.out.println(" Contains " + ageToCheck + ": " +
ageRange.contains(ageToCheck));
System.out.println(" Contains 70: " + ageRange.contains(70));
Range<String> nameRange = new Range<>("A", "M");
System.out.println(" Name range: " + nameRange);
System.out.println(" Contains Alice: " + nameRange.contains("Alice"));
System.out.println(" Contains Steve: " + nameRange.contains("Steve"));
}
}
public class UpperBounds {
static class NumberBox<T extends Number> {
private T value;
public NumberBox(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public double getDoubleValue() {
return value.doubleValue(); // Can call Number methods
}
public boolean isPositive() {
return value.doubleValue() > 0;
}
}
public static void main(String[] args) {
System.out.println("Upper bounds:\n");
NumberBox<Integer> intBox = new NumberBox<>(42);
System.out.println(" Integer: " + intBox.getValue());
System.out.println(" As double: " + intBox.getDoubleValue());
System.out.println(" Positive: " + intBox.isPositive());
NumberBox<Double> doubleBox = new NumberBox<>(3.14);
System.out.println(" Double: " + doubleBox.getValue());
System.out.println(" Positive: " + doubleBox.isPositive());
// NumberBox<String> strBox = new NumberBox<>("text"); // Compile error
// <T extends Type> means T must be Type or subclass/implementer
// Enables calling methods from the bounded type
// Number bound allows: intValue(), doubleValue(), etc.
// Compile error if type doesn't satisfy bound
System.out.println("\nComparable bound:");
class Sorter<T extends Comparable<T>> {
public T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
public T min(T a, T b) {
return a.compareTo(b) < 0 ? a : b;
}
}
Sorter<Integer> intSorter = new Sorter<>();
System.out.println(" Max(5, 10): " + intSorter.max(5, 10));
System.out.println(" Min(5, 10): " + intSorter.min(5, 10));
Sorter<String> strSorter = new Sorter<>();
System.out.println(" Max(apple, banana): " + strSorter.max("apple", "banana"));
System.out.println("\nStatistics:");
class Stats<T extends Number> {
private java.util.List<T> numbers;
public Stats(java.util.List<T> numbers) {
this.numbers = numbers;
}
public double average() {
double sum = 0;
for (T num : numbers) {
sum += num.doubleValue();
}
return sum / numbers.size();
}
public double sum() {
double total = 0;
for (T num : numbers) {
total += num.doubleValue();
}
return total;
}
}
Stats<Integer> intStats = new Stats<>(java.util.Arrays.asList(10, 20, 30, 40));
System.out.println(" Average: " + intStats.average());
System.out.println(" Sum: " + intStats.sum());
System.out.println("\nRange:");
class Range<T extends Comparable<T>> {
private T min;
private T max;
public Range(T min, T max) {
this.min = min;
this.max = max;
}
public boolean contains(T value) {
return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
}
public String toString() {
return "[" + min + " - " + max + "]";
}
}
Range<Integer> ageRange = new Range<>(18, 65);
System.out.println(" Range: " + ageRange);
int ageToCheck = ;
System.out.println(" Contains " + ageToCheck + ": " +
ageRange.contains(ageToCheck));
System.out.println(" Contains 70: " + ageRange.contains(70));
Range<String> nameRange = new Range<>("A", "M");
System.out.println(" Name range: " + nameRange);
System.out.println(" Contains Alice: " + nameRange.contains("Alice"));
System.out.println(" Contains Steve: " + nameRange.contains("Steve"));
}
}
<T extends Number> - T must be Number or subclass. Can call Number methods.
Multiple bounds
Combine class and interface constraints.
import java.io.Serializable;
public class MultipleBounds {
static class Calculator<T extends Number & Comparable<T>> {
public T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
public double add(T a, T b) {
return a.doubleValue() + b.doubleValue();
}
public boolean inRange(T value, T min, T max) {
return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
}
}
public static void main(String[] args) {
System.out.println("Multiple bounds:\n");
Calculator<Integer> intCalc = new Calculator<>();
System.out.println(" Max(5, 10): " + intCalc.max(5, 10));
System.out.println(" Add(5, 10): " + intCalc.add(5, 10));
int rangeValue = ;
System.out.println(" InRange(" + rangeValue + ", 5, 10): " +
intCalc.inRange(rangeValue, 5, 10));
Calculator<Double> doubleCalc = new Calculator<>();
System.out.println(" Max(3.14, 2.71): " + doubleCalc.max(3.14, 2.71));
// Multiple bounds: <T extends Type1 & Type2 & Type3>
// Class bound must come first (can only have one class)
// Interface bounds follow, separated by &
// T must satisfy ALL bounds
System.out.println("\nSerializable + Comparable:");
class Storage<T extends Serializable & Comparable<T>> {
private T value;
public void store(T value) {
this.value = value;
System.out.println(" Stored (serializable): " + value);
}
public T retrieve() {
return value;
}
public boolean isGreaterThan(T other) {
return value != null && value.compareTo(other) > 0;
}
}
Storage<String> strStorage = new Storage<>();
strStorage.store("Hello");
System.out.println(" Greater than 'Apple': " +
strStorage.isGreaterThan("Apple"));
Storage<Integer> intStorage = new Storage<>();
intStorage.store(42);
System.out.println(" Greater than 30: " +
intStorage.isGreaterThan(30));
System.out.println("\nCustom interfaces:");
interface Nameable {
String getName();
}
interface Identifiable {
long getId();
}
class Entity<T extends Nameable & Identifiable> {
private T item;
public Entity(T item) {
this.item = item;
}
public void display() {
System.out.println(" ID: " + item.getId() +
", Name: " + item.getName());
}
}
class User implements Nameable, Identifiable {
private long id;
private String name;
User(long id, String name) {
this.id = id;
this.name = name;
}
@Override
public String getName() { return name; }
@Override
public long getId() { return id; }
}
Entity<User> userEntity = new Entity<>(new User(1, "Alice"));
userEntity.display();
System.out.println("\nThree bounds:");
interface Readable {
String read();
}
interface Writable {
void write(String data);
}
interface Closeable {
void close();
}
class Resource<T extends Readable & Writable & Closeable> {
private T resource;
public Resource(T resource) {
this.resource = resource;
}
public void process() {
String data = resource.read();
System.out.println(" Read: " + data);
resource.write("Processed: " + data);
resource.close();
}
}
class File implements Readable, Writable, Closeable {
private String content = "File content";
@Override
public String read() { return content; }
@Override
public void write(String data) {
System.out.println(" Writing: " + data);
}
@Override
public void close() {
System.out.println(" Closed");
}
}
Resource<File> fileResource = new Resource<>(new File());
fileResource.process();
}
}
import java.io.Serializable;
public class MultipleBounds {
static class Calculator<T extends Number & Comparable<T>> {
public T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
public double add(T a, T b) {
return a.doubleValue() + b.doubleValue();
}
public boolean inRange(T value, T min, T max) {
return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
}
}
public static void main(String[] args) {
System.out.println("Multiple bounds:\n");
Calculator<Integer> intCalc = new Calculator<>();
System.out.println(" Max(5, 10): " + intCalc.max(5, 10));
System.out.println(" Add(5, 10): " + intCalc.add(5, 10));
int rangeValue = ;
System.out.println(" InRange(" + rangeValue + ", 5, 10): " +
intCalc.inRange(rangeValue, 5, 10));
Calculator<Double> doubleCalc = new Calculator<>();
System.out.println(" Max(3.14, 2.71): " + doubleCalc.max(3.14, 2.71));
// Multiple bounds: <T extends Type1 & Type2 & Type3>
// Class bound must come first (can only have one class)
// Interface bounds follow, separated by &
// T must satisfy ALL bounds
System.out.println("\nSerializable + Comparable:");
class Storage<T extends Serializable & Comparable<T>> {
private T value;
public void store(T value) {
this.value = value;
System.out.println(" Stored (serializable): " + value);
}
public T retrieve() {
return value;
}
public boolean isGreaterThan(T other) {
return value != null && value.compareTo(other) > 0;
}
}
Storage<String> strStorage = new Storage<>();
strStorage.store("Hello");
System.out.println(" Greater than 'Apple': " +
strStorage.isGreaterThan("Apple"));
Storage<Integer> intStorage = new Storage<>();
intStorage.store(42);
System.out.println(" Greater than 30: " +
intStorage.isGreaterThan(30));
System.out.println("\nCustom interfaces:");
interface Nameable {
String getName();
}
interface Identifiable {
long getId();
}
class Entity<T extends Nameable & Identifiable> {
private T item;
public Entity(T item) {
this.item = item;
}
public void display() {
System.out.println(" ID: " + item.getId() +
", Name: " + item.getName());
}
}
class User implements Nameable, Identifiable {
private long id;
private String name;
User(long id, String name) {
this.id = id;
this.name = name;
}
@Override
public String getName() { return name; }
@Override
public long getId() { return id; }
}
Entity<User> userEntity = new Entity<>(new User(1, "Alice"));
userEntity.display();
System.out.println("\nThree bounds:");
interface Readable {
String read();
}
interface Writable {
void write(String data);
}
interface Closeable {
void close();
}
class Resource<T extends Readable & Writable & Closeable> {
private T resource;
public Resource(T resource) {
this.resource = resource;
}
public void process() {
String data = resource.read();
System.out.println(" Read: " + data);
resource.write("Processed: " + data);
resource.close();
}
}
class File implements Readable, Writable, Closeable {
private String content = "File content";
@Override
public String read() { return content; }
@Override
public void write(String data) {
System.out.println(" Writing: " + data);
}
@Override
public void close() {
System.out.println(" Closed");
}
}
Resource<File> fileResource = new Resource<>(new File());
fileResource.process();
}
}
import java.io.Serializable;
public class MultipleBounds {
static class Calculator<T extends Number & Comparable<T>> {
public T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
public double add(T a, T b) {
return a.doubleValue() + b.doubleValue();
}
public boolean inRange(T value, T min, T max) {
return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
}
}
public static void main(String[] args) {
System.out.println("Multiple bounds:\n");
Calculator<Integer> intCalc = new Calculator<>();
System.out.println(" Max(5, 10): " + intCalc.max(5, 10));
System.out.println(" Add(5, 10): " + intCalc.add(5, 10));
int rangeValue = ;
System.out.println(" InRange(" + rangeValue + ", 5, 10): " +
intCalc.inRange(rangeValue, 5, 10));
Calculator<Double> doubleCalc = new Calculator<>();
System.out.println(" Max(3.14, 2.71): " + doubleCalc.max(3.14, 2.71));
// Multiple bounds: <T extends Type1 & Type2 & Type3>
// Class bound must come first (can only have one class)
// Interface bounds follow, separated by &
// T must satisfy ALL bounds
System.out.println("\nSerializable + Comparable:");
class Storage<T extends Serializable & Comparable<T>> {
private T value;
public void store(T value) {
this.value = value;
System.out.println(" Stored (serializable): " + value);
}
public T retrieve() {
return value;
}
public boolean isGreaterThan(T other) {
return value != null && value.compareTo(other) > 0;
}
}
Storage<String> strStorage = new Storage<>();
strStorage.store("Hello");
System.out.println(" Greater than 'Apple': " +
strStorage.isGreaterThan("Apple"));
Storage<Integer> intStorage = new Storage<>();
intStorage.store(42);
System.out.println(" Greater than 30: " +
intStorage.isGreaterThan(30));
System.out.println("\nCustom interfaces:");
interface Nameable {
String getName();
}
interface Identifiable {
long getId();
}
class Entity<T extends Nameable & Identifiable> {
private T item;
public Entity(T item) {
this.item = item;
}
public void display() {
System.out.println(" ID: " + item.getId() +
", Name: " + item.getName());
}
}
class User implements Nameable, Identifiable {
private long id;
private String name;
User(long id, String name) {
this.id = id;
this.name = name;
}
@Override
public String getName() { return name; }
@Override
public long getId() { return id; }
}
Entity<User> userEntity = new Entity<>(new User(1, "Alice"));
userEntity.display();
System.out.println("\nThree bounds:");
interface Readable {
String read();
}
interface Writable {
void write(String data);
}
interface Closeable {
void close();
}
class Resource<T extends Readable & Writable & Closeable> {
private T resource;
public Resource(T resource) {
this.resource = resource;
}
public void process() {
String data = resource.read();
System.out.println(" Read: " + data);
resource.write("Processed: " + data);
resource.close();
}
}
class File implements Readable, Writable, Closeable {
private String content = "File content";
@Override
public String read() { return content; }
@Override
public void write(String data) {
System.out.println(" Writing: " + data);
}
@Override
public void close() {
System.out.println(" Closed");
}
}
Resource<File> fileResource = new Resource<>(new File());
fileResource.process();
}
}
<T extends Number & Comparable<T>> - class first, then interfaces with &.
Bounded methods
Apply bounds to method-level type parameters.
import java.util.*;
public class BoundedMethods {
public static <T extends Comparable<T>> T findMax(List<T> list) {
if (list.isEmpty()) {
return null;
}
T max = list.get(0);
for (T item : list) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
public static void main(String[] args) {
System.out.println("Bounded methods:\n");
List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 7);
Integer maxNum = findMax(numbers);
System.out.println(" Max number: " + maxNum);
List<String> words = Arrays.asList("apple", "zebra", "banana");
String maxWord = findMax(words);
System.out.println(" Max word: " + maxWord);
// Method can declare its own type parameters with bounds
// <T extends Type> before return type
// Bound applies only to that method
// Enables calling bounded type's methods
System.out.println("\nNumeric operations:");
class MathUtils {
public static <T extends Number> double sum(List<T> numbers) {
double total = 0;
for (T num : numbers) {
total += num.doubleValue();
}
return total;
}
public static <T extends Number> double average(List<T> numbers) {
return sum(numbers) / numbers.size();
}
}
List<Integer> intList = Arrays.asList(10, 20, 30);
System.out.println(" Sum: " + MathUtils.sum(intList));
System.out.println(" Average: " + MathUtils.average(intList));
List<Double> doubleList = Arrays.asList(1.5, 2.5, 3.5);
System.out.println(" Sum: " + MathUtils.sum(doubleList));
System.out.println("\nCount matching:");
class Counter {
public static <T> int countMatching(List<T> list, T target) {
int count = 0;
for (T item : list) {
if (item.equals(target)) {
count++;
}
}
return count;
}
public static <T extends Comparable<T>> int countGreater(
List<T> list, T threshold) {
int count = 0;
for (T item : list) {
if (item.compareTo(threshold) > 0) {
count++;
}
}
return count;
}
}
List<Integer> nums = Arrays.asList(5, 10, 3, 10, 7);
System.out.println(" Count 10s: " + Counter.countMatching(nums, 10));
System.out.println(" Count > 5: " + Counter.countGreater(nums, 5));
System.out.println("\nCopy method:");
class Copier {
public static <T> void copy(List<T> source, List<T> dest) {
dest.clear();
dest.addAll(source);
}
public static <T extends Comparable<T>> void copyGreater(
List<T> source, List<T> dest, T threshold) {
dest.clear();
for (T item : source) {
if (item.compareTo(threshold) > 0) {
dest.add(item);
}
}
}
}
List<Integer> src = Arrays.asList(1, 5, 3, 8, 2, 9);
List<Integer> dst = new ArrayList<>();
int threshold = ;
Copier.copyGreater(src, dst, threshold);
System.out.println(" Copied > " + threshold + ": " + dst);
System.out.println("\nMin/Max finder:");
class MinMax {
public static <T extends Comparable<T>> T min(T a, T b) {
return a.compareTo(b) < 0 ? a : b;
}
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 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(" min(5, 10): " + MinMax.min(5, 10));
System.out.println(" max(5, 10): " + MinMax.max(5, 10));
System.out.println(" clamp(15, 0, 10): " + MinMax.clamp(15, 0, 10));
System.out.println(" clamp(-5, 0, 10): " + MinMax.clamp(-5, 0, 10));
}
}
import java.util.*;
public class BoundedMethods {
public static <T extends Comparable<T>> T findMax(List<T> list) {
if (list.isEmpty()) {
return null;
}
T max = list.get(0);
for (T item : list) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
public static void main(String[] args) {
System.out.println("Bounded methods:\n");
List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 7);
Integer maxNum = findMax(numbers);
System.out.println(" Max number: " + maxNum);
List<String> words = Arrays.asList("apple", "zebra", "banana");
String maxWord = findMax(words);
System.out.println(" Max word: " + maxWord);
// Method can declare its own type parameters with bounds
// <T extends Type> before return type
// Bound applies only to that method
// Enables calling bounded type's methods
System.out.println("\nNumeric operations:");
class MathUtils {
public static <T extends Number> double sum(List<T> numbers) {
double total = 0;
for (T num : numbers) {
total += num.doubleValue();
}
return total;
}
public static <T extends Number> double average(List<T> numbers) {
return sum(numbers) / numbers.size();
}
}
List<Integer> intList = Arrays.asList(10, 20, 30);
System.out.println(" Sum: " + MathUtils.sum(intList));
System.out.println(" Average: " + MathUtils.average(intList));
List<Double> doubleList = Arrays.asList(1.5, 2.5, 3.5);
System.out.println(" Sum: " + MathUtils.sum(doubleList));
System.out.println("\nCount matching:");
class Counter {
public static <T> int countMatching(List<T> list, T target) {
int count = 0;
for (T item : list) {
if (item.equals(target)) {
count++;
}
}
return count;
}
public static <T extends Comparable<T>> int countGreater(
List<T> list, T threshold) {
int count = 0;
for (T item : list) {
if (item.compareTo(threshold) > 0) {
count++;
}
}
return count;
}
}
List<Integer> nums = Arrays.asList(5, 10, 3, 10, 7);
System.out.println(" Count 10s: " + Counter.countMatching(nums, 10));
System.out.println(" Count > 5: " + Counter.countGreater(nums, 5));
System.out.println("\nCopy method:");
class Copier {
public static <T> void copy(List<T> source, List<T> dest) {
dest.clear();
dest.addAll(source);
}
public static <T extends Comparable<T>> void copyGreater(
List<T> source, List<T> dest, T threshold) {
dest.clear();
for (T item : source) {
if (item.compareTo(threshold) > 0) {
dest.add(item);
}
}
}
}
List<Integer> src = Arrays.asList(1, 5, 3, 8, 2, 9);
List<Integer> dst = new ArrayList<>();
int threshold = ;
Copier.copyGreater(src, dst, threshold);
System.out.println(" Copied > " + threshold + ": " + dst);
System.out.println("\nMin/Max finder:");
class MinMax {
public static <T extends Comparable<T>> T min(T a, T b) {
return a.compareTo(b) < 0 ? a : b;
}
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 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(" min(5, 10): " + MinMax.min(5, 10));
System.out.println(" max(5, 10): " + MinMax.max(5, 10));
System.out.println(" clamp(15, 0, 10): " + MinMax.clamp(15, 0, 10));
System.out.println(" clamp(-5, 0, 10): " + MinMax.clamp(-5, 0, 10));
}
}
import java.util.*;
public class BoundedMethods {
public static <T extends Comparable<T>> T findMax(List<T> list) {
if (list.isEmpty()) {
return null;
}
T max = list.get(0);
for (T item : list) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
public static void main(String[] args) {
System.out.println("Bounded methods:\n");
List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 7);
Integer maxNum = findMax(numbers);
System.out.println(" Max number: " + maxNum);
List<String> words = Arrays.asList("apple", "zebra", "banana");
String maxWord = findMax(words);
System.out.println(" Max word: " + maxWord);
// Method can declare its own type parameters with bounds
// <T extends Type> before return type
// Bound applies only to that method
// Enables calling bounded type's methods
System.out.println("\nNumeric operations:");
class MathUtils {
public static <T extends Number> double sum(List<T> numbers) {
double total = 0;
for (T num : numbers) {
total += num.doubleValue();
}
return total;
}
public static <T extends Number> double average(List<T> numbers) {
return sum(numbers) / numbers.size();
}
}
List<Integer> intList = Arrays.asList(10, 20, 30);
System.out.println(" Sum: " + MathUtils.sum(intList));
System.out.println(" Average: " + MathUtils.average(intList));
List<Double> doubleList = Arrays.asList(1.5, 2.5, 3.5);
System.out.println(" Sum: " + MathUtils.sum(doubleList));
System.out.println("\nCount matching:");
class Counter {
public static <T> int countMatching(List<T> list, T target) {
int count = 0;
for (T item : list) {
if (item.equals(target)) {
count++;
}
}
return count;
}
public static <T extends Comparable<T>> int countGreater(
List<T> list, T threshold) {
int count = 0;
for (T item : list) {
if (item.compareTo(threshold) > 0) {
count++;
}
}
return count;
}
}
List<Integer> nums = Arrays.asList(5, 10, 3, 10, 7);
System.out.println(" Count 10s: " + Counter.countMatching(nums, 10));
System.out.println(" Count > 5: " + Counter.countGreater(nums, 5));
System.out.println("\nCopy method:");
class Copier {
public static <T> void copy(List<T> source, List<T> dest) {
dest.clear();
dest.addAll(source);
}
public static <T extends Comparable<T>> void copyGreater(
List<T> source, List<T> dest, T threshold) {
dest.clear();
for (T item : source) {
if (item.compareTo(threshold) > 0) {
dest.add(item);
}
}
}
}
List<Integer> src = Arrays.asList(1, 5, 3, 8, 2, 9);
List<Integer> dst = new ArrayList<>();
int threshold = ;
Copier.copyGreater(src, dst, threshold);
System.out.println(" Copied > " + threshold + ": " + dst);
System.out.println("\nMin/Max finder:");
class MinMax {
public static <T extends Comparable<T>> T min(T a, T b) {
return a.compareTo(b) < 0 ? a : b;
}
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 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(" min(5, 10): " + MinMax.min(5, 10));
System.out.println(" max(5, 10): " + MinMax.max(5, 10));
System.out.println(" clamp(15, 0, 10): " + MinMax.clamp(15, 0, 10));
System.out.println(" clamp(-5, 0, 10): " + MinMax.clamp(-5, 0, 10));
}
}
<T extends Comparable<T>> T max(T a, T b) - can compare a and b.
Recursive bounds
Type parameter references itself in bound.
import java.util.*;
public class RecursiveBounds {
static class SortedList<T extends Comparable<T>> {
private List<T> items = new ArrayList<>();
public void add(T item) {
items.add(item);
Collections.sort(items);
System.out.println(" Added and sorted: " + item);
}
public T getMin() {
return items.isEmpty() ? null : items.get(0);
}
public T getMax() {
return items.isEmpty() ? null : items.get(items.size() - 1);
}
public List<T> getAll() {
return new ArrayList<>(items);
}
}
public static void main(String[] args) {
System.out.println("Recursive type bounds:\n");
SortedList<Integer> numbers = new SortedList<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
System.out.println(" Min: " + numbers.getMin());
System.out.println(" Max: " + numbers.getMax());
System.out.println(" All: " + numbers.getAll());
// <T extends Comparable<T>> is a recursive bound
// T must be comparable to itself
// Common pattern for sortable types
// Ensures type-safe comparison
System.out.println("\nWith enums:");
enum Priority implements Comparable<Priority> {
LOW, MEDIUM, HIGH
}
SortedList<Priority> priorities = new SortedList<>();
priorities.add(Priority.HIGH);
priorities.add(Priority.LOW);
priorities.add(Priority.MEDIUM);
System.out.println(" Sorted: " + priorities.getAll());
System.out.println("\nCustom comparable:");
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 + ")";
}
}
SortedList<Person> people = new SortedList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
System.out.println(" By age: " + people.getAll());
System.out.println("\nMax finder:");
class MaxFinder {
public static <T extends Comparable<T>> T max(T a, T b, T c) {
T temp = a.compareTo(b) > 0 ? a : b;
return temp.compareTo(c) > 0 ? temp : c;
}
public static <T extends Comparable<T>> T maxOf(List<T> items) {
if (items.isEmpty()) {
return null;
}
T max = items.get(0);
for (T item : items) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
}
Integer maxNum = MaxFinder.max(5, 10, 3);
System.out.println(" Max(5, 10, 3): " + maxNum);
String maxStr = MaxFinder.max("apple", "zebra", "banana");
System.out.println(" Max strings: " + maxStr);
List<Double> doubles = Arrays.asList(3.14, 2.71, 1.41, 1.73);
Double maxDouble = MaxFinder.maxOf(doubles);
System.out.println(" Max double: " + maxDouble);
System.out.println("\nBST node:");
class TreeNode<T extends Comparable<T>> {
T value;
TreeNode<T> left;
TreeNode<T> right;
TreeNode(T value) {
this.value = value;
}
public void insert(T newValue) {
if (newValue.compareTo(value) < 0) {
if (left == null) {
left = new TreeNode<>(newValue);
System.out.println(" Inserted left: " + newValue);
} else {
left.insert(newValue);
}
} else {
if (right == null) {
right = new TreeNode<>(newValue);
System.out.println(" Inserted right: " + newValue);
} else {
right.insert(newValue);
}
}
}
public boolean contains(T target) {
if (value.equals(target)) {
return true;
} else if (target.compareTo(value) < 0) {
return left != null && left.contains(target);
} else {
return right != null && right.contains(target);
}
}
}
TreeNode<Integer> root = new TreeNode<>(50);
root.insert(30);
root.insert(70);
root.insert(20);
root.insert(40);
int searchTarget = ;
System.out.println(" Contains " + searchTarget + ": " +
root.contains(searchTarget));
System.out.println(" Contains 60: " + root.contains(60));
}
}
import java.util.*;
public class RecursiveBounds {
static class SortedList<T extends Comparable<T>> {
private List<T> items = new ArrayList<>();
public void add(T item) {
items.add(item);
Collections.sort(items);
System.out.println(" Added and sorted: " + item);
}
public T getMin() {
return items.isEmpty() ? null : items.get(0);
}
public T getMax() {
return items.isEmpty() ? null : items.get(items.size() - 1);
}
public List<T> getAll() {
return new ArrayList<>(items);
}
}
public static void main(String[] args) {
System.out.println("Recursive type bounds:\n");
SortedList<Integer> numbers = new SortedList<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
System.out.println(" Min: " + numbers.getMin());
System.out.println(" Max: " + numbers.getMax());
System.out.println(" All: " + numbers.getAll());
// <T extends Comparable<T>> is a recursive bound
// T must be comparable to itself
// Common pattern for sortable types
// Ensures type-safe comparison
System.out.println("\nWith enums:");
enum Priority implements Comparable<Priority> {
LOW, MEDIUM, HIGH
}
SortedList<Priority> priorities = new SortedList<>();
priorities.add(Priority.HIGH);
priorities.add(Priority.LOW);
priorities.add(Priority.MEDIUM);
System.out.println(" Sorted: " + priorities.getAll());
System.out.println("\nCustom comparable:");
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 + ")";
}
}
SortedList<Person> people = new SortedList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
System.out.println(" By age: " + people.getAll());
System.out.println("\nMax finder:");
class MaxFinder {
public static <T extends Comparable<T>> T max(T a, T b, T c) {
T temp = a.compareTo(b) > 0 ? a : b;
return temp.compareTo(c) > 0 ? temp : c;
}
public static <T extends Comparable<T>> T maxOf(List<T> items) {
if (items.isEmpty()) {
return null;
}
T max = items.get(0);
for (T item : items) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
}
Integer maxNum = MaxFinder.max(5, 10, 3);
System.out.println(" Max(5, 10, 3): " + maxNum);
String maxStr = MaxFinder.max("apple", "zebra", "banana");
System.out.println(" Max strings: " + maxStr);
List<Double> doubles = Arrays.asList(3.14, 2.71, 1.41, 1.73);
Double maxDouble = MaxFinder.maxOf(doubles);
System.out.println(" Max double: " + maxDouble);
System.out.println("\nBST node:");
class TreeNode<T extends Comparable<T>> {
T value;
TreeNode<T> left;
TreeNode<T> right;
TreeNode(T value) {
this.value = value;
}
public void insert(T newValue) {
if (newValue.compareTo(value) < 0) {
if (left == null) {
left = new TreeNode<>(newValue);
System.out.println(" Inserted left: " + newValue);
} else {
left.insert(newValue);
}
} else {
if (right == null) {
right = new TreeNode<>(newValue);
System.out.println(" Inserted right: " + newValue);
} else {
right.insert(newValue);
}
}
}
public boolean contains(T target) {
if (value.equals(target)) {
return true;
} else if (target.compareTo(value) < 0) {
return left != null && left.contains(target);
} else {
return right != null && right.contains(target);
}
}
}
TreeNode<Integer> root = new TreeNode<>(50);
root.insert(30);
root.insert(70);
root.insert(20);
root.insert(40);
int searchTarget = ;
System.out.println(" Contains " + searchTarget + ": " +
root.contains(searchTarget));
System.out.println(" Contains 60: " + root.contains(60));
}
}
import java.util.*;
public class RecursiveBounds {
static class SortedList<T extends Comparable<T>> {
private List<T> items = new ArrayList<>();
public void add(T item) {
items.add(item);
Collections.sort(items);
System.out.println(" Added and sorted: " + item);
}
public T getMin() {
return items.isEmpty() ? null : items.get(0);
}
public T getMax() {
return items.isEmpty() ? null : items.get(items.size() - 1);
}
public List<T> getAll() {
return new ArrayList<>(items);
}
}
public static void main(String[] args) {
System.out.println("Recursive type bounds:\n");
SortedList<Integer> numbers = new SortedList<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
System.out.println(" Min: " + numbers.getMin());
System.out.println(" Max: " + numbers.getMax());
System.out.println(" All: " + numbers.getAll());
// <T extends Comparable<T>> is a recursive bound
// T must be comparable to itself
// Common pattern for sortable types
// Ensures type-safe comparison
System.out.println("\nWith enums:");
enum Priority implements Comparable<Priority> {
LOW, MEDIUM, HIGH
}
SortedList<Priority> priorities = new SortedList<>();
priorities.add(Priority.HIGH);
priorities.add(Priority.LOW);
priorities.add(Priority.MEDIUM);
System.out.println(" Sorted: " + priorities.getAll());
System.out.println("\nCustom comparable:");
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 + ")";
}
}
SortedList<Person> people = new SortedList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
System.out.println(" By age: " + people.getAll());
System.out.println("\nMax finder:");
class MaxFinder {
public static <T extends Comparable<T>> T max(T a, T b, T c) {
T temp = a.compareTo(b) > 0 ? a : b;
return temp.compareTo(c) > 0 ? temp : c;
}
public static <T extends Comparable<T>> T maxOf(List<T> items) {
if (items.isEmpty()) {
return null;
}
T max = items.get(0);
for (T item : items) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
}
Integer maxNum = MaxFinder.max(5, 10, 3);
System.out.println(" Max(5, 10, 3): " + maxNum);
String maxStr = MaxFinder.max("apple", "zebra", "banana");
System.out.println(" Max strings: " + maxStr);
List<Double> doubles = Arrays.asList(3.14, 2.71, 1.41, 1.73);
Double maxDouble = MaxFinder.maxOf(doubles);
System.out.println(" Max double: " + maxDouble);
System.out.println("\nBST node:");
class TreeNode<T extends Comparable<T>> {
T value;
TreeNode<T> left;
TreeNode<T> right;
TreeNode(T value) {
this.value = value;
}
public void insert(T newValue) {
if (newValue.compareTo(value) < 0) {
if (left == null) {
left = new TreeNode<>(newValue);
System.out.println(" Inserted left: " + newValue);
} else {
left.insert(newValue);
}
} else {
if (right == null) {
right = new TreeNode<>(newValue);
System.out.println(" Inserted right: " + newValue);
} else {
right.insert(newValue);
}
}
}
public boolean contains(T target) {
if (value.equals(target)) {
return true;
} else if (target.compareTo(value) < 0) {
return left != null && left.contains(target);
} else {
return right != null && right.contains(target);
}
}
}
TreeNode<Integer> root = new TreeNode<>(50);
root.insert(30);
root.insert(70);
root.insert(20);
root.insert(40);
int searchTarget = ;
System.out.println(" Contains " + searchTarget + ": " +
root.contains(searchTarget));
System.out.println(" Contains 60: " + root.contains(60));
}
}
<T extends Comparable<T>> - T comparable to itself. Common for sorting.
Bounds and inheritance
How bounded generics interact with class hierarchies.
import java.util.*;
public class BoundsInheritance {
static abstract class Entity {
private Long id;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
static class Repository<T extends Entity> {
private Map<Long, T> store = new LinkedHashMap<>();
private Long nextId = 1L;
public void save(T entity) {
if (entity.getId() == null) {
entity.setId(nextId++);
}
store.put(entity.getId(), entity);
System.out.println(" Saved: " + entity.getClass().getSimpleName() +
" id=" + entity.getId());
}
public T findById(Long id) {
return store.get(id);
}
public List<T> findAll() {
return new ArrayList<>(store.values());
}
public int count() {
return store.size();
}
}
public static void main(String[] args) {
System.out.println("Bounds with inheritance:\n");
class User extends Entity {
String name;
User(String name) {
this.name = name;
}
@Override
public String toString() {
return "User(" + getId() + ", " + name + ")";
}
}
Repository<User> userRepo = new Repository<>();
User user1 = new User("Alice");
User user2 = new User("Bob");
userRepo.save(user1);
userRepo.save(user2);
System.out.println(" All users: " + userRepo.findAll());
System.out.println(" Count: " + userRepo.count());
// Bounded type allows accessing members of bound class
// All entities have getId/setId methods
// Type parameter must extend (or implement) the bound
// Enables polymorphic behavior with type safety
System.out.println("\nMultiple entity types:");
class Product extends Entity {
String name;
double price;
Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Product(" + getId() + ", " + name + ", $" + price + ")";
}
}
Repository<Product> productRepo = new Repository<>();
productRepo.save(new Product("Laptop", 999.99));
productRepo.save(new Product("Mouse", 29.99));
System.out.println(" All products: " + productRepo.findAll());
System.out.println("\nService layer:");
class Service<T extends Entity> {
private Repository<T> repository;
public Service(Repository<T> repository) {
this.repository = repository;
}
public T create(T entity) {
repository.save(entity);
System.out.println(" Created: " + entity);
return entity;
}
public T update(T entity) {
if (entity.getId() == null) {
throw new IllegalArgumentException("Cannot update without ID");
}
repository.save(entity);
System.out.println(" Updated: " + entity);
return entity;
}
public T getById(Long id) {
T entity = repository.findById(id);
if (entity == null) {
System.out.println(" Not found: " + id);
}
return entity;
}
}
Service<User> userService = new Service<>(userRepo);
User newUser = userService.create(new User("Charlie"));
User found = userService.getById(newUser.getId());
System.out.println(" Retrieved: " + found);
System.out.println("\nAuditable entities:");
abstract class Auditable extends Entity {
private long createdAt = 1000L;
public long getCreatedAt() { return createdAt; }
}
class AuditableRepository<T extends Auditable> extends Repository<T> {
@Override
public void save(T entity) {
super.save(entity);
System.out.println(" Created at: " + entity.getCreatedAt());
}
}
class Order extends Auditable {
String product;
int quantity;
Order(String product, int quantity) {
this.product = product;
this.quantity = quantity;
}
@Override
public String toString() {
return "Order(" + getId() + ", " + product + " x" + quantity + ")";
}
}
AuditableRepository<Order> orderRepo = new AuditableRepository<>();
orderRepo.save(new Order("Laptop", 1));
orderRepo.save(new Order("Mouse", 2));
System.out.println("\nType hierarchy:");
abstract class Animal {
abstract String speak();
}
class Dog extends Animal {
@Override
String speak() { return "Woof"; }
}
class Cat extends Animal {
@Override
String speak() { return "Meow"; }
}
class AnimalShelter<T extends Animal> {
private List<T> animals = new ArrayList<>();
public void add(T animal) {
animals.add(animal);
System.out.println(" Added: " + animal.speak());
}
public void makeAllSpeak() {
for (T animal : animals) {
System.out.println(" " + animal.speak());
}
}
}
AnimalShelter<Dog> dogShelter = new AnimalShelter<>();
dogShelter.add(new Dog());
dogShelter.add(new Dog());
dogShelter.makeAllSpeak();
}
}
Subclasses satisfy superclass bounds. Integer extends Number satisfies <T extends Number>.
Exercise: Practical.java
Build a bounded generic collection for numbers