Dans un constructeur en Java, si vous voulez appeler un autre constructeur (ou un super constructeur), elle doit être la première ligne dans le constructeur. Je suppose que c'est parce que vous ne devriez pas être autorisé à modifier toutes les variables d'instance, avant que l'autre constructeur s'exécute. Mais pourquoi ne pouvez-vous pas avoir une déclaration avant le constructeur de délégation, afin de calculer la valeur complexe de l'autre fonction? Je ne peux pas penser à une bonne raison, et j'ai frappé quelques cas réels où j'ai écrit certains laid code de contourner cette limitation.
Donc, je me demandais simplement:
- Est-il une bonne raison de cette limitation?
- Est-il prévu de permettre à l'avenir de cette versions de Java? (Ou Soleil a définitivement dit ce n'est pas qui va se passer?)
Pour un exemple de ce dont je parle, examiner certains code que j'ai écrit que j'ai donné dans ce StackOverflow réponse. Dans ce code, j'ai un BigFraction classe, qui a un BigInteger numérateur et un BigInteger dénominateur. "Canoniques" constructeur est l' BigFraction(BigInteger numerator, BigInteger denominator)
formulaire. Pour tous les autres constructeurs, je viens de convertir les paramètres d'entrée à BigIntegers, et d'appeler la "canonique" du constructeur, parce que je ne veux pas dupliquer tout le travail.
Dans certains cas, c'est facile; par exemple, le constructeur qui prend deux long
s est trivial:
public BigFraction(long numerator, long denominator)
{
this(BigInteger.valueOf(numerator), BigInteger.valueOf(denominator));
}
Mais dans d'autres cas, c'est plus difficile. Envisager le constructeur qui prend un BigDecimal:
public BigFraction(BigDecimal d)
{
this(d.scale() < 0 ? d.unscaledValue().multiply(BigInteger.TEN.pow(-d.scale())) : d.unscaledValue(),
d.scale() < 0 ? BigInteger.ONE : BigInteger.TEN.pow(d.scale()));
}
Je trouve cela assez laid, mais il m'aide à éviter la duplication de code. Ce qui suit est ce que j'aimerais faire, mais il est illégal en Java:
public BigFraction(BigDecimal d)
{
BigInteger numerator = null;
BigInteger denominator = null;
if(d.scale() < 0)
{
numerator = d.unscaledValue().multiply(BigInteger.TEN.pow(-d.scale()));
denominator = BigInteger.ONE;
}
else
{
numerator = d.unscaledValue();
denominator = BigInteger.TEN.pow(d.scale());
}
this(numerator, denominator);
}
Mise à jour
Il y a eu de bonnes réponses, mais à ce jour, aucune réponse n'a été fournie que j'en suis entièrement satisfait, mais je ne se soucient pas assez pour commencer une prime, donc je vais répondre à ma propre question (principalement pour se débarrasser de l'ennuyeux "avez-vous pensé de marquage accepté de répondre" message).
Les solutions ont été suggérées sont les suivantes:
- Statique de l'usine.
- J'ai utilisé la classe dans beaucoup d'endroits, de sorte que le code serait briser si j'ai soudain eu débarrasser du public, les constructeurs et est allé avec de valueOf() les fonctions.
- Il se sent comme une solution de contournement à une limitation. Je n'aurais pas d'autres avantages d'une usine parce que cela ne peut pas être sous-classé et parce que les valeurs communes ne sont pas mis en cache/internés.
- Private static "constructeur de l'aide" des méthodes.
- Cela conduit à beaucoup de code de la météorisation.
- Le code est moche parce que, dans certains cas, j'ai vraiment besoin de calculer le numérateur et le dénominateur dans le même temps, et je ne peux pas retourner plusieurs valeurs, à moins que je retourne un
BigInteger[]
ou une sorte de privé intérieur de la classe.
Le principal argument à l'encontre de cette fonctionnalité est que le compilateur aurait pour vérifier que vous n'avez pas l'utilisation de toutes les instances des variables ou des méthodes avant d'appeler la superconstructor, parce que l'objet est dans un état non valide. Je suis d'accord, mais je pense que ce serait plus facile de vérifier que celui qui s'assure que toutes les finales des variables d'instance sont toujours initialisés à chaque constructeur, n'importe quel chemin à travers le code est pris. L'autre argument est que vous simplement ne pouvez pas exécuter du code à l'avance, mais c'est manifestement faux, car le code pour calculer les paramètres de la superconstructor est arriver exécuté quelque part, donc il doit être permis à un bytecode niveau.
Maintenant, ce que j'aimerais voir, est une bonne raison pourquoi le compilateur ne pouvais pas me laisser prendre ce code:
public MyClass(String s) {
this(Integer.parseInt(s));
}
public MyClass(int i) {
this.i = i;
}
Et de le réécrire comme ceci (le bytecode serait essentiellement identiques, je pense):
public MyClass(String s) {
int tmp = Integer.parseInt(s);
this(tmp);
}
public MyClass(int i) {
this.i = i;
}
La seule vraie différence que je vois entre ces deux exemples est que le "tmp
" de la variable lui permet d'être accessible après l'appel de this(tmp)
dans le deuxième exemple. Alors peut-être une syntaxe particulière (similaire à l' static{}
blocs pour l'initialisation de classe) devra être introduite:
public MyClass(String s) {
//"init{}" is a hypothetical syntax where there is no access to instance
//variables/methods, and which must end with a call to another constructor
//(using either "this(...)" or "super(...)")
init {
int tmp = Integer.parseInt(s);
this(tmp);
}
}
public MyClass(int i) {
this.i = i;
}