¿Por qué no campos abstractos?


100

¿Por qué las clases de Java no pueden tener campos abstractos como pueden tener métodos abstractos?

Por ejemplo: tengo dos clases que extienden la misma clase base abstracta. Cada una de estas dos clases tiene un método que es idéntico excepto por una constante String, que resulta ser un mensaje de error, dentro de ellas. Si los campos pudieran ser abstractos, podría hacer este resumen constante y llevar el método a la clase base. En su lugar, tengo que crear un método abstracto, llamado getErrMsg()en este caso, que devuelve el String, anular este método en las dos clases derivadas, y luego puedo abrir el método (que ahora llama al método abstracto).

¿Por qué no podía hacer que el campo fuera abstracto para empezar? ¿Podría haber sido diseñado Java para permitir esto?


3
Parece que podría haber eludido todo este problema haciendo que el campo no sea constante y simplemente proporcionando el valor a través del constructor, terminando con 2 instancias de una clase en lugar de 2 clases.
Nate

5
Al hacer que los campos sean abstractos en una superclase, se especifica que cada subclase debe tener este campo, por lo que no es diferente a un campo no abstracto.
Peter Lawrey

@peter, no estoy seguro de estar siguiendo tu punto. si se especificó una constante no abstracta en la clase abstracta, entonces su valor también es constante en todas las subclases. si fuera abstracto, entonces su valor tendría que ser implementado / proporcionado por cada subclase. entonces, no sería lo mismo en absoluto.
liltitus

1
@ liltitus27 Creo que mi punto hace 3.5 años era que tener campos abstractos no cambiaría mucho excepto romper la idea de separar al usuario de una interfaz de la implementación.
Peter Lawrey

Esto sería útil porque podría permitir anotaciones de campo personalizadas en la clase secundaria
geg

Respuestas:


103

Puede hacer lo que describió al tener un campo final en su clase abstracta que se inicializa en su constructor (código no probado):

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";
    }
}

Si su clase hija "olvida" inicializar la final a través del superconstructor, el compilador dará una advertencia y un error, como cuando no se implementa un método abstracto.


No tengo un editor para probarlo aquí, pero esto era lo que también iba a publicar ..
Tim

6
"el compilador dará una advertencia". En realidad, el constructor Child estaría intentando usar un constructor noargs inexistente y eso es un error de compilación (no una advertencia).
Stephen C

Y agregar al comentario de @Stephen C, "como cuando un método abstracto no está implementado" también es un error, no una advertencia.
Laurence Gonsalves

4
Buena respuesta, esto funcionó para mí. Sé que ha pasado un tiempo desde que se publicó, pero creo Baseque es necesario declararlo abstract.
Steve Chambers

2
Todavía no me gusta este enfoque (aunque es el único camino a seguir) porque agrega un argumento adicional a un constructor. En la programación de redes, me gusta crear tipos de mensajes, donde cada mensaje nuevo implementa un tipo abstracto pero debe tener un carácter designado único como 'A' para AcceptMessage y 'L' para LoginMessage. Esto asegura que un protocolo de mensaje personalizado pueda poner este carácter distintivo en el cable. Estos están implícitos en la clase, por lo que sería mejor servirlos como campos, no como algo que se pasa al constructor.
Adam Hughes

7

No veo ningún sentido en eso. Puede mover la función a la clase abstracta y simplemente anular algún campo protegido. No sé si esto funciona con constantes pero el efecto es el mismo:

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";
    }
}

Entonces, ¿su punto es que desea hacer cumplir la implementación / anulación / lo que sea errorMsgen las subclases? Pensé que solo querías tener el método en la clase base y no sabías cómo lidiar con el campo entonces.


4
Bueno, podría hacer eso, por supuesto. Y lo había pensado. Pero, ¿por qué tengo que establecer un valor en la clase base cuando sé con certeza que voy a anular ese valor en cada clase derivada? Su argumento también podría usarse para argumentar que tampoco tiene valor permitir métodos abstractos.
Paul Reiners

