泛型ArrayList与非泛型旧版ArrayList的实现差异解析
ArrayList: Implementation-Level Differences Great question—let’s dive into the nitty-gritty of how these two versions work under the hood, beyond just the syntax you use in your code.
1. Underlying Storage & Type Erasure
Both versions use an Object[] array as their underlying storage (yes, even the generic one!). The key difference here is how the compiler treats this array:
- Pre-generic
ArrayList: The array holds rawObjectinstances, so you can add any object to it with no compile-time checks. When you retrieve elements, you have to manually cast them to the desired type, like this:ArrayList oldList = new ArrayList(); oldList.add("hello"); String s = (String) oldList.get(0); // Manual cast required - Generic
ArrayList: Thanks to type erasure, the compiled bytecode still uses anObject[], but the compiler enforces type checks at compile time. When you retrieve elements, it automatically inserts the cast for you. For example:
If you try to add anArrayList<String> genList = new ArrayList<>(); genList.add("hello"); String s = genList.get(0); // Implicit cast added by compilerIntegertogenList, the compiler throws an error immediately—something the pre-generic version would never catch until runtime.
2. Method Signatures & Bridge Methods
The method signatures look different in source code, and the compiler does extra work to make generics compatible with legacy code:
- Pre-generic methods: Methods like
add(Object o)andObject get(int index)accept/return rawObjecttypes. No extra compiler magic here. - Generic methods: In source code, you have
add(E e)andE get(int index), whereEis the type parameter. After type erasure, these methods are converted toadd(Object o)andObject get(int index)—but the compiler generates bridge methods to maintain polymorphism. For example, if you subclassArrayList<String>and overrideadd(String), the compiler creates a bridge methodadd(Object)that delegates to your overriddenadd(String)method, ensuring compatibility with code that expects the rawArrayListinterface.
3. Compile-Time vs. Runtime Type Safety
This is the biggest implementation-related difference in terms of behavior:
- Pre-generic
ArrayList: All type checks happen at runtime. You can add any object to the list, and you’ll only get aClassCastExceptionif you try to cast an element to the wrong type when retrieving it. This makes bugs harder to catch early. - Generic
ArrayList: The compiler enforces type safety at compile time. It checks that every element added to the list matches the declared type parameter, so you can’t accidentally add anIntegerto aList<String>in the first place. The runtime has no knowledge of the generic type (thanks to type erasure), but the compile-time checks eliminate most type-related bugs before your code runs.
4. Serialization Behavior
While both versions serialize the underlying Object[], the generic version adds compile-time guarantees that affect serialization:
- Pre-generic
ArrayList: When deserializing, you could end up with a list containing any type of object, and you’ll only find out when you try to cast elements. - Generic
ArrayList: Even though the runtime doesn’t track the generic type during serialization, the compiler ensures that when you deserialize aList<String>, you’ll only cast elements toString(either implicitly or explicitly). This reduces the chance of runtime casting errors from deserialized data.
Quick Summary
At the bytecode level, the two versions are similar (both use Object[]), but the generic ArrayList is essentially a compile-time syntax sugar that adds:
- Compile-time type checking to prevent invalid element additions
- Implicit type casts when retrieving elements
- Bridge methods to maintain compatibility with legacy code
- Early bug detection that the pre-generic version can’t provide
内容的提问来源于stack exchange,提问作者SM2A

