Syzorr
Syzorr

Reputation: 607

Creating a List using the concrete type's Class

I can understand the example of instantiating a List object using a generic type. My current homework assignment involves creating an Appender for Log4j that stores the logs in a list. One of the requirements is that the user could specify a concrete implementation of List for the constructor and I have done this by just accepting a List that they have created.

Is there a way to make it so that they can provide the Class<T> implementing List that they want to be using and my constructor will instantiate a new instance of it?

Upvotes: 0

Views: 785

Answers (2)

Andy Brown
Andy Brown

Reputation: 19171

Class<T> will let you call newInstance(), which will return you a T. If you then combine this with wildcard generics you could in theory specify a Class<? extends List<LoggingEvent>> and create any type that implements the List<LoggingEvent> interface.

However here is the first problem: you cannot use a parameterized type with the class literal (i.e. LinkedList<LoggingEvent>.class will not compile). Therefore you have to relax your method/constructor parameter to only bound the wildcard on the raw type of List, like this: Class<? extends List>.

So now when you create the List you will have to cast it to the correct generic type. That will mean you need to do unchecked conversion using @SuppressWarnings("unchecked"). In this case this is safe to do as you will never try and use that raw type as any other generic type other than List<LoggingEvent>.

The final implementation would look something like:

class LogStore {
    private List<LogLine> loggingEvents = null;
    public LogStore(Class<? extends List> clazz) {
        try {
            @SuppressWarnings("unchecked")
            List<LogLine> logStoreList = clazz.newInstance();
            this.loggingEvents = logStoreList;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }       
    }
}

In this case this may be safe to do so if you provide the user with a set of prompts to let them choose a standard List<LoggingEvent> implementation, but if they are going to use this as an API and you have no control over which Class<? extends List> they pass in then you have two choices: either let the api fail at runtime in unpredictable places, or try and check the type argument for list. However, even if you do check the type argument you cannot check for all eventualities and the API may still break (e.g. if the user passes the class for a read-only List<LoggingEvent>).

For example, even something as simple as this will cause a (to the API user strange, possibly untraceable) runtime exception:

new LogStore(IntList.class);
// Used with IntList defined as ...
public class IntList extends ArrayList<Integer> {
    @Override public Integer remove(int index) { return super.remove(index); }
}

If you choose to do the latter of the two options you will want to do something like this to check it really is a List<LoggingEvent>:

public LogStore(Class<? extends List> clazz) {
    assertListTypeArgsValid(clazz);
    // ... the rest of the above method implementation ...
}
private void assertListOk(Class<? extends List> clazz) {
    boolean verified = false;
    for (Type intr : clazz.getGenericInterfaces()) {
        if (!(intr instanceof ParameterizedType)) continue;

        ParameterizedType pIntr = (ParameterizedType)intr;
        if (pIntr.getRawType().getTypeName() != "java.util.List") continue;

        Type[] typeArgs = pIntr.getActualTypeArguments();
        if (typeArgs.length != 1) break;

        Class<?> tac = (Class<?>)typeArgs[0];
        verified = tac.isAssignableFrom(LoggingEvent.class);
        if (!verified) throw new IllegalArgumentException("clazz must be a List<LoggingEvent>, and is a: "
                + pIntr.getTypeName());
        break;
    }
    if (!verified) throw new IllegalArgumentException("clazz must be a List<LoggingEvent>");
}

Upvotes: 1

Elliott Frisch
Elliott Frisch

Reputation: 201497

If I understand your question, then yes; the simplest method I can think of, would be a constructor that takes a Collection. For example, ArrayList(Collection).

List<SomeType> copyList = new ArrayList<>(original);

Or perhaps, you wanted something that returns a List<T> given an item T. Something like Collections.singletonList(T)

SomeType t = // ...
List<SomeType> al = Collections.singletonList(t);

Upvotes: 2

Related Questions