Question 20 · Section 20

What happens when trying to create an instance of generic type via new T()

Structured Java interview answer with junior, middle, and senior-level explanation.

Language versions: English Russian Ukrainian

🟢 Junior Level

You cannot create an instance of a generic type via new T(). This is a compilation error.

public class Box<T> {
    private T value;

    public Box() {
        value = new T();  // ❌ compilation error: "cannot instantiate T"
    }
}

Why: Due to type erasure, the compiler does not know what type T stands for at runtime. After compilation, T becomes Object, and new Object() is not what you want.

Solutions:

// Solution 1: Pass Class<T>
public class Box<T> {
    private T value;

    public Box(Class<T> type) throws Exception {
        value = type.getDeclaredConstructor().newInstance();
    }
}

// Solution 2: Factory (Supplier)
public class Box<T> {
    private T value;

    public Box(Supplier<T> factory) {
        value = factory.get();
    }
}

// Usage
Box<String> box1 = new Box<>(String.class);
Box<String> box2 = new Box<>(() -> "default");

🟡 Middle Level

Why is it not allowed?

Type erasure:

public class Box<T> {
    public Box() {
        value = new T();  // ❌ what does this mean after erasure?
    }
}

// After compilation:
public class Box {
    public Box() {
        value = new Object();  // T -> Object
        // But this is not what the developer intended!
    }
}

Problem: There is no information about the constructor of T. What if T has no no-arg constructor?

Working solutions

**1. Class approach:**

public class Factory<T> {
    private final Class<T> type;

    public Factory(Class<T> type) {
        this.type = type;
    }

    public T create() throws Exception {
        return type.getDeclaredConstructor().newInstance();
    }
}

Factory<String> stringFactory = new Factory<>(String.class);
String s = stringFactory.create();  // ""

2. Supplier approach:

public class Box<T> {
    private final Supplier<T> factory;

    public Box(Supplier<T> factory) {
        this.factory = factory;
    }

    public T create() {
        return factory.get();
    }
}

Box<User> userBox = new Box<>(User::new);
User user = userBox.create();

3. Abstract factory method:

public abstract class Creator<T> {
    protected abstract T createInstance();

    public T createAndProcess() {
        T instance = createInstance();
        // process
        return instance;
    }
}

public class UserCreator extends Creator<User> {
    @Override
    protected User createInstance() {
        return new User();
    }
}

Common mistakes

  1. Trying to bypass via cast:
    public class Box<T> {
     public T create() {
         return (T) new Object();  // ⚠️ unchecked cast
         // Works only if T = Object
         // ClassCastException for other types
     }
    }
    
  2. Reflection without check: ```java public T create(Class type) throws Exception { return type.newInstance(); // deprecated in Java 9+ }

// ✅ Better public T create(Class type) throws Exception { return type.getDeclaredConstructor().newInstance(); }


---

## 🔴 Senior Level

### Internal Implementation

**Why the compiler forbids it:**

JLS 15.9:

  • new T() is impossible because T is a type variable
  • Type variables have no constructor metadata in class files.
  • The JVM literally cannot emit a new instruction for T.
  • After erasure T -> Object or bound ```

Reflection approach:

public T create(Class<T> type) {
    try {
        Constructor<T> ctor = type.getDeclaredConstructor();
        return ctor.newInstance();
    } catch (NoSuchMethodException e) {
        // T has no no-arg constructor
        throw new RuntimeException(e);
    }
}

Architectural Trade-offs

Approaches to creating T:

Approach Pros Cons
**Class** Type-safe, simple Needs no-arg constructor
**Supplier** Flexible, any constructor Need to pass factory
Abstract method OOP style Need a subclass
Unchecked cast Simple Unsafe, ClassCastException risk

Edge Cases

1. Bounded type with no-arg constructor:

public class Box<T extends Runnable> {
    public T create(Class<T> type) throws Exception {
        // T must have a no-arg constructor
        return type.getDeclaredConstructor().newInstance();
    }
}

2. Constructor with parameters:

public T create(Class<T> type, String name) throws Exception {
    return type.getConstructor(String.class)
        .newInstance(name);
}

3. Generic array creation:

// Also not allowed
public class Box<T> {
    private T[] items = new T[10];  // ❌ error

    // ✅ Solution
    private T[] items = (T[]) new Object[10];
}

Performance

Ways to create T:
- new T(): N/A (not allowed)
- Class.newInstance(): ~50-100 ns (deprecated)
// Approximate values. Depend on JVM, optimization level, inlining.
- Constructor.newInstance(): ~100-200 ns
- Supplier.get(): ~1-5 ns (regular call)
- Unchecked cast: ~1 ns (but unsafe)

Supplier — fastest runtime approach

Production Experience

Spring Bean Factory:

@Component
public class EntityFactory {
    public <T> T create(Class<T> type) {
        return applicationContext.getBean(type);
    }
}

// Usage
User user = entityFactory.create(User.class);

Builder pattern:

public class Builder<T> {
    private final Supplier<T> factory;
    private final Map<String, Object> properties = new HashMap<>();

    public Builder(Supplier<T> factory) {
        this.factory = factory;
    }

    public Builder<T> with(String name, Object value) {
        properties.put(name, value);
        return this;
    }

    public T build() {
        T instance = factory.get();
        // inject properties
        return instance;
    }
}

User user = new Builder<>(User::new)
    .with("name", "John")
    .with("age", 25)
    .build();

Best Practices

// ✅ Supplier for flexibility
public class Box<T> {
    private final Supplier<T> factory;
    public Box(Supplier<T> factory) { this.factory = factory; }
    public T create() { return factory.get(); }
}

// ✅ Class<T> for simple cases
public T create(Class<T> type) {
    return type.getDeclaredConstructor().newInstance();
}

// ✅ Abstract factory method
protected abstract T createInstance();

// ❌ new T()
// ❌ Unchecked cast without need
// ❌ Class.newInstance() (deprecated)

🎯 Interview Cheat Sheet

Must know:

  • new T() — compilation error: “cannot instantiate T”
  • Reason: type erasure — after compilation T -> Object, JVM doesn’t know T’s constructor
  • Solution 1: Class<T>type.getDeclaredConstructor().newInstance()
  • Solution 2: Supplier<T>factory.get() — most flexible and fastest approach
  • Solution 3: Abstract factory method — subclass implements creation
  • Class.newInstance() deprecated in Java 9+, use getDeclaredConstructor().newInstance()

Frequent follow-up questions:

  • Why can’t you do new T()? — Type erasure: T is replaced with Object, no constructor info
  • **Which approach is better — Class or Supplier?** — Supplier is more flexible and faster (~1-5 ns vs ~100-200 ns)
  • Can you create T with parameters via Class? — Yes: type.getConstructor(String.class).newInstance(name)
  • What is the TypeReference pattern? — Jackson style: new TypeReference<User>() {} for getting generic type

Red flags (DO NOT say):

  • ❌ “new T() works via unchecked cast” — This is a compilation error, not a warning
  • ❌ “Class.newInstance() is the best way” — Deprecated in Java 9+, use getDeclaredConstructor
  • ❌ “Supplier is slower than Class” — Supplier is faster (~1-5 ns vs ~100-200 ns)
  • ❌ “You can create T via reflection without Class” — You need Class or Supplier

Related topics:

  • [[11. What are Generics in Java]]
  • [[13. What is type erasure]]
  • [[14. Can you create an array of generic type]]
  • [[15. What are bounded type parameters]]