Pourquoi pas des champs abstraits?

100

Pourquoi les classes Java ne peuvent-elles pas avoir de champs abstraits comme elles peuvent avoir des méthodes abstraites?

Par exemple: j'ai deux classes qui étendent la même classe de base abstraite. Ces deux classes ont chacune une méthode identique à l'exception d'une constante String, qui se trouve être un message d'erreur, en leur sein. Si les champs pouvaient être abstraits, je pourrais créer cette constante abstraite et faire remonter la méthode dans la classe de base. Au lieu de cela, je dois créer une méthode abstraite, appelée getErrMsg()dans ce cas, qui renvoie la chaîne, remplacer cette méthode dans les deux classes dérivées, puis je peux extraire la méthode (qui appelle maintenant la méthode abstraite).

Pourquoi ne pourrais-je pas simplement faire le champ abstrait pour commencer? Java aurait-il été conçu pour permettre cela?

Paul Reiners
la source
3
Il semble que vous auriez pu éviter tout ce problème en rendant le champ non constant et en fournissant simplement la valeur via le constructeur, pour aboutir à 2 instances d'une classe plutôt que 2 classes.
Nate le
5
En rendant un champ abstrait dans une super classe, vous avez spécifié que chaque sous-classe doit avoir ce champ, donc ce n'est pas différent d'un champ non abstrait.
Peter Lawrey
@peter, je ne suis pas sûr de suivre votre point de vue. si une constante non abstraite a été spécifiée dans la classe abstraite, alors sa valeur est également constante dans toutes les sous-classes. s'il était abstrait, alors sa valeur devrait être implémentée / fournie par chaque sous-classe. donc, ce ne serait pas du tout pareil.
liltitus27
1
@ liltitus27 Je pense que mon argument d'il y a 3,5 ans était que le fait d'avoir des champs abstraits ne changerait pas grand-chose sauf briser l'idée de séparer l'utilisateur d'une interface de l'implémentation.
Peter Lawrey
Cela serait utile car cela pourrait autoriser des annotations de champs personnalisés dans la classe enfant
geg

Réponses:

103

Vous pouvez faire ce que vous avez décrit en ayant un champ final dans votre classe abstraite qui est initialisé dans son constructeur (code non testé):

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 votre classe enfant "oublie" d'initialiser la finale via le super constructeur, le compilateur donnera un avertissement et une erreur, tout comme lorsqu'une méthode abstraite n'est pas implémentée.

rsp
la source
Je n'ai pas d'éditeur pour l'essayer ici, mais c'est ce que j'allais publier aussi ..
Tim
6
"le compilateur donnera un avertissement". En fait, le constructeur Child essaierait d'utiliser un constructeur noargs inexistant et c'est une erreur de compilation (pas un avertissement).
Stephen C
Et ajouter au commentaire de @Stephen C, "comme lorsqu'une méthode abstraite n'est pas implémentée" est aussi une erreur, pas un avertissement.
Laurence Gonsalves
4
Bonne réponse - cela a fonctionné pour moi. Je sais que cela fait un moment qu'il n'a pas été publié mais je pense qu'il Basefaut le déclarer abstract.
Steve Chambers
2
Je n'aime toujours pas cette approche (même si c'est la seule voie à suivre) car elle ajoute un argument supplémentaire à un constructeur. Dans la programmation réseau, j'aime créer des types de message, où chaque nouveau message implémente un type abstrait mais doit avoir un caractère désigné unique comme «A» pour AcceptMessage et «L» pour LoginMessage. Celles-ci garantissent qu'un protocole de message personnalisé pourra mettre ce caractère distinctif sur le câble. Ceux-ci sont implicites pour la classe, ils seraient donc mieux servis comme champs, pas comme quelque chose passé au constructeur.
Adam Hughes
7

Je ne vois aucun intérêt à cela. Vous pouvez déplacer la fonction vers la classe abstraite et simplement remplacer un champ protégé. Je ne sais pas si cela fonctionne avec des constantes mais l'effet est le même:

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

Donc, votre point est que vous voulez appliquer l'implémentation / le remplacement / quoi que ce soit errorMsgdans les sous-classes? Je pensais que vous vouliez juste avoir la méthode dans la classe de base et que vous ne saviez pas comment gérer le champ à ce moment-là.

