Reputation: 566
I have a strange problem with the Java compiler. Here is the code:
private <T extends IdentifiedBusinessTransversalEntity> T
getOrCreateTransversalEntity(Class<T> classT, String id) {
...}
private <T extends IdentifiedBusinessDSEntity> T
getOrCreateDSEntity(Class<T> classT, String id) {
...}
public abstract class IdentifiedBusinessDSEntity extends
BusinessDSEntity implements IdentifiedEntity {
...}
public abstract class IdentifiedBusinessTransversalEntity extends
BusinessTransversalEntity implements IdentifiedEntity {
...}
public <T extends IdentifiedEntity> T getOrCreate(Class<T> classT, String id)
{
if (IdentifiedBusinessDSEntity.class.isAssignableFrom(classT))
{
return getOrCreateDSEntity(classT.asSubclass(IdentifiedBusinessDSEntity.class),id);
} else if (IdentifiedBusinessTransversalEntity.class.isAssignableFrom(classT))
{ //must cast explicitly to T here but works well just above and is exactly the same. Strange
return (T) getOrCreateTransversalEntity(classT.asSubclass(IdentifiedBusinessTransversalEntity.class),id);
}
return null;
}
I don't understand why in the getOrCreate
function the compiler allows the first return without (T)
but not in the second return. The error is:
Type mismatch: cannot convert from IdentifiedBusinessTransversalEntity
to T
It should be ok: getOrCreateTRansversalEntity
returns a subclass of IdentifiedBusinessTransversalEntity
which implements IdentifiedEntity
.
The strangest is that it is ok for the first return and this is exactly symmetrical. What is the difference?
Upvotes: 1
Views: 1440
Reputation: 12112
That code should not compile.
I believe that you either have made a mistake which led you to believe that it works or that you have bug in your compiler. Both return-statements in getOrCreate
give compile errors for me in Eclipse 4.5.1 and javac 1.8.0_45.
EDIT: I changed this explanation, I think I misunderstood the problem at fist.
In the explanation I changed the name of the type parameter of getAndCreate
to C
to avoid confusion with other type parameters.
The problem is that the in the asSubclass
method, the information that the class is a subclass of C
is lost; the only information that remains is that the class is a subclass of for example IdentifiedBusinessDSEntity
.
asSubclass
has the following type:
<U> Class<? extends U> asSubclass(Class<U> clazz)
As we can see the original type parameter of the receiver class, T
, is not present in the return type.
getOrCreate
is declared to return C
. That's the reason you need a cast: To reintroduce the type C
to the return value.
asSubclass
We could imagine that asSubclass
had the following type:
<U> Class<? extends U & T> asSubclass(Class<U> clazz)
That would be type safe, and with that return type your code would compile without casts. But multiple type parameters as bounds like this is not allowed in Java.
The following is the code I used to investigate the problem:
class Test {
interface IdentifiedEntity {}
class BusinessDSEntity {}
class BusinessTransversalEntity {}
private <T extends IdentifiedBusinessTransversalEntity> T getOrCreateTransversalEntity(Class<T> classT, String id) {
return null;
}
private <T extends IdentifiedBusinessDSEntity> T getOrCreateDSEntity(Class<T> classT, String id) {
return null;
}
public abstract class IdentifiedBusinessDSEntity
extends BusinessDSEntity implements IdentifiedEntity {}
public abstract class IdentifiedBusinessTransversalEntity
extends BusinessTransversalEntity implements IdentifiedEntity {}
public <C extends IdentifiedEntity> C getOrCreate(Class<C> classT, String id) {
if (IdentifiedBusinessDSEntity.class.isAssignableFrom(classT)) {
// Error here. Note that the type of s does not contain C.
Class<? extends IdentifiedBusinessDSEntity> s = classT.asSubclass(IdentifiedBusinessDSEntity.class);
return getOrCreateDSEntity(s, id);
} else if (IdentifiedBusinessTransversalEntity.class.isAssignableFrom(classT)) {
// Also error here
return getOrCreateTransversalEntity(classT.asSubclass(IdentifiedBusinessTransversalEntity.class), id);
}
return null;
}
}
I can't think of any really elegant solution that preserves much type safety.
One alternative is to pass along the class of the return type to the entity specific methods. This is rather ugly and inconvenient and you have to manually verify that that class really is of a proper type.
It's probably better just to cast the return values, even if you lose some type safety.
Example:
class Test {
interface IdentifiedEntity {}
class BusinessDSEntity {}
class BusinessTransversalEntity {}
private <R extends IdentifiedEntity, T extends IdentifiedBusinessDSEntity>
R getOrCreateDSEntity(Class<T> classT, Class<R> classR, String id)
{
// Verify that classT really is subclass of classR.
classT.asSubclass(classR);
return null;
}
private <R extends IdentifiedEntity, T extends IdentifiedBusinessTransversalEntity>
R getOrCreateTransversalEntity(Class<T> classT, Class<R> classR, String id)
{
// Verify that classT really is subclass of classR.
classT.asSubclass(classR);
return null;
}
public abstract class IdentifiedBusinessDSEntity
extends BusinessDSEntity implements IdentifiedEntity {}
public abstract class IdentifiedBusinessTransversalEntity
extends BusinessTransversalEntity implements IdentifiedEntity {}
public <C extends IdentifiedEntity> C getOrCreate(Class<C> classT, String id) {
if (IdentifiedBusinessDSEntity.class.isAssignableFrom(classT)) {
return getOrCreateDSEntity(classT.asSubclass(IdentifiedBusinessDSEntity.class), classT, id);
} else if (IdentifiedBusinessTransversalEntity.class.isAssignableFrom(classT)) {
return getOrCreateTransversalEntity(classT.asSubclass(IdentifiedBusinessTransversalEntity.class), classT, id);
}
return null;
}
}
Upvotes: 1
Reputation: 11551
I'll give it a go. This explanation is based on observation more than explicit knowledge, so don't be surprised if I am corrected. What I think you are confusing is what the <T extends IdentifiedBusinessDSEntity> T
return type declaration means. It seems to me that this return declaration is creating an entirely new type, not describing an already declared type. In other words, you can have Class1 as Class1 extends ClassA
, but when you return T extends ClassA
you are not describing Class1 but an entirely new class (or type). You can't expect this new class to be the same as Class1, rather it is the same as saying you are returning a ClassX where ClassX extends ClassA
: Class1 and ClassX are not the same classes.
It looks like you are trying to implement a Factory Design pattern with caching. This code does that and should pretty much resemble what you were doing above. Assuming the factory is supposed to generate Interface implementations, there is no need to declare the return type as a class that extends the interface, just return the interface.
public class Factory {
interface I {}
List<I> iCache = new ArrayList<I>();
abstract class ClassA {}
abstract class ClassB {}
class Class1 extends ClassA implements I {}
class Class2 extends ClassB implements I {}
I getOrCreateTypeA() {
for( I cls: iCache ) {
if( cls instanceof ClassA ) return cls;
}
Class1 cls = new Class1();
iCache.add(cls);
return cls;
}
I getOrCreateTypeB() {
for( I cls: iCache ) {
if( cls instanceof ClassB ) return cls;
}
Class2 cls = new Class2();
iCache.add(cls);
return cls;
}
I getOrCreate(Class<?> cls) {
if ( ClassA.class.isAssignableFrom(cls)) {
return getOrCreateTypeA();
} else if ( ClassB.class.isAssignableFrom(cls)) {
return getOrCreateTypeB();
}
return null;
}
void run() {
I classI1 = getOrCreate(Class1.class);
System.out.println(classI1);
I classI2 = getOrCreate(Class2.class);
System.out.println(classI2);
I classI3 = getOrCreate(Class1.class);
System.out.println(classI3);
System.out.println(iCache);
}
public static void main(String... args) {
new Factory().run();
}
}
Upvotes: 0