简体   繁体   中英

Java Generics & Polymorphism

I had an error while generalizing some classes in order to reduce code duplicates in my project, the case is as follows:

interface ClassRoot{}

public class ClassChild1 implements ClassRoot{
    // constructor is omitted for simplicity
    public Collection<ClassChild1> createList(){

        Collection<ClassChild1> col = getCollectionFromSomewhere();
        return col;
    }
}

public class Class1{
    protected <T extends ClassRoot> Collection<T> doSth(){
        Collection<T> myCol = null;
        ClassChild1 child = new ClassChild1();

        // here I get compile time assignment error
        // also (assuming that myCol not null) myCol.add(new ClassChild1());
        // is not permitted.
        myCol = child.createList();
        return myCol;
    }
}

Isn't that against the polymorphism phenomenon? I know that for example,

List<Object> list1;

can never be (and should not be assigned for typesafety) to:

List<String> list2;

But here my case is different, i specified the Type Parameter extending some specific class expecting to make use of OOP Polymorphism concept, and doing assignments right exactly implementing right class. Now I have 2 questions;

  • May someone clearly explain why is that is forbidden in Java, for what justifiable reason?
  • How can I implement such a functionality, by using type params or wildcards?

The assignment

Collection<T> myCol = null;
myCol = child.createList();

will fail because you are assigning Collection<ClassChild1> to a Collection parameterized with an unknown type T , which may or may not be ClassChild1 .

You have specified T as inferred at the call site, so imagine the following code:

Class1 c1;
Collection<ClassChild2> c = c1.doSth();

You can see that the compiler would paint itself into a corner if it allowed you to compile such a doSth method since inside that method you want to add a ClassChild1 instance to a collection that in this example happens to hold instances of ClassChild2 .

Collection<ClassChild1> col = getCollectionFromSomewhere();
return col;

is returning a collection but actually your ClassChild1 is not a collection. This will definitely create compile time error. Make sure that the return type of the method must be a collection type or its descendant

createList does not provide a Collection, this is also an error.

You cannot assign a list of ClassChild1 to a list of T .

For example, if you rename RootClass to Animal , ClassChild1 to Cat . If now one instance of T would not be a Cat , you clearly cannot do the assignment you want.

It's hard to tell what the problem is, because:

public ClassChild1 createList(){

Must be wrong (as others have noted). Does it actually say:

public Collection<ClassChild1> createList(){

This compiles okay:

import java.util.*;

interface ClassRoot {}

class ClassChild1 implements ClassRoot {

    public Collection<? extends ClassRoot> createList(){

        Collection<ClassChild1> col = new ArrayList<ClassChild1>();
        return col;
    }
}

class Class1{
    protected Collection<? extends ClassRoot> doSth(){
        Collection<? extends ClassRoot> myCol = null;
        ClassChild1 child = new ClassChild1();

        myCol = child.createList();
        return myCol;
    }
}

I've declared createList to return something that is assignable to ClassRoot . What it returns is compatible with that.

Then I've changed doSth to return the same thing, so it is also compatible.

The downside is that you still cannot call:

myCol.add(child);

And for a good reason - even though we don't know what restriction exists on the collection items, that doesn't mean that there is no restriction (the room doesn't disappear when you turn the light off).

factored your code here . Your ClassChild implementation is,

public class ClassChild implements ClassRoot {
    public List<ClassChild> createList() {
        return Arrays.asList(new ClassChild[] {new ClassChild(), new ClassChild()});
    }


}

and your Class1 is

public class Class1 {

    public static <T extends ClassRoot> Collection<T> doSth(){
        Collection<T> myCol = null;
        ClassChild child = new ClassChild();
        /*you should note here*/
        myCol =  (Collection<T>) child.createList();
        return  myCol;
    }
}

You have to stop and at the place where I have mentioned as "You have to note here". If you do a cast as Collection , the problem is solved. But coming to your question, you have created a collection named myCol which contains type T that extends ClassRoot. But what is the guarantee that createList will return a collection that contains subtypes ClassRoot ? So you always to have cast explicitly.

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