Skip to content
Advertisement

An issue with Generic type parameters and Inheritance

Couldn’t concretize the title, sorry for that.

For example, we have a Parent class and a Child class:

package test;

public class Parent {
}
package test;

public class Child extends Parent {
}

And a class that can contain Parent‘s child in its field called a:

package test;

public class Holder<A extends Parent> {
    public A a;
}

Let’s create an instance of that class and try to assign a value to a:

package test;

public class Main {
    public static void main(String[] main) {
        Holder<Child> holder = new Holder<>();
        holder.a = new Child();
    }
}

So far, so good. But let’s say we want to move this assignation to a Holder‘s constructor:

package test;

public class Holder<A extends Parent> {
    public A a;

    public Holder() {
        a = new Child();
    }
}

Now we’re getting an error: Error:(7, 13) java: incompatible types: test.Child cannot be converted to A.

So, my question is, why doesn’t the compiler see that A is a Parent‘s child? Even if we set <A extends Parent>. And why we can assign values outside of the class, but can’t inside of it? Can I somehow fix this problem?

I know we can replace A with Parent and remove that type parameter or pass new Child() to constructor’s parameters in Main, but it’s because I’m giving a simplified example of the problem.

Advertisement

Answer

Bounded generic type parameters like A extends Something are not writable (as well upper-bounded wild cards ? extends Something). Any attempt to assign anything to the variable a apart from other variable of type A and null will fail.

In order to understand why, let’s consider the following code:

public class Parent {}
public class Child extends Parent {}
public class GrandChild extends Child {}

public class Holder<A extends Parent> {
    public A a;

    public void setA(A a) {
        this.a = a;
    }
}

A in the Holder class is just a place-holder for the type that will be provided at runtime. It’s a way to tell the compiler that we don’t know for now what the type is. But it will be a particular type that has a Parent class in its inheritance chain. The compiler will take that information into account while checking whether the operations done on the variable a are safe.

In the code below, the compiler will disallow to assign an object of Child as a value for a, because it’s incompatible with a type GrandChild (that happens to be the actual type for A). Only instances of GrandChild and its subtype can be assigned without issues.

public static void main(String[] args) {
    Holder<GrandChild> grandChildHolder = new Holder<>();

    grandChildHolder.setA(new GrandChild()); // no issues
    grandChildHolder.setA(new Child()); // compilation error
}

Similarly, the following assignments inside the Holder class will not succeed because type A will be known only Holder class will get instantiated. And compile doesn’t possess information whether it’ll be Child, GrandChild, etc., therefore it will not consider these operations to be safe.

public class Holder<A extends Parent> {
    public A a;
    // instance initialither block (runs when Holder object is being created)
    { 
        a = new Child(); // compilation error - type A could be a GrandChild
        a = new GrandChild(); // compilation error - type A could potentially be represented by class incompatible with GrandChild

        a = null; // no issues because null is a valid value for any type
    }

    public void setA(A a) {
        this.a = a;
    }
}

Upper-bounded generic parameters like A extends Parent are useful when want to make a class or method to be able to work with objects of different types and the same time impose a certain restriction on a range of the valid types in order to access the behavior of the Parent class (let’s assume there are some like work(), goShoping(), etc.).

If you declare the holder without extends clause, just Holder<A> only methods of the Object class (hashCode(), equals(), toString()) will be accessible with variables and parameters of type A.

Also, clause extends Parent will allow the compiler to spot attempts to introduce an invalid type parameter:

Holder<String> stringHolder; // error: 
// type argument String is not within the bounds of type A
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement