L. Heider
L. Heider

Reputation: 1819

Java Generic List<>

I am trying to do something like this:

1. Approach

Type ty = entityType.getEntityClass(); // could be e.g. String.class  
List<ty> list;

2. Approach

Class cl = entityType.getEntityClass(); // could be e.g. String.class
List<cl> list;

However, compiler simply says "no".

Is there any way to set the generic type <T> of e.g. a java.util.List dynamic only by having e.g. String.class or foo.getClass()?

...

What I am actually trying to do is initializing a List by a given parameter of type EntityTypeEnum or it's Class property value.

public enum EntityTypeEnum {
    ARTICLE(Article.class),
    ORDER(OrderHeader.class),
    CUSTOMER(Customer.class),
    CUSTOMER_SESSION(CustomerSession.class);

    private final Class entityClass;

    private EntityTypeEnum(final Class entityClass) {
        this.entityClass = entityClass;
    }

    public Class getEntityClass() {
        return entityClass;
    }
}

Constructive criticism is welcome. Thank you!

Add: (as a response of Andy's comment)

Class cl = String.class;
List<cl> list;

..is not working either!

List<String> list;

..works, of course.

So what is this difference between String and String.class?

Or is there a way to set the value like:

public enum EntityTypeEnum {
    ARTICLE(Article)
..

Upvotes: 2

Views: 713

Answers (2)

Stefan Winkler
Stefan Winkler

Reputation: 3956

It does not work because Generics are a compile-time concept but you try to use it as a runtime concept.

The difference is that for compile-time concepts, the compiler needs to be able to figure out the type based on the information it has on your code (i.e., without running or evaluating it).

This code here would be syntactically correct:

public enum EntityTypeEnum
{
  ARTICLE(String.class), // just using some builtin types to demonstrate
  ORDER(Integer.class), 
  CUSTOMER(Double.class), 
  CUSTOMER_SESSION(Short.class);

  private final Class<?> entityClass;

  private EntityTypeEnum(final Class<?> entityClass)
  {
    this.entityClass = entityClass;
  }

  public Class<?> getEntityClass()
  {
    return this.entityClass;
  }
}

class Test
{
  // here, T is the type parameter which is determined from the class type
  public <T> List<T> createList(final Class<T> clazz)
  {
    return new ArrayList<T>();
  }

  // this is the demo code on how to use it    
  public void foo()
  {
    EntityTypeEnum t = EntityTypeEnum.ARTICLE;
    List<?> list = createList(t.getEntityClass());
  }
}

The problem is that this does not help you much. List is more or less the same as List. The compiler cannot narrow the type down to a specific contained object class, because it depends on the runtime.

For your case, if you have a common superclass for your elements, you could use this information to narrow down the type:

public enum EntityTypeEnum
{
  ARTICLE(Article.class),
  ORDER(OrderHeader.class),
  CUSTOMER(Customer.class),
  CUSTOMER_SESSION(CustomerSession.class);

  private final Class<? extends CommonParent> entityClass;

  private EntityTypeEnum(final Class<? extends CommonParent> entityClass)
  {
    this.entityClass = entityClass;
  }

  public Class<? extends CommonParent> getEntityClass()
  {
    return this.entityClass;
  }
}

class Test
{
  public <T extends CommonParent> List<T> createList(final Class<T> clazz)
  {
    return new ArrayList<T>();
  }

  public void foo()
  {
    EntityTypeEnum t = EntityTypeEnum.ARTICLE;
    List<? extends CommonParent> list = createList(t.getEntityClass());
  }
}

But if you have a common parent, there is no benefit of the above code over just writing:

List<CommonParent> list = new ArrayList<CommonParent>();

and skipping all that additional generic stuff...

Upvotes: 1

Andy Turner
Andy Turner

Reputation: 140328

You are misunderstanding what Java generics are capable of: you can't do something based upon the value of a variable, only with the type of the variable.

Basically, Java generics are just an elision of casts: the compiler automatically inserts casts for you, and checks that the types resulting from those casts match up.


You can do what you describe in your example though, sort of, just not using enums. One of the shortcomings of enums in Java is that all elements have the same type.

Instead of using a real enum, you can use something that roughly looks like an enum:

final class EntityTypeEnumIsh {
  private EntityTypeEnumIsh() {}

  static final EntityClassSupplier<Article> ARTICLE =
    new EntityClassSupplier<>(Article.class);
  static final EntityClassSupplier<OrderHeader> ORDER =
    new EntityClassSupplier<>(OrderHeader.class);
  // ...

  final class EntityClassSupplier<T> {
    private final Class<T> clazz;

    EntityClassSupplier(Class<T> clazz) { this.clazz = clazz; }

    Class<T> getEntityClass() { return clazz; }
  }
}

You can now use these in a method as you describe:

<T> List<T> method(EntityClassSupplier<T> supplier) {
  return new ArrayList<>();
}

and invoke like this:

List<Article> list = method(EntityTypeEnumIsh.ARTICLE);

Of course, you don't get all of the niceness of a "real" enum (like serialization, resistance to reflection attacks etc). But you get something else useful, so weigh it up for your use case.

Upvotes: 4

Related Questions