简体   繁体   中英

Generics: Type as variable?

In order to be able to substitute a specific implementation, it is commonly known to write

List<AnyType> myList = new ArrayList<AnyType>();

instead of

ArrayList<AnyType> myList = new ArrayList<AnyType>();

This is easy to understand, this way you might change the implementation from ArrayList to LinkedList or any other kind of List with ease.

Well... this is all good and nice, but as I cannot instanciate "List" directly, I therefore would be required to type

public List<AnyType> getSpecificList()
{
    return new ArrayList<AnyType>();
}

which makes the previous pattern quite senseless. What if I now want to replace the implementation by an LinkedList instead of an ArrayList? It would be required to change it on two positions.

Is it possible to have something like this (I know the syntax is absolutely incorrect)?

public class MyClass<T>
{
    Type myListImplementation = ArrayList;

    List<T> myList = new myListImplementation<T>();

    public List<T> getSpecificList()
    {
        return new myListImplementation<T>();
    }
}

This would allow me to simply change the word "ArrayList" to "LinkedList" and everything is fine. I know that both lists may have different constructors and this would not work "as is". And I don't really want to add a second type-parameter for specifying the list-implementation that is being used.

Is there any clean mechanism to fix this?^

Thanks in advance and best regards Atmocreations

Why not use some kind of factory pattern instead of instantiating a specific list implementation directly. Then you need to change list implementation only in one place, inside the factory method.

For example you start with:

 List<T> createList() {
     return new ArrayList<T>();
 } 

 List<T> myList1 = createList();
 List<T> myList2 = createList();

Later, if you decide that you need a linked list instead, you just change the implementation of createList() and the rest of your code stays the same.

List<T> createList() {
    return new LinkedList<T>();
}

Erm - as far as I understand your question, what you want to do is already possible (and nothing to do with generics).

You will always need to give the exact type of the list (or any class) when actually making a constructor call, that's unavoidable. Likewise, you can always avoid specifics everywhere else (storing it in a variable, returning it from a method, passing it as a method parameter, etc.). In your case you're already doing this - by declaring myList as simply a List, you don't need to change it's declared type if you change the concrete class of list you store in it.

I think your question may be around the fact that you're creating two different lists in the same class (but both of the same type), and you want to abstract this out. You can do this quite easily with a factory-type pattern; either with a separate factory, or in your case, just replacing the myList declaration with

List<T> myList = getSpecificList();

Edit - out of interest, the closest thing you could get to your original proposed fix would be using reflection:

public class MyClass<T>
{
    Class<? extends List<T>> myListClass = ArrayList.class;

    List<T> myList = myListClass.newInstance();

    public List<T> getSpecificList()
    {
        return myListClass.newInstance();
    }
}

But don't do this - it's slow, inflexible, unusual (so harder to grok for other developers) and completely unnecessary in this case...

Double-edit: oh, and you'd have to deal with a whole bunch of reflection-based checked exceptions that I've left as an exercise to the reader, just in case you felt tempted. ;-)

What if I now want to replace the implementation by an LinkedList instead of an ArrayList? It would be required to change it on two positions.

No, you wouldn't. You could change each location independently. ie, one of those allocations could be a LinkedList and the other could be an ArrayList.

This really has nothing to do with generics, but is instead having to do with polymorphism .

The point of using List (or List<T> ) instead of ArrayList (or ArrayList<T> ) is that List is an interface while ArrayList is a concrete implementation. Interfaces are good, and you should use them, but they really have nothing to do with generics directly.

In your example, why do you need to make the actual type of your List a variable? If you really want to abstract away the creation of the object, you should use a factory method, as it appears you're doing.

Generally speaking, the purpose of polymorphism in general and interfaces in specific is that clients ("consumers") of your data objects don't need to know implementation details. However, the code that creates ("produces") your objects should be in a position to know implementation details (since it's populating the object). So it shouldn't be problematic to have the object creation code know that it's creating an ArrayList or a LinkedList or whatever.

The only way to instantiate a "parameterized" List implementation would be through reflection, but you're definitely making the right first step, by returning the List interface rather than a complex class. Some ideas that could work:

  1. write a private method like newList() which just returns an empty List implementation. Use this throughout your class and then if you want to change it from ArrayList to LinkedList, you only have to change the implementation of that newList() method. This is similar to your getSpecificList() method
  2. If you want clients to choose the List implementation: accept a List class in the constructor:

    public MyClass(Class<? extends List<T>>) { ... }

Idea #2 would require reflection to instantiate the List implementation, however, which probably isn't what you want.

If the "Array" or "Linked" part of the list is important to your implementation, then expose it. If it isn't important, then don't.

You could have an interface like:

 ArrayList<T> createArrayList();

if for some reason the implementation of List were important. Returning "List" is good practice when all you need is the List-ishness, in which case implementation is less important.

They way I understand your question (I could be mistaken though; your question isn't very clear), you are simply searching the correct syntax for a generic method – which simply looks like this:

public <T> List<T> getSpecificList()
{
    return new ArrayList<T>();
}

– Notice the leading <T> . Now if you want to change the type of the list, this change is restricted to one single position.

If you don't initialize myList, then you only need to change things in one spot. Unless of course you need to use any of the methods unique to ArrayList...

List<AnyType> myList = getSpecicList();

public List<AnyType> getSpecificList()
{
    return new ArrayList<AnyType>();
}

How about this?

public class MyClass<T>
{
    List<T> myList = this.getSpecificList();

    public List<T> getSpecificList()
    {
        return new ArrayList<T>();
    }
}

Now you only have to change the type in one place.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM