Java泛型extends/super疑问:不可添加元素的List场景及代码差异
Scenarios for List<? extends Number> (Cannot Add Elements)
You’re right that returning this type from a method is a common use case—it lets you expose a read-only view of a specific subclass list (like ArrayList<Integer>) while retaining the flexibility of treating it as a collection of Numbers. But there are other practical scenarios too:
1. Read-Only Method Parameters
Use this wildcard when you need a method that only reads from a collection, regardless of the specific Number subclass it holds. This makes your method far more flexible than using List<Number>, since you can pass in ArrayList<Integer>, LinkedList<Double>, etc.—types that aren’t compatible with List<Number> directly.
Example:
public static double calculateTotal(List<? extends Number> numbers) { double total = 0.0; for (Number num : numbers) { total += num.doubleValue(); } return total; }
You can call this with calculateTotal(new ArrayList<Integer>()) or calculateTotal(new ArrayList<Double>())—both work perfectly, since we only need to access Number-level methods.
2. Immutable Collection Holders
If you have a class that needs to store a collection of Number subclasses but should never modify it (only read), declaring the member variable as List<? extends Number> enforces this at compile time. It blocks accidental calls to add() or other mutation methods, keeping your internal state safe.
3. Compatibility with Generic APIs
Many generic utility libraries use bounded wildcards to accept a range of collection types. For example, if you’re working with a data processing tool that handles numeric datasets, using List<? extends Number> lets you integrate with different numeric collections without messy casting.
Differences Between List<? super Number> and List<Number>
Let’s break down the key distinctions between your two code snippets:
1. Element Retrieval Type
List<Number>: When you callget(), you get aNumberdirectly. You can immediately use allNumbermethods (likedoubleValue(),intValue()) without casting:Number num = myList.get(0); double val = num.doubleValue(); // No casting neededList<? super Number>: The compiler only knows elements are a supertype ofNumber(could beNumber,Serializable, or evenObject). Soget()returns anObject, and you need to cast toNumberto use its methods:Object obj = myList.get(0); if (obj instanceof Number) { double val = ((Number) obj).doubleValue(); // Cast required }
2. Assignment Flexibility
List<Number>: Can only be assigned to collections explicitly typed asList<Number>(or its concrete subclasses likeArrayList<Number>). You can’t assign aList<Object>orList<Serializable>to it—those are incompatible:// Compile error! // List<Number> myList = new ArrayList<Object>();List<? super Number>: Can accept any collection whose type is a supertype ofNumber(includingObject,Serializable, orNumberitself):// All valid List<? super Number> list1 = new ArrayList<Object>(); List<? super Number> list2 = new ArrayList<Serializable>(); List<? super Number> list3 = new ArrayList<Number>();
3. Use Case Intent
List<Number>: Use this when you need to both read from and write to the collection, and you want to work directly withNumbertypes. It makes clear that the collection is meant to hold anyNumbersubclass, and you’ll interact with them asNumbers.List<? super Number>: This follows the PECS principle (Producer Extends, Consumer Super)—use it when your code is a "consumer" that adds elements to the collection. It lets you accept a wider range of collection types, which is useful if you need to pass the collection to code that expects a more general type (likeList<Object>).
内容的提问来源于stack exchange,提问作者Majid Abdolhosseini

