web-dev-qa-db-fra.com

Pourquoi Java vous permet-il de lancer une collection?

J'ai une simple classe foo et je peux transtyper vers une interface de collection (Map ou List) sans erreur de compilation. Notez que la classe Foo n'implémente aucune interface et ne prolonge aucune autre classe.

public class Foo {

    public List<String> getCollectionCast() {
        return (List<String>) this;    // No compiler error
    }

    public Map<String, String> getCollection2Cast() {
        return (Map<String, String>) this;    // No compiler error
    }

    public Other getCast() {
        return (Other)this;     // Incompatible types. Cannot cast Foo to Other
    }

    public  static class Other {
        // Just for casting demo
    }

}

Pourquoi le compilateur Java ne renvoie-t-il pas erreur de types incompatibles lorsque j'essaie de convertir la classe Foo en une collection?

Foo n'implémente pas Collection. Je m'attendrais à une erreur de types incompatibles, car étant donné la signature de classe Foo actuelle, il ne peut s'agir d'une Collection.

65
istovatis

Ce n'est pas parce qu'elles sont des classes de collection, mais parce qu'elles sont interfaces. Foo ne les met pas en œuvre, mais ses sous-classes le pourraient. Ce n'est donc pas une erreur de compilation, car ces méthodes peuvent être valables pour des sous-classes. En runtime, si this ne fait pas partie d'une classe qui implémente ces interfaces, il s'agit naturellement d'une erreur d'exécution.

Si vous remplacez List<String> par ArrayList<String>, vous obtiendrez également une erreur de compilation, puisqu'un sous-type Foo pourrait implémenter List, mais ne peut pas être étendu. ArrayList (puisque Foo ne l'est pas). De même, si vous faites Foofinal, le compilateur vous donnera une erreur pour les conversions de votre interface car il sait qu'elles ne peuvent jamais être vraies (puisque Foo ne peut pas avoir de sous-classes et ne pas implémenter ces interfaces).

125
T.J. Crowder

Le compilateur n'empêche pas le code de transtyper un type vers une interface, sauf s'il peut établir avec certitude que la relation est impossible.

Si le type de cible est une interface, cela a du sens, car une classe qui étend Foo peut implémenter Map<String, String>. Cependant, notez que cela ne fonctionne que comme Foo N'EST PAS final. Si vous déclariez votre classe avec final class Foo, cette conversion ne fonctionnerait pas.

Si le type de cible est une classe, dans ce cas, il échouerait simplement (essayez (HashMap<String, String>) this), car le compilateur sait avec certitude que la relation entre Foo et HashMap est impossible.

Pour référence, ces règles sont décrites dans JLS-5.5.1 (T = type de cible - Map<String, String>, S = type de source - Foo)

Si T [type de cible] est un type d'interface:

  • Si S n'est pas une classe finale (§8.1.1), alors, s'il existe un supertype X de T et un supertype Y de S, tels que X et Y soient des types paramétrés manifestement distincts et que les effacements de X et Y sont identiques, une erreur de compilation se produit.
    Sinon, la conversion est toujours légale au moment de la compilation (car même si S n'implémente pas T, une sous-classe de S pourrait).

  • Si S est une classe finale (§8.1.1), alors S doit implémenter T, sinon une erreur de compilation survient.

Notez le commentaire gras-italique dans le texte cité.

47
ernest_k