Doubt with inheritance in Java method

6

I have the interface below

public interface BaseRelatorioDTO extends Serializable {

    public BaseFiltroDTO getFiltro();

    public List<? extends BaseRespostasDTO> getRespostas();

}

And I would like to create the method

public void setRespostas(final List<? extends BaseRespostasDTO> respostas);

However when creating this method, all classes that implement BaseRelatorioDTO and already have this method begin to give the error

  

Name clash: The method setRespostas(List<? extends RespostaHorariosDTO>) of type RelatorioHorariosDTO has the same erasure as setRespostas(List<? extends BaseRespostasDTO>) of type BaseRelatorioDTO but does not override it.

Here is an example of one of the classes:

public class RelatorioHorariosDTO implements BaseRelatorioDTO {

    private static final long serialVersionUID = -3828618335258371680L;

    private FiltroHorariosDTO filtro = new FiltroHorariosDTO();
    private List<RespostaHorariosDTO> respostas = new ArrayList<RespostaHorariosDTO>();

    @Override
    public FiltroHorariosDTO getFiltro() {
        return this.filtro;
    }

    @Override
    public List<RespostaHorariosDTO> getRespostas() {
        return this.respostas;
    }

    /**
     * @param respostasParam the respostas to set
     */
    public void setRespostas(final List<RespostaHorariosDTO> respostasParam) {
        this.respostas = respostasParam;
    }
}

If you look at my setRespostas method, it expects as a parameter a list of RespostaHorariosDTO , this class being written as below:

public class RespostaHorariosDTO implements BaseRespostasDTO {

    private static final long serialVersionUID = 5505724855293262084L;

    // Atributos e métodos acessores
}

What I am doing wrong that the method can not be declared in the interface so that I compel all classes that implement BaseRelatoriosDTO implement the method setRespostas ?

    
asked by anonymous 28.03.2014 / 00:08

2 answers

3

The problem is that Java discards the generic types after compilation (such as erasure that the error is mentioning), so that two functions with the same signature varying only the generic types are interpreted as the same function. Example:

interface A {
    public void foo(int x);           // OK
    public void bar(List<Integer> x); // Após compilação vira:
                                      // public void bar(List x)
}

class B implements A {
    public void foo(long x) { ... }       // Não tem problema: é overload, não override

    public void bar(List<Long> x) { ... } // Após compilação vira:
                                          // public void bar(List x)
                                          // Ops, é igual o da interface, então é override
                                          // Mas o tipo genérico é diferente... E agora?
}

Unfortunately, I do not think you can have this method the way you would like it to. By the Liskov replacement principle , if RelatorioHorariosDTO implements BaseRelatorioDTO then you should be able to use an object of that class wherever an instance performing that interface would be expected. So suppose that:

class MeuDTO implements BaseRespostasDTO { ... }
List<MeuDTO> lista = new ArrayList<MeuDTO>();

BaseRelatorioDTO x = new RelatorioHorariosDTO();
x.setRespostas(lista);

The last line is valid: BaseRelatorioDTO.setRespostas accepts any List whose generic type is subtype of BaseRespostasDTO , and lista satisfies this requirement. However, the RelatorioHorariosDTO class only accepts lists with RespostaHorariosDTO , so passing this list would be an error. The compiler can not solve this dilemma, so it forbids constructs of this type.

The alternative is every implementation of BaseRelatorioDTO have a simple method:

public void setRespostas(List<BaseRespostasDTO> respostasParam)

and do casts when needed ... ( example in ideone ) Maybe there are other options, but not I have enough generic experience in Java to indicate a satisfactory alternative.

Update : Why does the compiler accept a different return value in getRespostas , but not a different parameter in setRespostas ? Because in Java the return types are covariates , but the parameters are invariant :

interface C {
    public A foo();
    public void bar(A param);
}

class D implements C {
    public B foo() { ... }            // OK, override em C.foo
    public void bar(B param) { ... }  // O parâmetro é diferente: é overload, não override
                                      // (não importa se é subclasse, ainda assim é overload)
                                      // Faltou void bar(A param) - erro
}

That is, if Java supports covariant input types (almost no language gives - except for Eiffel) then you could write your classes as they are. If Java supported contravariant input types (many languages support), you could use a more general type (eg List<Object> ), but not a more specific type. But as in Java the input is invariant , to be considered override requires that the method parameters be identical to the superclass / interface.

    
28.03.2014 / 05:38
2

This answer is great.

If you'd like a solution for the implementation:

public class RelatorioHorariosDTO implements BaseRelatorioDTO<RespostaHorariosDTO> {

    private static final long serialVersionUID = -3828618335258371680L;

    private FiltroHorariosDTO filtro = new FiltroHorariosDTO();
    private List<RespostaHorariosDTO> respostas = new ArrayList<RespostaHorariosDTO>();

    @Override
    public FiltroHorariosDTO getFiltro() {
        return this.filtro;
    }

    @Override
    public List<RespostaHorariosDTO> getRespostas() {
        return this.respostas;
    }

    @Override
    public void setRespostas(List<RespostaHorariosDTO> respostasParam) {
        // TODO Auto-generated method stub

    }
}

And for the interface:

public interface BaseRelatorioDTO<T extends BaseRespostasDTO> extends Serializable {

    public BaseFiltroDTO getFiltro();
    public List<T> getRespostas();
    public void setRespostas(final List<T> respostasParam);

}

Edited

Added generic set method.

    
28.03.2014 / 21:00