Méthodes génériques Java dans les classes génériques

si vous créez une classe générique en Java (la classe a des paramètres de type générique), pouvez-vous utiliser des méthodes génériques (la méthode prend des paramètres de type générique)?

prenons l'exemple suivant:

public class MyClass {
  public <K> K doSomething(K k){
    return k;
  }
}

public class MyGenericClass<T> {
  public <K> K doSomething(K k){
    return k;
  }

  public <K> List<K> makeSingletonList(K k){
    return Collections.singletonList(k);
  }
}

comme on peut s'y attendre avec une méthode générique, je peux appeler doSomething(K) sur les instances de MyClass avec n'importe quel objet:

MyClass clazz = new MyClass();
String string = clazz.doSomething("String");
Integer integer = clazz.doSomething(1);

cependant, si je tente d'utiliser des instances de MyGenericClass sans spécifiant un type générique, Ce que j'appelle doSomething(K) renvoie un Object , indépendamment de ce que K a été adoptée en:

MyGenericClass untyped = new MyGenericClass();
// this doesn't compile - "Incompatible types. Required: String, Found: Object"
String string = untyped.doSomething("String");

curieusement, il compilera si le type de retour est une classe générique-par exemple List<K> (en fait, cela peut être expliqué - voir la réponse ci-dessous):

MyGenericClass untyped = new MyGenericClass();
List<String> list = untyped.makeSingletonList("String"); // this compiles

en outre, il compilera si la classe générique est dactylographiée, même si seulement avec des caractères génériques:

MyGenericClass<?> wildcard = new MyGenericClass();
String string = wildcard.doSomething("String"); // this compiles
  • y a-t-il une bonne raison pour laquelle appeler une méthode générique dans une classe générique non typée ne devrait pas fonctionner?

  • y a-t-il un truc intelligent concernant les classes génériques et les méthodes génériques qui me manque?

EDIT:

pour clarifier, je m'attendrais à ce qu'une classe générique non typée ou brute ne respecte pas les paramètres de type de la classe générique (parce qu'ils n'ont pas été fournie). Cependant, je ne vois pas pourquoi une classe générique non typée ou brute signifierait que les méthodes génériques ne sont pas respectées.

il s'avère que cette question a déjà été soulevée dans SO, C. F. this question . Les réponses à cette explication expliquent que lorsqu'une classe n'est pas typée / dans sa forme brute, tous génériques sont supprimés de la classe-y compris la dactylographie de méthodes génériques.

cependant, il n'y a pas vraiment d'explication quant à la raison pour laquelle c'est le cas. Permettez-moi donc de clarifier ma question:

  • pourquoi Java supprime-t-il le typage de méthode générique sur des classes génériques non dactylographiées ou de type brut? Est-il une bonne raison à cela, ou était-ce juste un oubli?

EDIT-discussion of JLS:

il a été suggéré (en réponse à la question précédente et à cette question) que ce est traité dans JLS 4.8 , qui stipule:

le type d'un constructeur (§8.8), la méthode de l'instance (§8.4, §9.4), ou le champ non statique (§8.3) M d'un type brut C qui n'est pas hérité de ses superclasses ou superinterfaces est le type brut qui correspond à l'effacement de son type dans la déclaration Générique correspondant à C.

il est clair pour moi comment cela se rapporte à une classe non typée - le les types génériques de classe sont remplacés par les types d'effacement. Si les génériques de classe sont liés, alors le type d'effacement correspond à ces limites. Si elles ne sont pas liées, alors le type d'effacement est objet-par exemple

// unbound class types
public class MyGenericClass<T> {
  public T doSomething(T t) { return t; }
}
MyGenericClass untyped = new MyGenericClass();
Object t = untyped.doSomething("String");

// bound class types
public class MyBoundedGenericClass<T extends Number> {
  public T doSomething(T t) { return t; }
}
MyBoundedGenericClass bounded = new MyBoundedGenericClass();
Object t1 = bounded.doSomething("String"); // does not compile
Number t2 = bounded.doSomething(1); // does compile

