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