Por que não campos abstratos?

100

Por que as classes Java não podem ter campos abstratos como podem ter métodos abstratos?

Por exemplo: eu tenho duas classes que estendem a mesma classe base abstrata. Cada uma dessas duas classes tem um método idêntico, exceto por uma constante String, que por acaso é uma mensagem de erro, dentro delas. Se os campos pudessem ser abstratos, eu poderia tornar essa constante abstrata e puxar o método para a classe base. Em vez disso, tenho que criar um método abstrato, chamado getErrMsg()neste caso, que retorna String, sobrescrever esse método nas duas classes derivadas e, então, posso puxar o método (que agora chama o método abstrato).

Por que eu não poderia simplesmente tornar o campo abstrato para começar? O Java poderia ter sido projetado para permitir isso?

Paul Reiners
fonte
3
Parece que você poderia ter evitado todo esse problema tornando o campo não constante e simplesmente fornecendo o valor por meio do construtor, resultando em 2 instâncias de uma classe em vez de 2 classes.
Nate
5
Ao tornar os campos abstratos em uma superclasse, você especifica que cada subclasse deve ter esse campo, portanto, isso não é diferente de um campo não abstrato.
Peter Lawrey
@peter, não tenho certeza se estou seguindo seu ponto. se uma constante não abstrata foi especificada na classe abstrata, então seu valor também é constante em todas as subclasses. se fosse abstrato, então seu valor teria que ser implementado / fornecido por cada subclasse. então, não seria o mesmo de forma alguma.
liltitus 27 de
1
@ liltitus27 Acho que meu ponto, 3,5 anos atrás, era que ter campos abstratos não mudaria muito, exceto quebrar toda a ideia de separar o usuário de uma interface da implementação.
Peter Lawrey de
Isso seria útil porque poderia permitir anotações de campo personalizado na classe filha
geg

Respostas:

103

Você pode fazer o que descreveu tendo um campo final em sua classe abstrata que é inicializado em seu construtor (código não testado):

abstract class Base {

    final String errMsg;

    Base(String msg) {
        errMsg = msg;
    }

    abstract String doSomething();
}

class Sub extends Base {

    Sub() {
        super("Sub message");
    }

    String doSomething() {

        return errMsg + " from something";
    }
}

Se sua classe filha "esquecer" de inicializar o final por meio do super construtor, o compilador dará um aviso de erro, assim como quando um método abstrato não é implementado.

rsp
fonte
Não tenho um editor para experimentar aqui, mas era isso que eu ia postar também ..
Tim
6
"o compilador dará um aviso". Na verdade, o construtor Child tentaria usar um construtor noargs inexistente e isso é um erro de compilação (não um aviso).
Stephen C
E adicionar ao comentário de @Stephen C, "como quando um método abstrato não é implementado" também é um erro, não um aviso.
Laurence Gonsalves
4
Boa resposta - isso funcionou para mim. Eu sei que já faz um tempo desde que foi postado, mas acho que Baseprecisa ser declarado abstract.
Steve Chambers
2
Ainda não gosto dessa abordagem (embora seja a única alternativa) porque adiciona um argumento extra a um construtor. Na programação de rede, gosto de fazer tipos de mensagem, onde cada nova mensagem implementa um tipo abstrato, mas deve ter um caractere exclusivo designado como 'A' para AcceptMessage e 'L' para LoginMessage. Isso garante que um protocolo de mensagem personalizado seja capaz de colocar esse caractere distinto na rede. Eles estão implícitos para a classe, portanto, seriam melhor servidos como campos, não como algo passado para o construtor.
Adam Hughes
7

Não vejo sentido nisso. Você pode mover a função para a classe abstrata e apenas substituir algum campo protegido. Não sei se funciona com constantes, mas o efeito é o mesmo:

public abstract class Abstract {
    protected String errorMsg = "";

    public String getErrMsg() {
        return this.errorMsg;
    }
}

public class Foo extends Abstract {
    public Foo() {
       this.errorMsg = "Foo";
    }

}

public class Bar extends Abstract {
    public Bar() {
       this.errorMsg = "Bar";
    }
}

Então, o que você quer dizer é que você quer impor a implementação / substituição / qualquer coisa errorMsgnas subclasses? Achei que você só queria ter o método na classe base e não sabia como lidar com o campo na época.

