Heim > Java > javaLernprogramm > Hauptteil

Detaillierte Erläuterung des Wissens über Java-Generika (mit Code)

不言
Freigeben: 2019-02-22 13:35:25
nach vorne
2311 Leute haben es durchsucht

Dieser Artikel vermittelt Ihnen detailliertes Wissen über Java-Generika (mit Code). Freunde in Not können darauf zurückgreifen.

Ich denke, jeder ist mit der Verwendung von Generika sehr vertraut, aber Details wie Typlöschung und Grenzerweiterung sind möglicherweise nicht ganz klar, daher konzentriert sich dieser Artikel auf deren Erläuterung und das Verständnis von Generika Es ist tatsächlich ersichtlich, dass die Generierungslogik einer Sprachfunktion auch für unsere tägliche Entwicklung sehr hilfreich ist.

Warum erscheinen Generika? alle, Generics Es handelt sich nicht um eine Sprachfunktion von Java, sondern um eine Funktion, die bis JDK1.5 nicht unterstützt wurde (die spezifischen Unterschiede werden später besprochen).

List list = new ArrayList();
list.add("123");
String s = (String) list.get(0);
Nach dem Login kopieren

Wie im obigen Code gezeigt, müssen wir uns merken, was wir in die Sammlung einfügen, und es dann erzwingen, wenn wir es herausnehmen. Dadurch wird dieser Typkonvertierungsfehler auch zur Laufzeit verschoben, d. h. es gibt kein Problem sicher, also erscheinen Generika;

Verwendungsszenarien

: generische Klassen, generische Schnittstellen, generische Methoden

public class Test<T>
public interface Test<T>
public <T> void test(T t)
Nach dem Login kopieren
2 , Welche Art von Problemen werden Generika mit sich bringen

Wie oben erwähnt, sind Generika keine Funktion, die Java von Anfang an hat. Wenn Sie also später Generika hinzufügen möchten, müssen Sie kompatibel sein. In früheren Versionen war die Kompromisslösung, die Sun gefunden hat type erasure

; das bedeutet, dass generische Informationen nur während der Kompilierung vorhanden sind und alle generischen Informationen während der Laufzeit gelöscht werden

List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass());
System.out.println(list2.getClass() == list1.getClass());
Nach dem Login kopieren
// Print: class java.util.ArrayList

true


Sie können sehen, dass
und

zur Laufzeit tatsächlich gleich sind. Sie sind alle

; wenn Sie also Generika verwenden, müssen Sie dies tun Denken Sie daran, dass List<String> zur Laufzeit keine generischen Informationen enthält und es unmöglich ist, Informationen über Parametertypen List<String> abzurufen. Wenn Sie also den Laufzeittyp abrufen müssen, werden Vorgänge von Generika nicht unterstützt! class java.util.ArrayList1. Typparameter können nicht mit Basistypen instanziiert werden

new ArrayList<int>();      // error
new ArrayList<Integer>();  // correct
Nach dem Login kopieren
Aufgrund der Typlöschung wird seine Obergrenze gelöscht, nämlich ; Die direkte übergeordnete Klasse der 8 Grundtypen ist

, daher kann der Grundtyp den Grundtyp nicht zum Instanziieren der Typparameter verwenden, sondern muss die Wrapper-Klasse des Grundtyps verwenden; >2. Kann nicht zur Laufzeittypprüfung

t instanceof T             // error
t instanceof List<T>       // error
t instanceof List<String>  // error
t instanceof List          // correct
Nach dem Login kopieren
Object verwendet werden, kann aber mit Number

3 kompensiert werden > kann auch zum Kompensieren verwendet werden

clazz.isInstance();4. Kann nicht statisch werden

T t = new T();  // error
Nach dem Login kopieren

, da statische Variablen in der Klasse gemeinsam genutzt werden und generische Typen undefiniert sind , also generisch Der Typ kann nicht statisch gemacht werden; wenn er jedoch nicht statisch ist, kann der Compiler anhand des Kontexts ableiten, was