alors que les méthodes génériques sont des méthodes d'instance, il n'est pas clair pour moi que JLS 4.8 s'applique aux méthodes génériques. Le type de la méthode générique ( <K> dans l'exemple précédent) n'est pas sans nom, car son type est déterminé par les paramètres de la méthode-seulement la classe est non typée / raw-typé.

28
demandé sur Community 2013-08-01 22:15:31

6 réponses

"for backwards compatibility" semble une raison suffisante pour l'effacement de type des types génériques de classe - il est nécessaire par exemple pour vous permettre de retourner une liste non typée et de la passer à un code d'héritage. L'extension de cette de méthodes génériques semble difficile de sous-cas.

le JLS snippet de 4.8 (que vous citez) couvre les constructeurs, les méthodes d'instance et les champs membres - les méthodes génériques ne sont qu'un cas particulier des méthodes d'instance en général. Il semble donc votre cas est couvert par cet extrait.

adaptation de JLS 4.8 à ce cas particulier:

le type d'une méthode générique est le type brut qui correspond à la erasure de son type dans la déclaration Générique correspondant à C.

(ici le "type" de la méthode inclurait tous les types de paramètre et de retour). Si vous interprétez "effacement" comme "effacement de tous les génériques", alors cela semble correspondre le comportement observé, bien qu'il ne soit pas très intuitif ou même utile. Il semble presque comme une consistance trop zélée, d'effacer tous génériques, plutôt que juste les paramètres de classe génériques (bien que Qui Suis-je pour deviner les concepteurs).

peut-être qu'il pourrait y avoir des problèmes où les paramètres génériques de classe interagissent avec les paramètres génériques de méthode - dans votre code ils sont entièrement indépendants, mais vous pouvez imaginer d'autres cas où ils sont assignés / mélangés ainsi. Je pense qu'il vaut la peine de souligner que l'utilisation de types bruts ne sont pas recommandés, comme selon le JLS:

l'utilisation de types bruts n'est autorisée qu'à titre de concession à la compatibilité de code legacy. L'utilisation de types bruts dans le code écrit après introduction de la généralité dans le langage de programmation Java fortement déconseillée. Il est possible que les futures versions de Java le langage de programmation interdira l'utilisation de types bruts

une partie de la pensée des développeurs java est apparente ici:

http://bugs.sun.com/view_bug.do?bug_id=6400189

(bug + Correction montrant que le type de retour d'une méthode est traité comme faisant partie du type de la méthode aux fins de ce type d'effacement)

il y a aussi cette demande , où quelqu'un semble demander le comportement que vous décrivez - effacer seulement les paramètres génériques de classe, pas d'autres génériques - mais il a été rejeté avec ce raisonnement:

la requête est de modifier le type erasure de sorte que dans la déclaration de type Foo<T> , erasure ne supprime que T des types paramétrés. Puis, il arrive que dans la déclaration de Map<K,V> , Set<Map.Entry<K,V>> s'efface en Set<Map.Entry> .

mais si Map<K,V> avait une méthode qui a pris type Map<String,V> , son erasure serait juste Map<String> . Pour l'effacement de type pour changer le nombre de paramètres de type est horrible, en particulier pour la résolution de la méthode de compilation-time. Nous n'allons absolument pas accepter cette demande.

c'est trop s'attendre à pouvoir utiliser des types crus ( Map ) tout en obtenant certains du type-sécurité des génériques ( Set<Map.Entry> ).

7
répondu Graham Griffiths 2013-08-12 04:46:34

avec des génériques Java, si vous utilisez la forme brute d'une classe générique, alors tous génériques sur la classe, même des méthodes génériques sans rapport telles que vos méthodes makeSingletonList et doSomething , deviennent crus. La raison pour laquelle je comprends qu'il est de fournir rétrocompatibilité avec le code Java écrit pré-génériques.

S'il n'y a pas d'utilisation pour votre paramètre de type générique T , alors il suffit de le supprimer de MyGenericClass , en laissant vos méthodes générique avec K . Sinon, vous devrez vivre avec le fait que le paramètre de type de classe T doit être donné à votre classe pour utiliser des génériques sur quoi que ce soit d'autre dans la classe.

