JavaRush /Java-Blog /Random-DE /Platzhalter in Java Generics

Platzhalter in Java Generics

Veröffentlicht in der Gruppe Random-DE
Hallo! Wir beschäftigen uns weiterhin mit dem Thema Generika. Sie verfügen bereits über ein gutes Maß an Wissen darüber aus früheren Vorlesungen (über die Verwendung von Varargs bei der Arbeit mit Generika und über das Löschen von Typen ), aber ein wichtiges Thema haben wir noch nicht behandelt: Platzhalter . Dies ist ein sehr wichtiges Merkmal von Generika. So sehr, dass wir ihm einen eigenen Vortrag gewidmet haben! Es gibt jedoch nichts Kompliziertes an Wildcards, das werden Sie jetzt sehen :) Platzhalter in Generika – 1Schauen wir uns ein Beispiel an:
public class Main {

   public static void main(String[] args) {

       String str = new String("Test!");
       // ниWieих проблем
       Object obj = str;

       List<String> strings = new ArrayList<String>();
       // ошибка компиляции!
       List<Object> objects = strings;
   }
}
Was ist denn hier los? Wir sehen zwei sehr ähnliche Situationen. Im ersten Schritt versuchen wir, ein Objekt Stringin den Typ umzuwandeln Object. Damit gibt es keine Probleme, alles funktioniert wie es soll. Aber in der zweiten Situation gibt der Compiler einen Fehler aus. Obwohl es den Anschein hat, dass wir dasselbe tun. Es ist nur so, dass wir jetzt eine Sammlung mehrerer Objekte verwenden. Aber warum tritt der Fehler auf? Was ist im Wesentlichen der Unterschied, ob wir ein Objekt Stringin einen Typ Objectoder 20 Objekte umwandeln? Es gibt einen wichtigen Unterschied zwischen einem Objekt und einer Sammlung von Objekten . Wenn eine Klasse ein Nachfolger einer Klasse ist , dann ist sie kein Nachfolger . Aus diesem Grund konnten wir unsere nicht dazu bringen . ist ein Erbe , aber kein Erbe . Intuitiv sieht das nicht sehr logisch aus. Warum ließen sich die Schöpfer der Sprache von diesem Prinzip leiten? Stellen wir uns vor, der Compiler würde uns hier keinen Fehler melden: BАCollection<B>Collection<A>List<String>List<Object>StringObjectList<String>List<Object>
List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
In diesem Fall könnten wir beispielsweise Folgendes tun:
objects.add(new Object());
String s = strings.get(0);
Da uns der Compiler keine Fehler anzeigte und uns erlaubte, einen Verweis List<Object> objectauf eine Sammlung von Strings zu erstellen, können wir keinen String, sondern einfach ein beliebiges Objekt stringshinzufügen ! Dadurch haben wir die Garantie verloren, dass sich nur die im Generic angegebenen Objekte in unserer Sammlung befinden . Das heißt, wir haben den Hauptvorteil von Generika verloren – die Typensicherheit. Und da der Compiler uns das alles erlaubt hat, bedeutet das, dass wir nur während der Programmausführung einen Fehler erhalten, der immer viel schlimmer ist als ein Kompilierungsfehler. Um solche Situationen zu verhindern, gibt uns der Compiler eine Fehlermeldung: stringsObjectString
// ошибка компиляции
List<Object> objects = strings;
...und erinnert ihn daran, dass List<String>er kein Erbe ist List<Object>. Dies ist eine eiserne Regel für die Anwendung von Generika und muss bei der Verwendung dieser Arzneimittel beachtet werden. Lass uns weitermachen. Nehmen wir an, wir haben eine kleine Klassenhierarchie:
public class Animal {

   public void feed() {

       System.out.println("Animal.feed()");
   }
}

public class Pet extends Animal {

   public void call() {

       System.out.println("Pet.call()");
   }
}

public class Cat extends Pet {

   public void meow() {

       System.out.println("Cat.meow()");
   }
}
An der Spitze der Hierarchie stehen einfach Tiere: Von ihnen werden Haustiere geerbt. Haustiere werden in zwei Arten unterteilt: Hunde und Katzen. Stellen Sie sich nun vor, wir müssten eine einfache Methode erstellen iterateAnimals(). Die Methode sollte eine Sammlung beliebiger Tiere ( Animal, Pet, Cat, Dog) akzeptieren, alle Elemente durchlaufen und jedes Mal etwas an die Konsole ausgeben. Versuchen wir, diese Methode zu schreiben:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Es scheint, dass das Problem gelöst ist! Wie wir jedoch kürzlich herausgefunden haben, sind List<Cat>wir keine Erben ! Wenn wir versuchen, eine Methode mit einer Liste von Katzen aufzurufen , erhalten wir daher einen Compilerfehler: List<Dog>List<Pet>List<Animal>iterateAnimals()
import java.util.*;