ist, zum Beispiel:

private static T t;                  // error
private T t;                         // correct
private static List<T> list;         // error
private static List<?> list;         // correct
private static List<String> list;    // correct

// e.g.
class Test<T> {
  private T t;
  public void set(T arg) { t = arg; }
  public T get() { return t; }
}
Nach dem Login kopieren

Gemäß dem obigen Code ist dies möglich Es ist deutlich zu erkennen, dass der Compiler nicht-statische Typableitungen verarbeitet.

clazz.newInstance();Außerdem ist der Grund, warum List<?> immer noch korrekt ist, darin zu sehen, dass der Compiler die Korrektheit der Typkonvertierung feststellen kann Kompilierung;

5. Instanzen generischer Klassen können nicht ausgelöst oder erfasst werden

T

Test l = new Test();
System.out.println(l.get());
l.set("123");
System.out.println(l.get());

// javap -v 反编译
12: invokevirtual #15         // Method JDK/Test14_genericity$Test.get:()Ljava/lang/Object;
15: invokevirtual #16         // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
18: aload_1
19: ldc       #17             // String 123
21: invokevirtual #18         // Method JDK/Test14_genericity$Test.set:(Ljava/lang/Object;)V
24: getstatic   #6            // Field java/lang/System.out:Ljava/io/PrintStream;

// ---------------------------
Test l = new Test();
System.out.println(l.get());
l.set("123");
System.out.println(l.get());

// javap -v 反编译
12: invokevirtual #15         // Method JDK/Test14_genericity$Test.get:()Ljava/lang/Object;
15: invokevirtual #16         // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
18: aload_1
19: bipush    123
21: invokestatic  #17         // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
Nach dem Login kopieren

Da beim Abfangen von Ausnahmen Informationen zur Laufzeit benötigt werden Die Vererbungsbeziehung der Ausnahme wird nicht ermittelt.

Überladung als Parameter ist nicht zulässig.

catch (T t)                        // error
class Test<T> extends Throwable    // error
Nach dem Login kopieren

während der Laufzeit gelöscht, Überladung Die beiden Methodensignaturen von ;

void test(List<Integer> list)
void test(List<String> list)
Nach dem Login kopieren
Der Hauptgrund, warum generische Arrays nicht erstellt werden können:

Arrays sind kovariant, während Generics unveränderlich sind

Die

Informationen des Arrays werden zur Laufzeit dynamisch erstellt und generische Klasseninformationen können zur Laufzeit nicht abgerufen werden

Sie können sehen Gemäß der obigen Erklärung besteht die allgemeine Idee der sogenannten Löschkompensation oder Korrektur nach dem Löschen darin, zusätzliche Methoden zu verwenden, um die Laufzeit über Typinformationen zu informieren, die in lokalen Variablen oder über den genauen Typ spezifizierter Parameter aufgezeichnet werden können (

);

    3. Grenzerweiterung
  • Aus Sicherheitsgründen sind Java-Generika unveränderlich (um Typkonvertierungsfehler beim Abrufen von Daten zu vermeiden);
  • List<String>[] lists = new ArrayList<String>[10];             // error
    List<String>[] lists1 = (List<String>[]) new ArrayList[10];   // correct
    Nach dem Login kopieren
  • Bei der Verwendung von Sammlungsklassen ist es etwas umständlich, jede Sammlung dazu zu zwingen, den genauen Typ anzugeben. Ich möchte beispielsweise eine Sammlung zum Speichern von A und Unterklassen von A angeben , super,? um die Grenzen von Generika zu erweitern und zu verwalten;

    1. 无界通配符 <?>

    通配符主要用于泛型的使用场景(泛型一般有“声明”和“使用”两种场景);
    通常情况下 <?> 和原生类型大致相同,就像 List 和 List<?> 的表现大部分都是一样的;但是要注意他们其实是有本质去别的,<?> 代表了某一特定的类型,但是编译器不知道这种类型是什么;而原生的表示可以是任何 Object,其中并没有类型限制;

    List<?> list = new ArrayList<String>();    // correct
    list.add("34");                            // error
    String s = list.get(0);                    // error
    Object o = list.get(0);                    // correct
    
    boolean add(E e);
    Nach dem Login kopieren

    上面的代码很明确的反应了这一点(<?> 代表了某一特定的类型,但是编译器不知道这种类型是什么),

    • 因为编译器不知道这种类型是什么,所以在添加元素的时候,当然也就不能确认添加的这个类型是否正确;当使用<?>的时候,代码中的 add(E e) 方法,此时的 E 会被替换为 <?>实际上编译器为了安全起见,会直接拒绝参数列表中涉及通配符的方法调用;就算这个方法没有向集合中添加元素,也会被直接拒绝;

    • List<?> 取出元素的时候,同样因为不知道这个特定的类型是什么,所以只能将取出的元素放在Object中;或者在取出后强转;

    2. 上界 <extends>

    extends,主要用于确定泛型的上界;

    <T extends Test>                             // 泛型声明
    <T extends Test & interface1 & interface2>   // 声明泛型是可以确定多个上界
    <? extends T>                                // 泛型使用时
    Nach dem Login kopieren

    界定的范围如图所示:

    Detaillierte Erläuterung des Wissens über Java-Generika (mit Code)

    应当注意的是当extends用于参数类型限定时:

    List<? extends List> list = new ArrayList<ArrayList>();  // correct
    list.add(new ArrayList());                               // error
    List l = list.get(0);                                    // correct
    ArrayList l = list.get(0);                               // error
    Nach dem Login kopieren

    上面的分析同无界通配符类似,只是 List l = list.get(0); 是正确的,是因为 <? extends List> 界定了放入的元素一定是 List 或者 list 的子类,所以取出的元素能放入 List 中,但是不能放入 ArrayList 中;

    3. 下界 <super>

    super,主要用于确定泛型的下界;如图所示:

    Detaillierte Erläuterung des Wissens über Java-Generika (mit Code)

    List<? super HashMap> list = new ArrayList<>();   // correct
    LinkedHashMap m = new LinkedHashMap();            // correct
    HashMap m1 = m;                                   // correct
    Map m2 = m;                                       // correct
    list.add(m);                                      // correct
    list.add(m1);                                     // correct
    list.add(m2);                                     // error
    
    Map mm = list.get(0);                             // error
    LinkedHashMap mm1 = list.get(0);                  // error
    Nach dem Login kopieren

    根据图中的范围对照代码,就能很快发现Map在List的范围之外;而编辑器为了安全泛型下界集合取出的元素只能放在 Object里面;

    4. PECS 原则

    PECS原则是对上界和下界使用的归纳,即producer-extends, consumer-super;结合上面的两幅图,表示:

    • extends只能读,相当于生产者,向外产出;

    • super只能写,相当于消费者,只能接收消费;

    • 同时边界不能同时规定上界和下界,正如图所示,他们的范围其实是一样的,只是开口不一样;

    5. 自限定类型

    对于上面讲的泛型边界拓展,有一个很特别的用法,

    class Test<T extends Test<T>> {}
    public <T extends Comparable<T>> T max(List<T> list) {}
    Nach dem Login kopieren

    自限定类型可以通俗的解释,就是用自己限定自己,即自和自身相同的类进行某操作;如上面的 max 方法,就表示可以和自身进行比较的类型;

    那么如果想要表达只要是同一祖先就能相互比较呢?

    public <T extends Comparable<? super>> T max(List<? extends T> list) {}
    Nach dem Login kopieren

    >:表明只要是同一祖先就能相互比较, extends T>表明集合中装的都是同一祖先的元素;(出至《Effective Java》第 28 条)

    总结

    • 对于泛型的时候首先要很清楚的知道,在运行时没有任何泛型的信息,全部都被擦除掉了;

    • 需要知道 Java 泛型做不到的事情;

    • 需要知道怎么拓展边界,让泛型更加灵活;

    Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des Wissens über Java-Generika (mit Code). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:cnblogs.com
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage