Skip to content

Latest commit

 

History

History
319 lines (236 loc) · 7.43 KB

type-erasure.md

File metadata and controls

319 lines (236 loc) · 7.43 KB
archetype title linkTitle author readings tldr outcomes quizzes youtube fhmedia
lecture-cg
Type Erasure
Type Erasure
Carsten Gips (HSBI)
key comment
Ullenboom2021
Kapitel 11.2 und 11.6
key comment
LernJava
Kapitel Generics
key comment
Java-SE-Tutorial
Specialized Trails: Generics
key
Bloch2018
Generics existieren eigentlich nur auf Quellcode-Ebene. Nach der Typ-Prüfung etc. entfernt der Compiler alle generischen Typ-Parameter und alle `<...>` (=> "Type-Erasure"), d.h. im Byte-Code stehen nur noch Raw-Typen bzw. die oberen Typ-Schranken der Typ-Parameter, in der Regel `Object`. Zusätzlich baut der Compiler die nötigen Casts ein. Als Anwender merkt man davon nichts, muss das "Type-Erasure" wegen der Auswirkungen aber auf dem Radar haben!
k2
Typ-Löschung und Auswirkungen
link name
VL Generics: Type Erasure

Typ-Löschung (Type-Erasure)

::: notes Der Compiler ersetzt nach Prüfung der Typen und ihrer Verwendung alle Typ-Parameter durch

  1. deren obere (Typ-)Schranke und
  2. passende explizite Cast-Operationen (im Byte-Code).

Die obere Typ-Schranke ist in der Regel der Typ der ersten Bounds-Klausel oder Object, wenn keine Einschränkungen formuliert sind.

Bei parametrisierten Typen wie List<T> wird der Typ-Parameter entfernt, es entsteht ein sogenannter Raw-Typ (List, quasi implizit mit Object parametrisiert).

=> Ergebnis: Nur eine (untypisierte) Klasse! Zur Laufzeit gibt es keine Generics mehr!

Hinweis: In C++ ist man den anderen möglichen Weg gegangen und erzeugt für jede Instantiierung die passende Klasse. Siehe Modul "Systemprogrammierung" :)

Beispiel: Aus dem folgenden harmlosen Code-Fragment: :::

class Studi<T> {
    T myst(T m, T n) { return n; }

    public static void main(String[] args) {
        Studi<Integer> a = new Studi<>();
        int i = a.myst(1, 3);
    }
}

\pause

\bigskip \hrule \smallskip

::: notes wird nach der Typ-Löschung durch Compiler (das steht dann quasi im Byte-Code): :::

class Studi {
    Object myst(Object m, Object n) { return n; }

    public static void main(String[] args) {
        Studi a = new Studi();
        int i = (Integer) a.myst(1, 3);
    }
}

::: notes Die obere Schranke meist Object => new T() verboten/sinnfrei (s.u.)! :::

Type-Erasure bei Nutzung von Bounds

:::::: columns ::: column

[vor der Typ-Löschung durch Compiler:]{.notes}

class Cps<T extends Number> {
    T myst(T m, T n) {
        return n;
    }

    public static void main(String[] args) {
        Cps<Integer> a = new Cps<>();
        int i = a.myst(1, 3);
    }
}

::: ::: column

[nach der Typ-Löschung durch Compiler:]{.notes}

class Cps {
    Number myst(Number m, Number n) {
        return n;
    }

    public static void main(String[] args) {
        Cps a = new Cps();
        int i = (Integer) a.myst(1, 3);
    }
}

::: ::::::

Raw-Types: Ich mag meine Generics "well done" :-)

Raw-Types: Instanziierung ohne Typ-Parameter => Object

Stack s = new Stack(); // Stack von Object-Objekten

\bigskip

  • Wegen Abwärtskompatibilität zu früheren Java-Versionen noch erlaubt.
  • Nutzung wird nicht empfohlen! (Warum?)

::: notes

Anmerkung

Raw-Types darf man zwar selbst im Quellcode verwenden (so wie im Beispiel hier), sollte die Verwendung aber vermeiden wegen der Typ-Unsicherheit: Der Compiler sieht im Beispiel nur noch einen Stack für Object, d.h. dort dürfen Objekte aller Typen abgelegt werden - es kann keine Typprüfung durch den Compiler stattfinden. Auf einem Stack<String> kann der Compiler prüfen, ob dort wirklich nur String-Objekte abgelegt werden und ggf. entsprechend Fehler melden.

Etwas anderes ist es, dass der Compiler im Zuge von Type-Erasure selbst Raw-Types in den Byte-Code schreibt. Da hat er vorher bereits die Typsicherheit geprüft und er baut auch die passenden Casts ein.

Das Thema ist eigentlich nur noch aus Kompatibilität zu Java5 oder früher da, weil es dort noch keine Generics gab (wurden erst mit Java6 eingeführt). :::

Folgen der Typ-Löschung: new

::: cbox new mit parametrisierten Klassen ist nicht erlaubt! :::

\bigskip \bigskip

class Fach<T> {
    public T foo() {
        return new T();  // nicht erlaubt!!!
    }
}

\bigskip Grund: Zur Laufzeit keine Klasseninformationen über T mehr

::: notes Im Code steht return (CAST) new Object();. Das neue Object kann man anlegen, aber ein Cast nach irgendeinem anderen Typ ist sinnfrei: Jede Klasse ist ein Untertyp von Object, aber eben nicht andersherum. Außerdem fehlt dem Objekt vom Typ Object auch sämtliche Information und Verhalten, die der Cast-Typ eigentlich mitbringt ... :::

Folgen der Typ-Löschung: static

::: cbox static mit generischen Typen ist nicht erlaubt! :::

\bigskip \bigskip

class Fach<T> {
    static T t;                    // nicht erlaubt!!!
    static Fach<T> c;              // nicht erlaubt!!!
    static void foo(T t) { ... };  // nicht erlaubt!!!
}

Fach<String>  a;
Fach<Integer> b;

\bigskip Grund: Compiler generiert nur [eine]{.alert} Klasse! Beide Objekte würden sich die statischen Attribute teilen \newline (Typ zur Laufzeit unklar!).

\smallskip

Hinweis: Generische (statische) Methoden sind erlaubt.

Folgen der Typ-Löschung: instanceof

::: cbox instanceof mit parametrisierten Klassen ist nicht erlaubt! :::

\bigskip \bigskip

:::::: columns ::: {.column width="60%"}

class Fach<T> {
    void printType(Fach<?> p) {
        if (p instanceof Fach<Number>)
            ...
        else if (p instanceof Fach<String>)
            ...
    }
}

::: ::: {.column width="40%"}

[Grund: Unsinniger Code nach Typ-Löschung:]{.notes}

class Fach {
void printType(Fach p) {
    if (p instanceof Fach)
        ...
    else if (p instanceof Fach)
        ...
    }
}

::: ::::::

Folgen der Typ-Löschung: .class

::: cbox .class mit parametrisierten Klassen ist nicht erlaubt! :::

\bigskip \bigskip

boolean x;
List<String>  a = new ArrayList<String>();
List<Integer> b = new ArrayList<Integer>();

x = (List<String>.class == List<Integer>.class);  // Compiler-Fehler
x = (a.getClass() == b.getClass());               // true

\bigskip Grund: Es gibt nur List.class (und kein List<String>.class bzw. List<Integer>.class)!

Wrap-Up

  • Generics existieren eigentlich nur auf Quellcode-Ebene
  • "Type-Erasure":
    • Compiler entfernt [nach Typ-Prüfungen etc.]{.notes} generische Typ-Parameter [etc.]{.notes} => im Byte-Code nur noch Raw-Typen [bzw. die oberen Typ-Schranken der Typ-Parameter, in der Regel Object]{.notes}
    • Compiler baut passende Casts in Byte-Code ein
    • Transparent für User; Auswirkungen beachten!

::: slides

LICENSE

Unless otherwise noted, this work is licensed under CC BY-SA 4.0. :::