1
Bueno, de esta manera no todas las subclases tienen que anular el valor. También puedo definir protected String errorMsg;qué hace cumplir de alguna manera que establezco el valor en las subclases. Es similar a esas clases abstractas que realmente implementan todos los métodos como marcadores de posición para que los desarrolladores no tengan que implementar todos los métodos aunque no los necesiten.
Felix Kling

7
Decir protected String errorMsg;no no hace cumplir que se establece el valor en las subclases.
Laurence Gonsalves

1
Si el valor es nulo si no lo establece cuenta como "cumplimiento", ¿cómo llamaría no cumplimiento?
Laurence Gonsalves

9
@felix, hay una diferencia entre la ejecución y los contratos . Todo este post se centra en las clases abstractas, que a su vez representan un contrato que debe ser cumplido por cualquier subclase. entonces, cuando hablamos de tener un campo abstracto, estamos diciendo que cualquier subclase debe implementar el valor de ese campo, por contrato. su enfoque no garantiza ningún valor y no hace explícito lo que se espera, como lo hace un contrato. muy, muy diferente.
liltitus

3

Obviamente, podría haber sido diseñado para permitir esto, pero bajo las sábanas todavía tendría que hacer un envío dinámico y, por lo tanto, una llamada a un método. El diseño de Java (al menos en los primeros días) fue, hasta cierto punto, un intento de ser minimalista. Es decir, los diseñadores intentaron evitar agregar nuevas funciones si podían simularse fácilmente con otras funciones que ya estaban en el lenguaje.


@Laurence: no me resulta obvio que se podrían haber incluido campos abstractos. Es probable que algo así tenga un impacto profundo y fundamental en el sistema de tipos y en la usabilidad del lenguaje. (De la misma manera que la herencia múltiple trae problemas profundos ... que pueden morder al programador de C ++ desprevenido.)
Stephen C

0

Al leer su título, pensé que se refería a miembros de instancia abstractos; y no pude verles de mucha utilidad. Pero los miembros estáticos abstractos es otro asunto completamente diferente.

A menudo he deseado poder declarar un método como el siguiente en Java:

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

Básicamente, me gustaría insistir en que las implementaciones concretas de mi clase principal proporcionan un método de fábrica estático con una firma específica. Esto me permitiría obtener una referencia a una clase concreta Class.forName()y estar seguro de que podría construir una en una convención de mi elección.


1
Sí, bueno ... ¡esto probablemente significa que estás usando demasiado la reflexión!
Stephen C

Me suena como un extraño hábito de programación. Class.forName (..) huele a un defecto de diseño.
whiskeysierra

En estos días, básicamente siempre usamos Spring DI para lograr este tipo de cosas; pero antes de que ese marco se hiciera conocido y popular, especificaríamos clases de implementación en propiedades o archivos XML, luego usaríamos Class.forName () para cargarlos.
Drew Wills

Puedo darte un caso de uso para ellos. La ausencia de "campos abstractos" casi imposible declarar un campo de un tipo genérico en una clase genérica: @autowired T fieldName. Si Tse define como algo así como T extends AbstractController, el borrado de tipo hace que el campo se genere como tipo AbstractControllery no se puede conectar automáticamente porque no es concreto ni único. En general, estoy de acuerdo en que no son de mucha utilidad para ellos y, por lo tanto, no pertenecen al idioma. Con lo que no estaría de acuerdo es que NO tienen uso para ellos.
DaBlick

0

Otra opción es definir el campo como público (final, si lo desea) en la clase base, y luego inicializar ese campo en el constructor de la clase base, dependiendo de la subclase que se esté utilizando actualmente. Es un poco turbio, ya que introduce una dependencia circular. Pero, al menos, no es una dependencia que pueda cambiar, es decir, la subclase existirá o no existirá, pero los métodos o campos de la subclase no pueden influir en el 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;
    }
  }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.