为何从泛型方法调用泛型可变参数方法会返回Object[]?
pickTwo Return Object[] Instead of Integer[]? Great question—this is a classic gotcha tied to Java's type erasure and how the List.toArray() methods work, exactly as Joshua Bloch covers in Effective Java. Let's break this down with concrete examples and clear reasoning.
First, let's assume the code you're looking at looks something like this (the typical example for this scenario):
public static <T> T[] pickTwo(T a, T b, T c) { List<T> selected = Arrays.asList(a, b, c); Collections.shuffle(selected); return selected.subList(0, 2).toArray(); } // When you try to call it: Integer[] pair = pickTwo(1, 2, 3); // Compile error! Returns Object[], not Integer[]
The Core Problem: Two Versions of toArray()
First, let's clarify the two toArray methods on List:
Object[] toArray(): This is the no-argument version, and it always returns anObject[]. Why? Because of type erasure—at runtime, the JVM has no idea thatselectedis aList<Integer>; it only knows it's aListof some object type. This method can't create a typed array because it has no type information to work with.<T> T[] toArray(T[] array): This overloaded version takes a prototype array. It uses the type of that array to create (or reuse) an array of the correct type for the list's elements.
Why the Original pickTwo Can't Return Integer[]
In the original method, we're calling the no-arg toArray(). Even though we know at compile time that T is Integer when we call pickTwo(1,2,3), that type information is erased when the method runs.
The compiler can't fix this because:
- At the point where
toArray()is called insidepickTwo, there's no concrete type information available to create aT[]array. The generic typeTis just a placeholder—there's no way for the JVM to instantiate an array ofTdirectly (Java doesn't allow generic array creation likenew T[2]). - The book's note about the array type being determined by the compile-time type of the method's parameter refers to the prototype array passed to
toArray(T[]). Since we aren't passing any such parameter here, the method defaults toObject[].
How to Fix It to Return T[]
If we want pickTwo to return a typed array like Integer[], we need to explicitly provide the type information. Here are two common approaches:
1. Pass the Class Type as a Parameter
public static <T> T[] pickTwo(T a, T b, T c, Class<T> type) { List<T> selected = Arrays.asList(a, b, c); Collections.shuffle(selected); // Create a typed array using the Class object @SuppressWarnings("unchecked") T[] result = (T[]) Array.newInstance(type, 2); return selected.subList(0, 2).toArray(result); } // Now this works: Integer[] pair = pickTwo(1, 2, 3, Integer.class);
2. Use the Type of One of the Arguments
If you can assume at least one argument is non-null, you can get the type from that:
public static <T> T[] pickTwo(T a, T b, T c) { List<T> selected = Arrays.asList(a, b, c); Collections.shuffle(selected); // Use the class of the first argument to create the array @SuppressWarnings("unchecked") T[] result = (T[]) Array.newInstance(a.getClass(), 2); return selected.subList(0, 2).toArray(result); }
Recapping Bloch's Explanation
The key takeaway from the book is that without explicit type information (either via a prototype array or a Class object), the compiler and runtime can't know what type of array to create. The no-arg toArray() has no choice but to return Object[], even if the list contains elements of a specific type. The compiler can't infer the correct array type because that information is lost to type erasure by the time the method executes.
内容的提问来源于stack exchange,提问作者3code

