Usage des génériques

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

Bonjour à tous,

Quotidiennement, je contribue à un projet open-source qui utilise massivement les génériques et je dois me plier à une pratique qui me semble bizarre. J'aurais aimé l'avis de la communauté. Voici un bout de mon code pour illustrer mes propos :

Nous possédons 2 interfaces dans des fichiers différents :

1
2
3
public interface CtStatement {}

public interface CtBlock extends CtStatement {}

Nous possédons maintenant une classe (et son interface) qui va utiliser ces interfaces :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public interface CtIf extends CtStatement {
  <S extends CtStatement> S getElse();
}

public class CtIfImpl extends CtStatementImpl implements CtIf {
  CtStatement elseStatement;

  @Override
  public <S extends CtStatement> S getElse() {
    return (S) elseStatement; // cast obligatoire
  }
}

Ma question est la suivante :

Pourquoi suis-je obligé de caster mon elseStatement alors qu'il est typé avec un type compatible par rapport à la déclaration du type de la méthode ?

Si vous savez m'éclairer, je suis preneur. :)

Pourquoi suis-je obligé de caster mon elseStatement alors qu'il est typé avec un type compatible par rapport à la déclaration du type de la méthode ?

Andr0

Parce que le type n'est pas compatible, en fait : comme tu précises dans l'implémentation que tu retournes le type S paramétré et non simplement un CtStatement, tu es obligé de rajouter ce cast.

À noter qu'il n'est pas obligatoire, tu peux tout à fait implémenter ta méthode de cette manière, avec le même warning :

1
2
3
4
    @Override
    public CtStatement getElse() {
        return elseStatement; // cast obligatoire
    }

Parce que le type n'est pas compatible, en fait : comme tu précises dans l'implémentation que tu retournes le type S paramétré et non simplement un CtStatement, tu es obligé de rajouter ce cast.

Dans la mesure où j'indique que S doit étendre CtStatement, pourquoi le type serait incompatible ? A noter que quelque chose comme ceci ne fonctionne pas non plus :

1
2
3
4
5
@Override
public <S extends CtStatement> S getElse() {
  CtBlock<?> elseBranch = null;
  return elseBranch; // erreur de compilation alors que CtBlock étend CtStatement
}

À noter qu'il n'est pas obligatoire, tu peux tout à fait implémenter ta méthode de cette manière, avec le même warning :

1
2
3
4
    @Override
    public CtStatement getElse() {
        return elseStatement; // cast obligatoire
    }

SpaceFox

Bien vu ça !

Dans la mesure où j'indique que S doit étendre CtStatement, pourquoi le type serait incompatible ? A noter que quelque chose comme ceci ne fonctionne pas non plus :

Le type n'est pas vraiment compatible, parce que tu essaye de mettre une mère dans une fille.

En fait ce que tu décrit ici :

1
2
3
4
5
6
7
8
public class CtIfImpl extends CtStatementImpl implements CtIf {
  CtStatement elseStatement;

  @Override
  public <S extends CtStatement> S getElse() {
    return (S) elseStatement; // cast obligatoire
  }
}

correspond presque à ça (en sachant que toute les classes héritent de Object):

1
2
3
4
5
6
7
8
public class CtIfImpl extends CtStatementImpl implements CtIf {
  Object elseStatement;

  @Override
  public String getElse() {
    return (String) elseStatement; // cast obligatoire
  }
}

Là tu devrais voir pourquoi le cast est obligatoire.

En gros, ta classe fille a potentiellement des attributs que ne possède pas ta classe mère. Lorsque tu essaye de faire rentrer ta classe mère dans la fille, le compilo se demande donc si tu es sur de ce que tu fais car ce n'est pas très naturel. Les attributs en plus que possède la fille ne seront peut-être pas alimentées, d'où le cast pour dire au compilo que tu sais ce que tu fais.

Par contre il n y a pas de problème à faire rentrer la fille dans la mère, car tous les attributs de la mère seront alimentées avec celle de la fille.

Par contre il n y a pas de problème à faire rentrer la fille dans la mère, car tous les attributs de la mère seront alimentées avec celle de la fille.

firm1

Qu'en est-il de mon exemple dans mon message précédentCtBlock est une classe fille de CtStatement ?

Andr0

Dans ton exemple, CtBlock est une classe fille de CtStatement, ce qui en fait une sœur de ce que tu déclare comme type S. Rien ne garantie que deux sœurs peuvent rentrer l'une dans l'autre. Dans les deux sens, il faudra caster.

Si je reprend l'analogie avec mon exemple plus haut, c'est comme si tu cherchais à faire ça (sachant toujours que toute classe hérite de Object) :

1
2
3
4
  public String getElse() {
    Double elseBranch = null;
    return elseBranch; // pour que ça passe la compilation, il faut soit le caster, soit utiliser une méthode que renvoit le type String.
  }

Double et String sont des sœurs, et les sœurs et ne sont donc pas explicitement compatibles.

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