Felix Kling
fonte
4
Bem, eu poderia fazer isso, é claro. E eu tinha pensado nisso. Mas por que tenho que definir um valor na classe base quando sei com certeza que vou substituir esse valor em cada classe derivada? Seu argumento também pode ser usado para argumentar que também não há valor em permitir métodos abstratos.
Paul Reiners
1
Bem, desta forma, nem toda subclasse tem que substituir o valor. Também posso definir o protected String errorMsg;que impõe de alguma forma que eu configure o valor nas subclasses. É semelhante àquelas classes abstratas que realmente implementam todos os métodos como espaços reservados para que os desenvolvedores não tenham que implementar todos os métodos, embora não precisem deles.
Felix Kling
7
Dizendo protected String errorMsg;que não impor que você definir o valor em subclasses.
Laurence Gonsalves
1
Se ter o valor nulo se você não defini-lo conta como "aplicação", o que você chamaria de não aplicação?
Laurence Gonsalves
9
@felix, há uma diferença entre execução e contratos . toda esta postagem é centrada em classes abstratas, que por sua vez representam um contrato que deve ser cumprido por qualquer subclasse. então, quando falamos em ter um campo abstrato, estamos dizendo que qualquer subclasse deve implementar o valor daquele campo, por contrato. sua abordagem não garante nenhum valor e não torna explícito o que é esperado, como um contrato faz. muito, muito diferente.
liltitus27 de
3

Obviamente, ele poderia ter sido projetado para permitir isso, mas nos bastidores ainda teria que fazer o despacho dinâmico e, portanto, uma chamada de método. O design de Java (pelo menos nos primeiros dias) foi, até certo ponto, uma tentativa de ser minimalista. Ou seja, os designers tentaram evitar a adição de novos recursos se eles pudessem ser facilmente simulados por outros recursos já existentes na linguagem.

Laurence Gonsalves
fonte
@Laurence - é não óbvio para mim que os campos abstratos poderia ter sido incluído. É provável que algo assim tenha um impacto profundo e fundamental no sistema de tipos e na usabilidade da linguagem. (Da mesma forma que a herança múltipla traz problemas profundos ... que podem incomodar o programador C ++ desavisado.)
Stephen C
0

Lendo seu título, pensei que você estava se referindo a membros de instância abstratos; e eu não via muito uso para eles. Mas membros estáticos abstratos é outra questão completamente.

Muitas vezes desejei poder declarar um método como o seguinte em Java:

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

Basicamente, gostaria de insistir que as implementações concretas de minha classe pai fornecem um método de fábrica estático com uma assinatura específica. Isso me permitiria obter uma referência a uma classe concreta com Class.forName()e ter certeza de que poderia construir uma em uma convenção de minha escolha.

Drew Wills
fonte
1
Sim, bem ... isso provavelmente significa que você está usando muito a reflexão !!
Stephen C
Parece um estranho hábito de programação para mim. Class.forName (..) cheira a uma falha de design.
whiskeysierra
Hoje em dia, basicamente sempre usamos o Spring DI para realizar esse tipo de coisa; mas antes que essa estrutura se tornasse conhecida e popular, especificaríamos classes de implementação em propriedades ou arquivos XML e, em seguida, usaríamos Class.forName () para carregá-los.
Drew Wills
Eu posso te dar um caso de uso para eles. A ausência de "campos abstratos" quase impossível declarar um campo de um tipo genérico em uma classe genérica resumo: @autowired T fieldName. Se Tfor definido como algo como T extends AbstractController, o apagamento do tipo faz com que o campo seja gerado como um tipo AbstractControllere não pode ser autowired porque não é concreto e não é único. Em geral, concordo que não têm muita utilidade para eles e, portanto, não pertencem ao idioma. O que eu não concordaria é que NÃO há uso para eles.
DaBlick
0

Outra opção é definir o campo como público (final, se preferir) na classe base e, em seguida, inicializar esse campo no construtor da classe base, dependendo de qual subclasse está sendo usada no momento. É um pouco obscuro, pois introduz uma dependência circular. Mas, pelo menos, não é uma dependência que pode mudar - ou seja, a subclasse existirá ou não, mas os métodos ou campos da subclasse não podem influenciar o valor de field.

public abstract class Base {
  public final int field;
  public Base() {
    if (this instanceof SubClassOne) {
      field = 1;
    } else if (this instanceof SubClassTwo) {
      field = 2;
    } else {
      // assertion, thrown exception, set to -1, whatever you want to do 
      // to trigger an error
      field = -1;
    }
  }
}
ragerdl
fonte