Pergunta

I have written a converter structure in Java for which I'd like to create a factory.

My basic idea is that I pass in a class of a valid type and receive a valid subtype converter but fail with the generics and inheritance part.

This is my interface:

interface ItemRequestConverterFactory {

  public <IR extends ItemRequest> ItemRequestConverter<IR> newInstance(Class<IR> clazz);

}

and the implementation:

public class DefaultItemRequestConverterFactory implements ItemRequestConverterFactory {

  @Override
  public <IR extends ItemRequest> ItemRequestConverter<IR> newInstance(Class<IR> clazz) {
    if (clazz.equals(CreatePartRequestConverter.class))
    return new CreatePartRequestConverter();

    return null;
  }

}

Unfortunately, Eclipse says: "Type mismatch: cannot convert from CreatePartRequestConverter to ItemRequestConverter".

Where is the problem in my generic signature?

Foi útil?

Solução

You seem to have a mismatch between the type parameters and the types that they should actually represent: You are passing a Class<IR> to the newInstance method. This is a class that represents an ItemRequest. However, you are comparing this class instance to a class that is probably an implementation of the ItemRequestConverter interface.

Although this compiles when you add an appropriate type parameter to the CreatePartRequestConverter class, it's probably not what you want to achieve:

interface ItemRequestConverter<IR extends ItemRequest>{}
interface ItemRequest{}
interface ItemRequestConverterFactory 
{
    public <IR extends ItemRequest> ItemRequestConverter<IR> newInstance(Class<IR> itemRequestClass);
}
class CreatePartRequestConverter<IR extends ItemRequest> implements ItemRequestConverter<IR>
{
}

class DefaultItemRequestConverterFactory implements ItemRequestConverterFactory 
{

    @Override
    public <IR extends ItemRequest> ItemRequestConverter<IR> newInstance(Class<IR> itemRequestClass) 
    {
        // Does not make sense: Comparing ItemRequest class with something
        // that is probably an implementation of ItemRequestConverter
        if (itemRequestClass.equals(CreatePartRequestConverter.class))
        {
            return new CreatePartRequestConverter<IR>();
        }
        return null;
    }
}

Depending on what you actually want to achieve, you could pass the ItemRequestConverter class to the factory method (and parameterize the method accordingly) :

interface ItemRequestConverter<IR extends ItemRequest>{}
interface ItemRequest{}
interface ItemRequestConverterFactory 
{
    public <IRC extends ItemRequestConverter<?>> ItemRequestConverter<?> newInstance(Class<IRC> itemRequestConverterClass);
}
class CreatePartRequestConverter implements ItemRequestConverter<ItemRequest>
{
}

class DefaultItemRequestConverterFactory implements ItemRequestConverterFactory 
{
    @Override
    public <IRC extends ItemRequestConverter<?>> ItemRequestConverter<?> newInstance(
        Class<IRC> itemRequestConverterClass)
    {
        if (itemRequestConverterClass.equals(CreatePartRequestConverter.class))
        {
            return new CreatePartRequestConverter();
        }
        return null;
    }
}

If you also need the information about the ItemRequest class, the method signatures may start to become a little bit nasty, and one would have to analyze in more detail where and how this type information should be provided or used.


EDIT for the comment:

I think that this is not possible in a (compile-time) type-checked way. The problem here is that at the point where you create and return the new ItemRequestConverter, you can not say for sure that it is an ItemRequestConverter whose type parameter is IR. Or the other way around: You can't detect at compile-time that the type parameter of CreatePartRequestConverter (namely, CreatePartRequest) is the same as IR.

Based on the code snippets discussed so far, I think that it should be possible to simply cast in this case. The responsibility of making sure that the cast is valid is then left to the one who implements the "runtime-type-check":

if (itemRequestClass.equals(CreatePartRequest.class))
{
    CreatePartRequestConverter result = new CreatePartRequestConverter();
    return (ItemRequestConverter<IR>) result;
}

because nothing prevents you from writing

//                          !!!
if (itemRequestClass.equals(SomeCompletelyDifferentRequest.class))
{
    CreatePartRequestConverter result = new CreatePartRequestConverter();
    return (ItemRequestConverter<IR>) result;
}

So it should be valid to do this:

interface ItemRequestConverter<IR extends ItemRequest>{}
interface ItemRequest{}
interface ItemRequestConverterFactory 
{
    public <IR extends ItemRequest> ItemRequestConverter<IR> 
        newInstance(Class<IR> itemRequestClass);
}

class CreatePartRequest implements ItemRequest {}
class CreatePartRequestConverter 
    implements ItemRequestConverter<CreatePartRequest> {}

class DefaultItemRequestConverterFactory implements ItemRequestConverterFactory 
{
    @Override
    public <IR extends ItemRequest> ItemRequestConverter<IR> newInstance(
            Class<IR> itemRequestClass)
    {
        if (itemRequestClass.equals(CreatePartRequest.class))
        {
            CreatePartRequestConverter result = new CreatePartRequestConverter();
            return (ItemRequestConverter<IR>) result;
        }
        return null;
    }
}

public class GenericFactoryTest
{
    public static void main(String[] args)
    {
        ItemRequestConverterFactory factory = null;
        ItemRequestConverter<CreatePartRequest> converter = 
            factory.newInstance(CreatePartRequest.class);        
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top