Understanding Java Generics: Concepts, Usage, and Practical Examples
This article explains the importance of Java generics, demonstrates common pitfalls with raw collections, shows how generics provide compile‑time type safety, and covers generic classes, interfaces, wildcards, methods, bounds, and array usage with clear code examples.
1. Overview
Generics play a crucial role in Java, being widely used in object‑oriented programming and design patterns to provide type safety and reduce runtime errors.
Generics , also called parameterized types, allow a type to be expressed as a parameter, similar to method parameters. The actual type is supplied when the generic is instantiated, enabling the compiler to enforce type constraints.
2. A Simple Example
The following code creates a raw ArrayList that stores both a String and an Integer , then casts each element to String at runtime, causing a ClassCastException :
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for (int i = 0; i < arrayList.size(); i++) {
String item = (String) arrayList.get(i);
Log.d("GenericTest", "item = " + item);
}By declaring the list with a generic type parameter, the compiler catches the type mismatch before the program runs:
List<String> arrayList = new ArrayList<String>();
// arrayList.add(100); // compile‑time error3. Features of Generics
Generics are only effective at compile time. After compilation, type information is erased (type erasure), and the generated bytecode works with raw types. Consequently, generic type parameters do not exist at runtime.
Example output demonstrates that two generic instances with different type arguments share the same runtime class:
Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();
if (classStringArrayList.equals(classIntegerArrayList)) {
Log.d("GenericTest", "Types are the same");
}4. Using Generics
4.1 Generic Classes
A generic class defines a type parameter that can be used for fields, constructors, and methods. The most common generic classes are collection types such as List , Set , and Map .
class Generic<T> {
private T key;
public Generic(T key) { this.key = key; }
public T getKey() { return key; }
}Instances can be created with any reference type:
Generic<Integer> genericInt = new Generic<Integer>(123456);
Generic<String> genericStr = new Generic<String>("key_value");
Log.d("GenericTest", "key is " + genericInt.getKey());
Log.d("GenericTest", "key is " + genericStr.getKey());4.2 Generic Interfaces
Generic interfaces are defined similarly to generic classes and are often used for factories or producers.
public interface Generator<T> {
T next();
}Implementation without a concrete type parameter:
class FruitGenerator<T> implements Generator<T> {
@Override public T next() { return null; }
}Implementation with a concrete type:
public class FruitGenerator implements Generator<String> {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}4.3 Wildcards
The unbounded wildcard ? represents an unknown type and can be used when the exact type is not needed. It behaves as a supertype of all reference types.
public void showKeyValue(Generic<?> obj) {
Log.d("GenericTest", "key value is " + obj.getKey());
}Upper‑bounded wildcards restrict the unknown type to be a subclass of a specific class (e.g., ? extends Number ), while lower‑bounded wildcards use ? super T to allow supertypes.
4.4 Generic Methods
A generic method declares its own type parameters between the modifiers and the return type. This allows the method to operate on different types independently of the class.
public
T genericMethod(Class<T> tClass) throws InstantiationException, IllegalAccessException {
T instance = tClass.newInstance();
return instance;
}Another example shows a method that returns the key from a generic container:
public
T showKeyName(Generic<T> container) {
System.out.println("container key :" + container.getKey());
return container.getKey();
}4.5 Generic Methods with Varargs
public
void printMsg(T... args) {
for (T t : args) {
Log.d("GenericTest", "t is " + t);
}
}4.6 Static Methods and Generics
Static methods cannot use the class’s generic type parameters. To use generics in a static context, the method itself must be generic:
public static
void show(T t) {
// implementation
}4.7 Upper and Lower Bounds
Bounds are added to the type parameter declaration, not to the usage. Example of an upper‑bounded method:
public
T showKeyName(Generic<T> container) {
System.out.println("container key :" + container.getKey());
return container.getKey();
}4.8 Generic Arrays
Java does not allow creation of arrays of concrete generic types (e.g., List [] ) because of type‑erasure. However, arrays of unbounded wildcards or raw types are permitted:
List
[] lsa = new List
[10]; // OK
Object[] oa = lsa; // safe cast
List
li = new ArrayList<>();
li.add(3);
oa[1] = li; // allowed at runtime
Integer i = (Integer) lsa[1].get(0); // explicit cast required5. Conclusion
The examples above illustrate the core ideas behind Java generics, including type safety, compile‑time checks, and common patterns such as generic classes, interfaces, wildcards, methods, bounds, and array handling. Applying generics in everyday code can simplify development and improve code quality.
Original source: blog.csdn.net/s10461/article/details/53941091
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.