Itérer sur un attribut hérité

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Je bloque totalement sur comment faire pour itérer sur un attribut hérité en Java. Soit deux classes, Foo et Bar, cette dernière héritant de la première.

Voici le code correspondant aux deux classes :

1
2
3
abstract class Foo {
    protected ArrayList<Foo> children;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Bar extends Foo {
    public ArrayList<Bar> getLeaves() {
        ArrayList<Bar> leaves = new ArrayList<>();

        for (Bar child : children) {
            if (child.children.size() > 0) {
                leaves.addAll(child.getLeaves());
            } else {
                leaves.add(child);
            }
        }

        return leaves;
    }
}

À la ligne 5, j’ai l’erreur suivante :

1
2
3
Incompatible types.
Required: Foo
Found:    Bar

Je pensais que avec le métamorphisme ça ne posait pas de problème de faire ce genre de chose… Je dois sûrement tout mélanger. :-° Or je dois forcément avoir un Bar car j’utilise des méthodes propres à cette classe.
Comment suis-je sensé faire ce que je souhaite faire ?

Merci pour votre aide !

+0 -0

Bar étends Foo mais Bar n’est pas Foo. Or, là tu lu dis dans Foo ligne 2 que tu as une liste de Foo, et dans Bar ligne 5 que tu veux récupérer un Bar.

Ce dont tu as besoin, c’est de dire dans Foo que children est une liste de « Foo ou n’importe lequel de ses descendants ».

La notion théorique derrière, c’est les notions de covariance et de contravariance. Je te laisse chercher et revenir si tu as d’autres questions.

PS : en fait, même pas sûr que dans ce cas ça fonctionne directement – la flemme de faire le test – peut-être que paramétriser Foo serait plus propre. Dans tous les cas, d’un point de vue modèle objet, ça semble vachement bancal (les noms de variables utilisés n’aident pas).

Bonjour,

Ton problème, c’est que tu itères sur des objets de type Bar alors que ’children’ est une liste de Foo. Un Bar est effectivement un Foo, mais tous les Foo ne sont pas des Bar.

Tu dois donc faire

1
for (Foo child : children)

et changer le type de ta liste.

Ensuite, s’ils doivent absolument être des Bar, tu peux utiliser instanceof et ensuite caster ton objet (et donc pas besoin de changer le type de ta liste).


Grillé :P

Désolé pour les noms qui n’aident pas. :-°

Effectivement, merci pour votre aide, il suffisait de caster après l’objet :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    public ArrayList<Bar> getLeaves() {
        ArrayList<Bar> leaves = new ArrayList<>();

        for (Foo child : children) {
            if (child.children.size() > 0) {
                leaves.addAll(((Bar)child).getLeaves());
            } else {
                leaves.add((Bar)child);
            }
        }

        return leaves;
    }

Ce qui fonctionne parfaitement, merci à vous !

Euh, ton cast est un peu sauvage là !

Il faudrait que tu penses à vérifier le type avant.


Sinon, si ta classe Foo sert juste à contenir une ArrayList du type qui hérite de Foo, tu peux faire une tambouille comme suit (mais si tu ne sais pas ce que ça fait, ni ce que tu fais, évite):

1
2
3
public abstract class Foo<T> {
  ArrayList<T> children;
}
1
2
3
public class Bar extends Foo<Bar> {
  ...
}
+0 -0

Ouep je comprend, c’est les templates, comme en C++ ! Seulement ce n’est pas ce que je recherche, Foo étant un peu plus complexe.

Comment ça mon cast est un peu sauvage ? Comment vérifier le type de ?

Merci pour tes conseils !

Tu cast child en Bar sans même t’assurer que celui-ci est bel est bien un Bar. La seule chose que tu sais, c’est qu’il s’agit d’un Foo (par définition).

Il te faut donc vérifier le type de l’objet child, ce que tu peux faire comme ça:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public ArrayList<Bar> getLeaves() {
  ArrayList<Bar> leaves = new ArrayList<>();

  for (Foo child : children) {
    if (!(child instanceof Bar)) continue;

    if (child.children.isEmpty()) {
      leaves.add((Bar)child);
    } else {
      leaves.addAll(((Bar)child).getLeaves());
    }
  }
  return leaves;
}

Edit:

Pense à utiliser les fonctions isEmpty(), sur une ArrayList la différence de coût est nulle, mais sur d’autres structures de données, size() prend beaucoup plus de temps.

+0 -0

Petite précision, c’est toujours mieux de mettre l’interface la plus proche en type de retour d’une méthode, en paramètre, ou en déclaration. Si un jour tu veux changer l’implémentation de ta collection et passer d’ArrayList<> à LinkedList<> ça sera plus beaucoup plus simple, principalement si tu donnes un « contrat » à quelqu’un d’autres, ça évite d’avoir à modifier tous les prototypes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public List<Bar> getLeaves() {
  List<Bar> leaves = new ArrayList<>();

  for (Foo child : children) {
    if (!(child instanceof Bar)) continue;

    if (child.children.isEmpty()) {
      leaves.add((Bar)child);
    } else {
      leaves.addAll(((Bar)child).getLeaves());
    }
  }
  return leaves;
}
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte