et en Java - pourquoi cela fonctionne de cette façon?


Encore un novice, essayant de comprendre les génériques Java. J'ai observé tous les sujets, j'ai trouvé, mais j'ai encore d'énormes questions. Pourriez-vous m'expliquer les choses suivantes:

  1. <? extends SomeClass> signifie, que ? est "tout type", et extends SomeClass signifie, que ce n'importe quel type peut être qu'une sous-classe de SomeClass. OK, j'écris deux classes élémentaires:
abstract class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
}

class Student extends Person {
    public Student(String name) {
        super(name);
    }
}

Classe Student sera ? dans notre exemple. ? extends Person, pour être précis. Ensuite, j'essaie d'ajouter un nouvel étudiant à ArrayList, que, comme Je comprends de ce qui est écrit ci-dessus, applique toutes les classes, qui sont des sous-classes de personne:

Student clarissa = new Student("Clarissa Starling");
List<? extends Person> list = new ArrayList<>();
list.add(clarissa); //Does not compile

Eclipse dit:

"La méthode add(capture#3-de ? étend Personne) dans le type La liste n'est pas applicable pour le arguments (Étudiant) "

Comment la classe Student peut-elle ne pas être applicable, lorsque nous avons déclaré List, paramétré par <? extends Person>, et Student étend exactement la classe Person?

Néanmoins, le code suivant:

List<? super Person> list = new ArrayList<>();
list.add(clarissa); 

Compile et fonctionne bien (list.get(0), passé à la méthode println, me montre le résultat correct de l'invocation toString). Si je comprends bien, List<? super Person> signifie que je peux passer à cette liste n'importe quel type, c'est-à-dire un super type pour notre classe Person (dans notre cas, c'est la classe Object uniquement). Mais nous voyons, que, contrairement à la logique, nous pouvons facilement ajouter sous-classe Étudiant à notre List<? super Person>!

OK, mettez de côté nos émotions, et voyons ce qui peut arriver avec Clarissa Starling dans notre collection. Prenons notre classe Student, et ajoutons quelques méthodes à il:

class Student extends Person {
    private int grant;
    public Student(String name) {
        super(name);
    }

    public void setGrant(int grant) {
        this.grant = grant;
    }

    public int getGrant() {
        return this.grant;
    }

}

Ensuite, nous passons un objet, instancié à partir de cette classe renouvelée (notre objet "clarissa", par exemple), à List<? extends Person>. En faisant cela, nous voulons dire que nous pouvons stocker des sous-classes dans la collection de ses superclasses. Peut-être, je ne comprends pas certaines idées fondamentales, mais à ce stade, je ne vois aucune différence entre l'ajout de sous-classe à la collection de ses superclasses et l'attribution de la référence à l'objet "clarissa" à la variable, personne typée. Nous avons la même réduction de méthodes invokables, lorsque nous voulons traiter l'une d'entre elles, en utilisant notre variable de superclasse. Alors, pourquoi List<? extends SomeClass> ne fonctionne pas de la même manière, dans lequel List<? super SomeClass> fonctionne inversement?

  1. Je ne comprends pas la différence fondamentale entre <T> (ou <E>, ou toute autre lettre de la partie appropriée de JLS) et <?>. Les deux <T> et <?> sont typeholders, alors pourquoi nous avons deux "mots-clés" (ces symboles ne sont PAS des mots-clés, j'ai juste utilisé ce mot pour souligner la signification lourde des deux symboles en langage Java) dans le même but?
Author: Victor Zhdanov, 2016-02-27

1 answers

La façon dont je le regarde est la suivante - l'espace réservé T représente un type défini et dans les endroits où nous avons besoin de connaître le type réel, nous devons pouvoir le résoudre. En revanche, le caractère générique ? signifie n'importe quel type et je n'aurai jamais besoin de savoir ce qu'est ce type. Vous pouvez utiliser les limites extends et super pour limiter ce caractère générique d'une manière ou d'une autre, mais il n'y a aucun moyen d'obtenir le type réel.

Donc, si j'ai un List<? extends MySuper> alors tout ce que je sais à ce sujet, c'est que chaque objet qu'il contient implémente le MySuper interface, et tous les objets de cette liste sont du même type. Je ne sais pas ce qu'est ce type, seulement que c'est un sous-type de MySuper. Cela signifie que je peux sortir des objets de cette liste tant que je n'ai besoin que d'utiliser l'interface MySuper. Ce que je ne peux pas faire, c'est de mettre des objets dans la liste parce que je ne sais pas quel est le type - le compilateur ne le permettra pas car même s'il m'arrive d'avoir un objet du bon type, il ne peut pas être sûr au moment de la compilation. Ainsi, la collection est, dans un sens, une lecture seule collection.

La logique fonctionne dans l'autre sens quand vous avez List<? super MySuper>. Ici, nous disons que la collection est d'un type défini qui est un supertype de MySuper. Cela signifie que vous pouvez toujours y ajouter un objet MySuper. Ce que vous ne pouvez pas faire, parce que vous ne connaissez pas le type réel, c'est d'en récupérer des objets. Vous avez donc maintenant une sorte de collection en écriture seule.

Lorsque vous utilisez un caractère générique borné par rapport au paramètre de type générique "standard", c'est là que la valeur des différences commence à devenir apparente. Disons que j'ai 3 classes Person, Student et Teacher, avec Person étant la base Student et Teacher étendre. Dans une API, vous pouvez écrire une méthode qui prend une collection de Person et fait quelque chose à chaque élément de la collection. C'est bien, mais vous vous souciez vraiment seulement que la collection soit d'un type compatible avec l'interface Person - elle devrait fonctionner avec List<Student> et List<Teacher> aussi bien. Si vous définissez la méthode comme ceci

public void myMethod(List<Person> people) {
    for (Person p: people) {
        p.doThing();
    }
}

Alors ça ne peut pas prendre List<Student> ou List<Teacher>. Donc, au lieu de cela, vous le définiriez pour prendre List<? extends Person>...

public void myMethod(List<? extends Person> people){
    for (Person p: people) {
        p.doThing();
    }
}

, Vous pouvez le faire parce que myMethod jamais besoin d'ajouter à la liste. Et maintenant vous trouvez que List<Student> et List<Teacher> peuvent tous deux être passés dans la méthode.

Maintenant, disons que vous avez une autre méthode qui veut ajouter des étudiants à une liste. Si le paramètre de méthode prend un List<Student> alors il ne peut pas prendre un List<People> même si cela devrait être bien. Donc, vous l'implémentez en prenant un List<? super Student> par exemple

public void listPopulatingMethod(List<? extends Student> source, List<? super Student> sink) {
    for (Student s: source) {
        sink.add(s);
    }
}

C'est le cœur de PECS, que vous pouvez lire plus en détail ailleurs... Qu'est-ce que PECS (Producer Extends Consumer Super)? http://www.javacodegeeks.com/2011/04/java-generics-quick-tutorial.html

 15
Author: sisyphus, 2018-03-06 18:30:42