Explain Codes LogoExplain Codes Logo

Instantiating a generic class in Java

java
instantiation
generics
reflection
Nikita BarsukovbyNikita Barsukov·Feb 24, 2025
TLDR

To instantiate a generic class in Java, specify the type parameters within angle brackets:

ClassName<ConcreteType> obj = new ClassName<>();

For multiple types:

ClassName<Type1, Type2> obj = new ClassName<>();

Utilizing diamond <> for type inference, wrapper classes replace primitives. For example with Integer:

MyClass<Integer> myObj = new MyClass<>();

However, some scenarios challenge this straightforwardness, including type erasure or instantiating a generic type T within a class. Here's how to navigate these.

Beyond the basics: advanced instantiation

1. Class references: A light in the type erasure darkness

Including a Class<T> reference to the generic class constructor enables dynamic instantiation, tackling some type erasure problems.

public class Foo<T> { private Class<T> type; public Foo(Class<T> type) { this.type = type; } // Say "Hello, world!" to our instance creator. public T createInstance() throws IllegalAccessException, InstantiationException { return type.newInstance(); } }

2. Factories: The Santa's workshop of object creation

Factories produce T instances, enhancing flexibility and eliminating nasty reflection exceptions.

public interface Factory<T> { T create(); } public class Foo<T> { private Factory<T> factory; public Foo(Factory<T> factory) { this.factory = factory; } // This method is the conveyor belt of Santa's workshop. public T createInstance() { return factory.create(); } }

3. Java 8 Suppliers: Your friendly neighborhood instantiators

Java 8 introduced Supplier<T>, a functional interface just waiting to instantiate generic types.

import java.util.function.Supplier; public class Foo<T> { private Supplier<T> supplier; public Foo(Supplier<T> supplier) { this.supplier = supplier; } // Look, up in the sky! It's a bird, it's a plane, it's... our instantiate! public T createInstance() { return supplier.get(); } }

4. The reflection torture chamber: Tread carefully

Reflection is a tool, but with sharp edges. newInstance() might revolt if T lacks a default constructor.

public T createInstance() throws IllegalAccessException, InstantiationException { // newInstance() may throw a tantrum if T lacks a default constructor return type.newInstance(); } // Scooby-Doo would love this: getGenericSuperclass() for some type intel Type mySuperclass = getClass().getGenericSuperclass();

5. Beyond parameter-less constructors: Unleash the Factory

Need a T with no parameter-less constructor? A Factory interface implementation is here to rescue.

MyClass<String> myObj = new MyClass<>(String::new); // Supplier for String's constructor, parameter-less no more!

6. The type erasure rabbit hole: Alice in Java-land

Thanks to type erasure, generic types vanish at runtime. Our strategies bridge this gap through type inference.

Type genericType = ((ParameterizedType)myObj.getClass().getGenericSuperclass()).getActualTypeArguments()[0];