4
répondu rgettman 2013-08-01 18:20:43

j'ai trouvé une raison pour rejeter complètement les génériques (mais ce n'est pas tout à fait bon). La raison est: les génériques peuvent être délimités. Considérons cette classe:

public static class MyGenericClass<T> {
    public <K extends T> K doSomething(K k){
        return k;
    }

    public <K> List<K> makeSingletonList(K k){
        return Collections.singletonList(k);
    }
}

le compilateur doit rejeter les génériques dans doSomething , lorsque vous utilisez la classe sans génériques. Et je pense que tous les génériques sont rejetés pour être cohérent avec ce comportement.

makeSingletonList compile parce que Java n'a décoché un cast de List à List<K> (mais compilateur affiche d'avertissement).

2
répondu MAnyKey 2013-08-08 18:25:04

cela ne répond pas à la question fondamentale, mais bien à celle de savoir pourquoi makeSingletonList(...) compile alors que doSomething(...) ne compile pas:

Dans un non de la mise en œuvre de MyGenericClass :

MyGenericClass untyped = new MyGenericClass();
List<String> list = untyped.makeSingletonList("String");

... est l'équivalent de:

MyGenericClass untyped = new MyGenericClass();
List list = untyped.makeSingletonList("String");
List<String> typedList = list;

cela compilera (avec plusieurs avertissements), mais est alors ouvert aux erreurs d'exécution.

en effet, c'est aussi analogue à:

MyGenericClass untyped = new MyGenericClass();
List<Integer> list = untyped.makeSingletonList("String"); // this compiles!!!
Integer a = list.get(0);

...qui se compile mais lancera un runtime ClassCastException quand vous essayez d'obtenir la valeur String et le lancer dans un Integer .

1
répondu amaidment 2013-08-06 12:48:39

la raison en est la rétrocompatibilité avec le code pre-generics. Le code pre-generics n'utilisait pas d'arguments génériques, mais plutôt ce qui semble aujourd'hui être un type brut. Le code pré-génériques utiliserait Object au lieu de références utilisant le type générique, et les types crus utiliseraient le type Object pour tous les arguments génériques, de sorte que le code était en effet rétro-compatible.

par exemple, considérez ce code:

List list = new ArrayList();

il s'agit d'un code pré-générique, et une fois que des génériques ont été introduits, cela a été interprété comme un type générique brut équivalent à:

List<?> list = new ArrayList<>();

parce que le ? n'a pas de mot clé extends ou super après lui, il est transformé en ceci:

List<Object> list = new ArrayList<>();

la version de List qui a été utilisée avant génériques utilisé un Object pour se référer à un élément de liste, et cette version utilise également un Object pour se référer à un list element, donc la rétrocompatibilité est conservée.

0
répondu tbodt 2013-08-06 10:30:12

de mon commentaire sur MAnyKeys réponse:

je pense que l'erasure de type complet a du sens combiné avec la compatibilité mentionnée à l'envers. Lorsque l'objet est créé sans argument de type générique, il peut l'être (ou devrait l'être?) supposait que l'usage de l'objet se déroulait en code non générique.

Considérer cet héritage de code:

public class MyGenericClass {
    public Object doSomething(Object k){
        return k;
    }
}

appelé comme ceci:

MyGenericClass foo = new MyGenricClass();
NotKType notKType = foo.doSomething(new NotKType());

maintenant la classe et sa méthode est rendue Générique:

public class MyGenericClass<T> {
    public <K extends KType> K doSomething(K k){
        return k;
    }
}

maintenant le code d'appelant ci-dessus ne compilerait plus comme NotKType n'est pas un suptype de KType . Pour éviter cela, les types génériques sont remplacés par Object . Bien qu'il y ait des cas où cela ne ferait aucune différence (comme votre exemple), il est au moins très complexe pour le compilateur d'analyser quand. Peut-être même impossible.

je sais que ce szenario semble un peu construit mais je suis sûr que ça arrive de temps en temps.

0
répondu André Stannek 2013-08-06 14:15:15