Definition
- Bounded type parameter –
restricts T to Number or its subclasses (Integer, Double, BigDecimal), allowing calls to Number methods likedoubleValue() - Upper-bounded wildcard –
? extends Accountmeans "some unknown subtype of Account"; you can read elements as Account but cannot add elements (the compiler does not know the exact type) - Lower-bounded wildcard –
? super SavingsAccountmeans "some unknown supertype of SavingsAccount"; you can add SavingsAccount but reads return Object - PECS – Producer Extends (read from
? extends T), Consumer Super (write to? super T); the standard guideline from Effective Java - Unbounded wildcard –
?means completely unknown type;List>is read-only with elements as Object
At National Bank of India, ? extends Account is like a report that contains accounts of some specific subtype (Savings or Current). You can read accounts from the report, but you cannot file a new account into it because you do not know whether it accepts Savings or Current. ? super SavingsAccount is like a filing drawer that accepts at least SavingsAccount (it might also accept any Account or even Object). You can file a SavingsAccount into it, but when you read something out, you only know it is some Object.
Understanding the Concepts
What are bounded types, wildcards, and PECS?
How do bounded type parameters work?
A bounded type parameter restricts the types that can be used as arguments. means T must be Number or a subclass. Inside the method, you can call any method defined on the bound (Number.doubleValue(), Number.intValue()).
package com.bank.demo;
import java.math.BigDecimal;
import java.util.List;
public class BoundedTypeDemo {
// T must extend Number: can call doubleValue()
static <T extends Number> BigDecimal sumBalances(List<T> amounts) {
BigDecimal total = BigDecimal.ZERO;
for (T amount : amounts) {
total = total.add(BigDecimal.valueOf(amount.doubleValue()));
}
return total;
}
public static void main(String[] args) {
List<Integer> intAmounts = List.of(10000, 25000, 50000);
List<Double> doubleAmounts = List.of(10000.50, 25000.75);
System.out.println("Int sum: " + sumBalances(intAmounts));
System.out.println("Double sum: " + sumBalances(doubleAmounts));
// List<String> would NOT compile:
// sumBalances(List.of("SAV2001")); // String does not extend Number
}
}
Int sum: 85000
Double sum: 35001.25
How do upper-bounded wildcards (? extends T) work?
A List extends Account> is a list of some unknown subtype of Account. You can read elements from it as Account, but you cannot add elements because the compiler does not know the exact subtype.
package com.bank.demo;
import java.math.BigDecimal;
import java.util.List;
public class UpperBoundDemo {
static abstract class Account {
abstract BigDecimal getBalance();
}
static class SavingsAccount extends Account {
private final BigDecimal balance;
SavingsAccount(BigDecimal balance) { this.balance = balance; }
@Override BigDecimal getBalance() { return balance; }
}
static class CurrentAccount extends Account {
private final BigDecimal balance;
CurrentAccount(BigDecimal balance) { this.balance = balance; }
@Override BigDecimal getBalance() { return balance; }
}
// ? extends Account: PRODUCER (read Account from the list)
static BigDecimal totalBalance(List<? extends Account> accounts) {
BigDecimal total = BigDecimal.ZERO;
for (Account acc : accounts) { // read as Account: OK
total = total.add(acc.getBalance());
}
// accounts.add(new SavingsAccount(...)); // COMPILE ERROR: cannot write
return total;
}
public static void main(String[] args) {
List<SavingsAccount> savings = List.of(
new SavingsAccount(new BigDecimal("500000")),
new SavingsAccount(new BigDecimal("300000"))
);
List<CurrentAccount> current = List.of(
new CurrentAccount(new BigDecimal("1200000"))
);
System.out.println("Savings total: " + totalBalance(savings));
System.out.println("Current total: " + totalBalance(current));
}
}
Savings total: 800000
Current total: 1200000
How do lower-bounded wildcards (? super T) work?
A List super SavingsAccount> is a list of some unknown supertype of SavingsAccount. You can add SavingsAccount to it, but when you read elements, the compiler only knows they are Object.
package com.bank.demo;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
public class LowerBoundDemo {
static class Account {
String id;
Account(String id) { this.id = id; }
@Override public String toString() { return "Account(" + id + ")"; }
}
static class SavingsAccount extends Account {
SavingsAccount(String id) { super(id); }
@Override public String toString() { return "Savings(" + id + ")"; }
}
// ? super SavingsAccount: CONSUMER (write SavingsAccount into the list)
static void addDefaults(List<? super SavingsAccount> list) {
list.add(new SavingsAccount("SAV-DEFAULT-001"));
list.add(new SavingsAccount("SAV-DEFAULT-002"));
// Reading: can only get Object
Object obj = list.get(0); // Object, not SavingsAccount
}
public static void main(String[] args) {
List<Account> accountList = new ArrayList<>();
addDefaults(accountList); // List<Account> is a supertype of SavingsAccount
System.out.println("Account list: " + accountList);
List<Object> objectList = new ArrayList<>();
addDefaults(objectList); // List<Object> is also a supertype
System.out.println("Object list: " + objectList);
}
}
Account list: [Savings(SAV-DEFAULT-001), Savings(SAV-DEFAULT-002)]
Object list: [Savings(SAV-DEFAULT-001), Savings(SAV-DEFAULT-002)]
What is the PECS rule?
PECS stands for Producer Extends, Consumer Super. It tells you which wildcard to use based on whether you are reading from or writing to the collection.
| Role | Wildcard | Operations | Example |
|---|---|---|---|
| Producer (read) | ? extends T | Read T from it | totalBalance(List extends Account>) |
| Consumer (write) | ? super T | Write T into it | addDefaults(List super SavingsAccount>) |
| Both read and write | T (no wildcard) | Read and write T | void process(List |
The JDK demonstrates PECS in Collections.copy:
// JDK signature:
public static <T> void copy(List<? super T> dest, // CONSUMER: writes T
List<? extends T> src) // PRODUCER: reads T
package com.bank.demo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PecsDemo {
public static void main(String[] args) {
List<String> src = List.of("SAV2001", "CUR3001", "FD4001");
List<String> dest = new ArrayList<>(List.of("", "", ""));
Collections.copy(dest, src); // dest=consumer(super), src=producer(extends)
System.out.println("Copied: " + dest);
}
}
Copied: [SAV2001, CUR3001, FD4001]
What are common mistakes?
Mistake: Writing to a ? extends collection
// WRONG: cannot add to a producer
List<? extends Number> numbers = new ArrayList<Integer>();
numbers.add(42); // COMPILE ERROR: ? extends is read-only
// CORRECT: use ? super for writing
List<? super Integer> numbers = new ArrayList<Number>();
numbers.add(42); // OK: Integer is a subtype of ? super Integer
Mistake: Reading specific type from ? super
// WRONG: cannot read specific type from consumer
List<? super Integer> list = new ArrayList<Number>();
list.add(42);
Integer val = list.get(0); // COMPILE ERROR: can only read as Object
// CORRECT: read as Object
Object val = list.get(0); // OK
What about multiple bounds?
A type parameter can have multiple bounds. Use & to require multiple interfaces or a class and interfaces.
package com.bank.demo;
import java.io.Serializable;
public class MultipleBoundsDemo {
// T must be both Comparable and Serializable
static <T extends Comparable<T> & Serializable> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
public static void main(String[] args) {
String result = max("SAV2001", "CUR3001");
System.out.println("Max: " + result);
Integer maxInt = max(500000, 300000);
System.out.println("Max int: " + maxInt);
}
}
Max: SAV2001
Max int: 500000
What should a senior engineer know?
- Invariance of Java generics.
Listis NOT a subtype ofList. This is different from arrays whereString[]IS a subtype ofObject[]. Wildcards compensate for this invariance. - Get and Put Principle. This is another name for PECS: use extends when you only get values out of a structure, use super when you only put values into a structure.
- Intersection types.
creates an intersection type. The first bound can be a class; the rest must be interfaces. - Type inference improvements. Java 8 improved type inference for method calls in chains. Java 10's var further reduces type declaration verbosity.
What are common interview follow-up questions?
- "What does PECS stand for?" : Producer Extends, Consumer Super. Use extends when reading from a collection, super when writing to it.
- "Can you add elements to List extends Number>?" : No. Only null can be added. The compiler does not know the exact subtype.
- "What is the difference between
and extends Number>?" : T is a named type parameter you can reference elsewhere in the method. ? is an anonymous wildcard used in a single position. - "Give a real example of PECS." : Collections.copy(dest, src) where dest uses ? super T (consumer) and src uses ? extends T (producer).
Visual Overview
PECS Rule: Producer Extends, Consumer Super
=============================================
List<? extends Account> List<? super SavingsAccount>
┌────────────────────────┐ ┌────────────────────────────┐
│ PRODUCER (read from) │ │ CONSUMER (write into) │
│ │ │ │
│ Account acc = list.get()│ │ list.add(new Savings(...)) │
│ -> OK (read as Account)│ │ -> OK (write Savings) │
│ │ │ │
│ list.add(new Savings()) │ │ Savings s = list.get() │
│ -> COMPILE ERROR │ │ -> COMPILE ERROR │
│ (cannot write) │ │ (can only read as Object) │
└────────────────────────┘ └────────────────────────────┘
Collections.copy signature:
copy(List<? super T> dest, <-- CONSUMER: write T into dest
List<? extends T> src) <-- PRODUCER: read T from src
Key Takeaway
Bounded types restrict what types can be used as generic arguments. means T must be Number or a subclass, enabling calls to Number methods inside the generic code. Wildcards add flexibility: ? extends T is for reading (producer), ? super T is for writing (consumer), and ? is for methods that do not care about the element type.
PECS (Producer Extends, Consumer Super) is the essential rule from Effective Java for choosing the right wildcard. When a method reads elements from a collection, declare it as ? extends T so it can accept List? super T so it can accept List
At National Bank of India, Rajesh applied PECS when building the balance calculation service. The method totalBalance(List extends Account> accounts) could accept a list of any Account subtype, while addDefaults(List super SavingsAccount> list) could write defaults into any list that accepted SavingsAccount. This maximised code reuse while maintaining full type safety.
Remember one sentence: use ? extends T to read from a collection (Producer Extends) and ? super T to write into a collection (Consumer Super).
---