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.
CodePudding user response:
Upper-bounded generics A extends Something
are not writable. Any attempt to assign anything apart from null
to the variable a
inside the Holder
class 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 and that has a Parent
class in its inheritance chain. And that information compiler will take into account while checking whether the operation done on the variable a
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
).
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 posses 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 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 instantiate the Holder
class with an invalid type:
Holder<String> stringHolder; // error: type argument String is not within the bounds of type A
CodePudding user response:
A
is a [type] parameter. It's not an actual type. Like most classes, you could initialize the member via a parameter in the method constructor.
public class Main {
public static void main(String[] args) {
new Holder<Child>(new Child());
}
}
class Parent {
}
class Child extends Parent {
}
class Holder<A extends Parent> {
public A a;
public Holder(A a) {
this.a = a;
}
}