views:

187

answers:

3

I defined a Java interface to represent the ability of an object to copy itself (no, I don't want to use Cloneable, but thanks for the suggestion ;-). It's generic:

public interface Copiable<T> {
    T copy();
}

An object will only make copies of its own type:

public class Foo implements Copiable<Foo> {
    Foo copy() { return new Foo(); }
}

Now, I'm reading Class<?> objects from a non-generic API (Hibernate metadata, if that is of interest to anyone), and, if they are Copiable, I want to register them with one of my components. The register method has the following signature:

<T extends Copiable<T>> register(Class<T> clazz);

My problem is, how do I cast the class before passing it to the method? What I'm currently doing is:

Class<?> candidateClass = ...
if (Copiable.class.isAssignableFrom(candidateClass)) {
    register(candidateClass.asSubclass(Copiable.class));
}

This does what I want, but with a compiler warning. And I don't think I'll be able to express the recursive bound in a runtime cast.

Is there a way to rework my code to make it typesafe?

Thanks

A: 

There's no way to cast non-generic API to generic and not get a warning. You can use @SuppressWarnings("unchecked") annotation to suppress it.
Class<?> is rather pointless in this case, too - you can use non-generic Class candidateClass instead.

ChssPly76
Actually, there is a way: if the type parameter of my method was non-recursive, e.g <T extends SomeInterface>, candidateClass.asSubclass(SomeInterface.class) would give me the correct type. Surely, asSubclass can throw a ClassCastException at runtime, but that would never happen as I test the type with isAssignableFrom => no warning.My question is whether I can do that with a recursive type parameter.
Olivier
Btw, Class<?> is not pointless: if I used Class I'd get another warning for using a raw type.
Olivier
That's not what I meant. You've said in your question that you're obtaining `candidateClass` from non-generic API. Or did I misinterpret that? If it is indeed non-generic then **by definition** there's no way to convert it to generic without a warning. As far as what I meant by "pointless", `Class<?>` has the same semantic meaning as non-generic `Class` and if you're going to be suppressing the warning anyway you just might use the latter.
ChssPly76
The warning is not on the first line of the last snippet. It's on the third line. Again, the issue was not about obtaining the class from a non-generic API: if I didn't have the recursive bound, I could write the code in a completely type-safe way, without suppressing any warning.
Olivier
A: 

A runtime cast to <? extends Copiable<?> fails (in the register() call) because there's no way to declare that the first ? and the second ? are the same (unknown) thing.

The asSubclass() call gives you a compiler warning for pretty much the same reason -- because the generics have been erased, there's no way at runtime to prove that a Copiable is a Copiable<AnythingInParticular>. You've proved that it's a Copiable, but you haven't proved that it's a Copiable<Itself>.

I think the best you can hope for is to localize the warnings to one messy method, and add @SuppressWarnings("unchecked") and some comments.

Do you know for sure this will work at runtime? I get the feeling the compiler's telling the truth here. Do the classes you're getting from Hibernate actually extend Copiable, or do they just happen to declare methods that match the Copiable interface? If it's the latter, then -- Java being a strongly typed language -- your isAssignableFrom() is always going to return false anyway.

David Moles
Yes, this confirms what I intuited. Then I'll assume that if a class implements Copiable, it implements Copiable<Itself>, and I can safely ignore the warning. This is acceptable since all the classes are under my control. I accept this answer, thanks!
Olivier
And yes, that works at runtime. The classes I'm getting from Hibernate are the classes of my entities, where I declare the interface.
Olivier
A: 

Maybe you could apply the super type token paradigm somehow? Why do you need to constrain your register()? I think the problem is that you use Class.forName and it is too much a runtime thing.

kd304
Nice link indeed, but I don't think it will solve my problem. I constrain register, well, to express a constraint on the type of classes that will be accepted :-) Granted, I will probably end up suppressing a warning, but in the client code. I mean, if I write a call to register with a class literal, it provide the correct type safety. The problem is that my client doesn't know the class until runtime.
Olivier