Search code examples
javagenericsreturntype-safety

usage of generics as return type


I've a structure like this:

abstract class MyDomain{...}
abstract class FooDomain extends MyDomain{...}
abstract class BarDomain extends MyDomain{...}
class FirstConcreteBarDomain extends BarDomain{...}
class SecondConcreteBarDomain extends BarDomain{...}

I need a factory that creates MyDomain objects. My first attempt was this:

public interface ISpecializedObjectsFactory {
    public <T extends MyDomain> T create(Class<?> clazz);
}

Implementend as:

public class FirstSpecializedObjectsFactory implements ISpecializedObjectsFactory {

    @Override
    public <T extends MyDomain> T create(Class<?> clazz) {
        if(clazz.equals(BarDomain.class))
            return new FirstBarDomain();
        throw new InvalidParameterException();
    }

Same for the SecondBarDomain.

FIRST QUESTION: Why this is generating an error that says that it cannot cast FirstBarDomain to T?

After this error I've introduced a cast: return (T) new FirstBarDomain();.

The problem is that the cast is unsafe and I want to be confident for the result, so I've introduced another constraint (assuming that each MyDomain object have always 2 levels of derivation):

public <T extends AnagrafeDomain, S extends T> S create(Class<T> clazz)

SECOND QUESTION: Assuming that this factory is the only entry point where MyDomain objects are created, and that the calls to the factory never use the concrete classes (but are always like: BarDomain subj = SpecializedObjectsFactory.getFactory().create(BarDomain.class);), the question is: is this new version safe?


Solution

  • The reason the cast is unsafe is because of this particular line:

    public <T extends MyDomain> T create(Class<?> clazz) {
    

    This infers the return type from the call site; in other words, consider the following class:

    public abstract class MyFakeDomain extends MyDomain { }
    

    The following code will then compile, but fail at runtime:

    ISpecializedObjectsFactory factory = new FirstSpecializedObjectsFactory();
    MyFakeDomain broken = factory.create(BarDomain.class);
    

    This will throw a ClassCastException because of the type inference; the inferred type will be MyFakeDomain, resulting in an attempt to cast FirstBarDomain to MyFakeDomain, which is an illegal cast - hence the unsafe warning.

    The type inference is also the reason why the cast must be present; whilst FirstBarDomain is definitely a subclass of MyDomain, we do not know if it is of type T, as T could be any MyDomain subclass, not necessarily FirstBarDomain.

    However, if the caller is careful, your code will work fine - whether you consider this acceptable or not is up to you.

    This gives us the answer to your second question: using BarDomain as the type to be inferred will not always be safe, as it could be another subclass of MyDomain. The only type that would be always safe here is MyDomain - however, if you are planning on only using MyDomain as the type, you might as well remove the generic type bound and just make the return type MyDomain.