Je viens de tomber sur ce problème et cela m'a pris par surprise.

Super-classe:

public class Foo {

    @Override
    public String toString(){
        return String.format("Result = %s", calculate());
    }
    public double calculate(){
        return 1;
    }
}

Sous-classe:

public class Bar extends Foo {
    @Override
    public String toString(){
        return String.format("%s", super.toString());
    }
    @Override
    public double calculate(){
        return 123.456;
    }
}

Chauffeuse:

public static void main(String[] args) {
    System.out.println(new Bar().toString());
}

La sortie que je souhaite dans ce scénario est 1 Le résultat que j'obtiens est 123.456

Comment empêcher Foo's toString() d'appeler Bar's calculate()?

2
Anthony Audette 3 avril 2017 à 05:16

2 réponses

Meilleure réponse

Si vous ne voulez pas du tout laisser les sous-classes remplacer calculate, marquez-le final:

public final double calculate() {
    ...
}

Si vous voulez les laisser remplacer calculate, mais que vous ne voulez pas utiliser les remplacements à cet emplacement particulier, placez l'implémentation de calculate dans une méthode privée et utilisez la version privée:

public double calculate() {
    return _calculate();
}
private double _calculate() {
    return 1.0;
}
@Override
public String toString(){
    return String.format("Result = %s", _calculate());
}
6
user2357112 supports Monica 3 avril 2017 à 02:21

Vous ne pouvez pas le faire directement - vous avez remplacé calculate() et puisque vous avez un objet Bar, ce sera toujours Bar.calculate() qui sera appelé lorsque calculate() est utilisé 1 . Il serait très déroutant et difficile de créer des designs OoO raisonnables s'il en était autrement!

Si vous voulez vraiment que le comportement soit décrit, la solution habituelle consiste simplement à demander à Foo.toString() d'appeler une méthode d'assistance non remplaçable (par exemple, private ou final) qui implémente la logique que vous avez dans Foo.calculate(). Vous pouvez alors être assuré que toString() se comporte toujours de la même manière, même lorsque Foo est remplacé 2 . Ensuite, vous pouvez implémenter Foo.calculate() en appelant cette méthode d'assistance, en faisant plaisir à DRY dieux.

Bien sûr, vous pourriez vous demander s'il y a un problème avec la conception de votre classe. Le changement suggéré ci-dessus signifie que vous avez la situation inhabituelle où Foo.calculate() est utilisé pour les appels toString() même sur des objets Bar, mais un appel direct à {{X3 }} entraînera le comportement Bar. Ainsi, votre sortie toString() ne correspondra pas à ce que verra toute personne qui appelle calculate(). C'est rarement ce que vous voulez.


1 À l'exception qu'il est possible, dans Bar , d'appeler explicitement la méthode de la superclasse calculate() en utilisant super.calculate(). Cela ne vous aide pas ici, car ce que vous voulez faire est, dans la superclasse, d'empêcher l'appel virtuel à calculate() d'aller ailleurs.

2 Bien sûr, si quelqu'un remplace toString(), alors tous les paris sont ouverts. Vous pouvez toujours le faire final si vous voulez éviter cela.

5
BeeOnRope 3 avril 2017 à 02:28