Félix Kling
la source
4
Eh bien, je pourrais le faire bien sûr. Et j'y avais pensé. Mais pourquoi dois-je définir une valeur dans la classe de base alors que je sais pertinemment que je vais remplacer cette valeur dans chaque classe dérivée? Votre argument pourrait également être utilisé pour faire valoir qu'il n'y a aucune valeur non plus à autoriser des méthodes abstraites.
Paul Reiners
1
Eh bien, de cette façon, toutes les sous-classes ne doivent pas remplacer la valeur. Je peux aussi simplement définir protected String errorMsg;ce qui impose d'une manière ou d'une autre que je définis la valeur dans les sous-classes. Il est similaire à ces classes abstraites qui implémentent en fait toutes les méthodes en tant qu'espaces réservés afin que les développeurs n'aient pas à implémenter chaque méthode même s'ils n'en ont pas besoin.
Felix Kling
7
Dire protected String errorMsg;n'impose pas que vous définissiez la valeur dans les sous-classes.
Laurence Gonsalves
1
Si avoir la valeur nulle si vous ne la définissez pas compte comme "application", alors qu'appelleriez-vous non-application?
Laurence Gonsalves
9
@felix, il y a une différence entre l' exécution et les contrats . tout ce message est centré sur les classes abstraites, qui à leur tour représentent un contrat qui doit être rempli par toute sous-classe. Ainsi, lorsque nous parlons d'avoir un champ abstrait, nous disons que toute sous-classe doit implémenter la valeur de ce champ, par contrat. votre approche ne garantit aucune valeur et ne rend pas explicite ce qui est attendu, comme le fait un contrat. très, très différent.
liltitus27
3

Évidemment, il aurait pu être conçu pour permettre cela, mais sous les couvertures, il devrait encore faire une répartition dynamique, et donc un appel de méthode. La conception de Java (du moins dans les premiers jours) était, dans une certaine mesure, une tentative d'être minimaliste. Autrement dit, les concepteurs ont essayé d'éviter d'ajouter de nouvelles fonctionnalités si elles pouvaient être facilement simulées par d'autres fonctionnalités déjà présentes dans le langage.

Laurence Gonsalves
la source
@Laurence - il n'est pas évident pour moi que des champs abstraits auraient pu être inclus. Quelque chose de ce genre est susceptible d'avoir un impact profond et fondamental sur le système de type et sur la convivialité du langage. (De la même manière que l'héritage multiple apporte de profonds problèmes ... qui peuvent mordre le programmeur C ++ imprudent.)
Stephen C
0

En lisant votre titre, je pensais que vous faisiez référence aux membres de l'instance abstraite; et je ne voyais pas beaucoup d’utilité pour eux. Mais les membres statiques abstraits sont une tout autre affaire.

J'ai souvent souhaité pouvoir déclarer une méthode comme la suivante en Java:

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

Fondamentalement, je voudrais insister sur le fait que les implémentations concrètes de ma classe parente fournissent une méthode de fabrique statique avec une signature spécifique. Cela me permettrait d'obtenir une référence à une classe concrète avec Class.forName()et d'être certain que je pourrais en construire une dans une convention de mon choix.

Drew Wills
la source
1
Ouais bien ... cela signifie probablement que vous utilisez beaucoup trop la réflexion !!
Stephen C
Cela me semble être une étrange habitude de programmation. Class.forName (..) sent comme un défaut de conception.
whiskeysierra
De nos jours, nous utilisons toujours Spring DI pour accomplir ce genre de chose; mais avant que ce framework ne devienne connu et populaire, nous spécifions les classes d'implémentation dans les propriétés ou les fichiers XML, puis utilisons Class.forName () pour les charger.
Drew Wills le
Je peux vous donner un cas d'utilisation pour eux. L'absence de « champs abstraits » presque impossible de déclarer un champ d'un type générique dans une classe générique abstraite: @autowired T fieldName. Si Test défini comme quelque chose comme T extends AbstractController, l'effacement de type provoque la génération du champ en tant que type AbstractControlleret qui ne peut pas être câblé automatiquement car il n'est ni concret ni unique. Je suis généralement d'accord pour dire qu'ils ne sont pas très utiles et qu'ils n'appartiennent donc pas à la langue. Ce avec quoi je ne suis pas d'accord, c'est qu'il n'y a AUCUNE utilité pour eux.
DaBlick
0

Une autre option consiste à définir le champ comme public (final, si vous le souhaitez) dans la classe de base, puis à initialiser ce champ dans le constructeur de la classe de base, en fonction de la sous-classe actuellement utilisée. C'est un peu louche, en ce sens qu'il introduit une dépendance circulaire. Mais, au moins, ce n'est pas une dépendance qui peut jamais changer - c'est-à-dire que la sous-classe existera ou n'existera pas, mais les méthodes ou les champs de la sous-classe ne peuvent pas influencer la valeur 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
la source