public class Main3 {


   public static void iterateAnimals(Collection<Animal> animals) {

       for(Animal animal: animals) {

           System.out.println("Еще один шаг в цикле пройден!");
       }
   }

   public static void main(String[] args) {


       List<Cat> cats = new ArrayList<>();
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());

       //ошибка компилятора!
       iterateAnimals(cats);
   }
}
Die Situation sieht für uns nicht gut aus! Es stellt sich heraus, dass wir separate Methoden zur Zählung aller Tierarten schreiben müssen? Eigentlich nein, das musst du nicht :) Und Wildcards werden uns dabei helfen ! Wir werden das Problem mit einer einfachen Methode lösen, indem wir die folgende Konstruktion verwenden:
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Dies ist der Platzhalter. Genauer gesagt ist dies die erste von mehreren Arten von Wildcards – „ extends “ (ein anderer Name ist Upper Bounded Wildcards ). Was sagt uns dieser Entwurf? Dies bedeutet, dass die Methode eine Sammlung von Klassenobjekten Animaloder Objekten einer beliebigen Nachfolgeklasse als Eingabe verwendet Animal (? extends Animal). AnimalMit anderen Worten: Die Methode kann die Sammlung , Petoder Dogals Eingabe akzeptieren Cat– es macht keinen Unterschied. Stellen wir sicher, dass das funktioniert:
public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
   iterateAnimals(dogs);
}
Konsolenausgabe:

Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Wir haben insgesamt 4 Sammlungen und 8 Objekte erstellt und es gibt genau 8 Einträge in der Konsole. Alles funktioniert super! :) Mit Wildcard konnten wir die erforderliche Logik mit der Bindung an bestimmte Typen problemlos in einer Methode unterbringen. Wir haben die Notwendigkeit beseitigt, für jede Tierart eine separate Methode zu schreiben. Stellen Sie sich vor, wie viele Methoden uns zur Verfügung stünden, wenn unsere Anwendung in einem Zoo oder einer Tierklinik eingesetzt würde :) Schauen wir uns nun eine andere Situation an. Unsere Vererbungshierarchie bleibt dieselbe: Die oberste Klasse ist Animal, direkt darunter befindet sich die Klasse Pets Petund auf der nächsten Ebene ist Catund Dog. Jetzt müssen Sie die Methode umschreiben iretateAnimals(), damit sie mit jeder Tierart außer Hunden funktioniert . Collection<Animal>Das heißt, es sollte , Collection<Pet>or als Eingabe akzeptieren Collection<Cat>, aber nicht mit funktionieren Collection<Dog>. Wie können wir das erreichen? Es scheint, dass die Aussicht, für jeden Typ eine eigene Methode zu schreiben, wieder vor uns steht :/ Wie sonst können wir dem Compiler unsere Logik erklären? Und das geht ganz einfach! Auch hier kommen uns wieder Wildcards zu Hilfe. Aber dieses Mal werden wir einen anderen Typ verwenden – „ super “ (ein anderer Name ist Lower Bounded Wildcards ).
public static void iterateAnimals(Collection<? super Cat> animals) {

   for(int i = 0; i < animals.size(); i++) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Das Prinzip ist hier ähnlich. Die Konstruktion <? super Cat>teilt dem Compiler mit, dass die Methode iterateAnimals()eine Sammlung von Objekten der Klasse Catoder einer anderen Vorgängerklasse als Eingabe verwenden kann Cat. CatIn unserem Fall passen die Klasse selbst , ihr Vorfahre – Petsund der Vorfahre des Vorfahren – zu dieser Beschreibung Animal. Die Klasse Dogpasst nicht in diese Einschränkung und daher führt der Versuch, eine Methode mit einer Liste zu verwenden, List<Dog>zu einem Kompilierungsfehler:
public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);

   //ошибка компиляции!
   iterateAnimals(dogs);
}
Unser Problem ist gelöst und wieder erwiesen sich Wildcards als äußerst nützlich :) Damit ist die Vorlesung abgeschlossen. Jetzt sehen Sie, wie wichtig das Thema Generika beim Erlernen von Java ist – wir haben 4 Vorlesungen damit verbracht! Aber jetzt haben Sie ein gutes Verständnis für die Thematik und können sich beim Vorstellungsgespräch beweisen :) Und jetzt ist es an der Zeit, sich wieder den Aufgaben zu widmen! Viel Erfolg im Studium! :) :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION