Types - Eine Library für ... Typen

Ja, ich hab’s nicht so mit kreativen Namen…

Vor einigen Jahren hatte ich da mal was angefangen, und damals ziemlich viel (also… teilweise schon richtig viel) Zeit investiert. Es ging dabei um die Behandlung von Typen in Java. Ganz konkret unter anderem die Frage, wann ein Typ einem anderen zuweisbar ist. Also, im einfachsten (!) Fall sowas wie

Collection<? extends Number> = List<Integer> // Geht
Collection<? extends Number> = List<String> // Geht nicht

Damals hatte ich eigentlich Ziele, die noch deutlich darüber hinausgingen. Richtig kompliziert wird das, wenn man Typparameter unterstützen will:

Collection<T> = List<Integer> // Geht das?!

Was muss (oder darf) T an dieser Stelle sein, damit die Zuweisung gültig ist? Das ist aber zu kompliziert (für mich) : Man muss da einen ganze “Typ-Kontext” aufbauen, in dem dann Schnitt- und Vereinigungsmengen irgendwelcher Bounds gebildet werden. (Der Code liegt lokal hier rum, aber … das ist nicht ausgereift, und deswegen auch nicht Teil der Library).

Die Library, in der die Teile liegen, die … einigermaßen “stabil” (und vielleicht für den einen oder anderen “nützlich”) sind, liegt hier:

Und die oben angedeutete Frage kann damit beantwortet werden:

// Build a type using a TypeBuilder (here, the type "List<? extends Number>")
Type listWithSubtypeOfNumber = Types
    .create(List.class)
    .withSubtypeOf(Number.class)
    .build();

// or parse some type from a String
Type listWithIntegers = 
    Types.parse("java.util.List<java.lang.Integer>");

// Check assignability
System.out.println(Types.isAssignable(
    listWithSubtypeOfNumber, listWithIntegers)); // true
1 „Gefällt mir“

Nich schlecht. Vielleicht kannst du auch eine Funktionalität wie in Guavas TypeToken einbauen, um den Type zu einer gegebenen generischen Klasse konstruieren zu können: https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/reflect/TypeToken.html

Ja, die Klasse bzw. den Teil von Guava kenne ich natürlich. Und tatsächlich hatte ich Guava und TypeTokens eine zeitlang dort verwendet, wo jetzt die Types verwendet wird.

(Ich hatte damals gehofft, dass https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/reflect/TypeResolver.html die Funktionalität böte, die ich brauchte, aber … das ist nur ein Bobbycar, wo ich einen Formel-1-Wagen brauche, dessen „Motor“ wohl in jdk7u-langtools/Types.java at master · openjdk-mirror/jdk7u-langtools · GitHub steckt…)

Die TypeToken-Klasse bietet zwar einige praktische Funktionen, ausgerichtet auf die Rolle, in der sie innerhalb von Guava verwendet wird. Aber viel von der eigentlichen Funktionalität gibt es auf Ebene von Type-Objekten schon. D.h. viel von der TypeToken-Funktionalität ist im Endeffekt nur ein „wrapping“ um Type-Objekte.

(Alles, was ich jetzt sage, ist nicht wertend. Nur als Abgrenzung. Und ich bin sicher, dass bei Guava Leute an den Type-Klassen arbeiten, die versteckte Stolperfallen berücksichtigen, die ich nicht berücksichtigt habe. Es ist ja nur Version 0.0.1-SNAPSHOT :wink: )

Welche Funktionalität du genau meintest, ist nicht klar:


Das „klassische“

TypeToken<List<String>> typeToken = new TypeToken<List<String>>() {};

wird in der Library eben mit

Type type = Types.create(List.class).withType(String.class).build();

erledigt. (Oder mit Types.parse("java.util.List<String>"), wenn man parsen statt bauen will).

Dafür gibt es zwei Gründe:

  • Ich fand die „leeren, anonymen Klassen“ von TypeToken recht sperrig
  • Das ganze ist besonders unpraktisch, wenn die Typen zur Laufzeit erstellt werden sollen. Es gibt in Guava da dann zwar auch die where-Methode, die so etwas erlaubt (wieder mit anonymen Klassen…), aber das ist dann insgesamt IMHO doch weniger bequem als der TypeBuilder

Vielleicht meintest du mit dem „Type zu einer gegebenen generischen Klasse konstruieren“ aber auch das das „Herausfinden von Typparametern“, das TypeToken anbietet - wie im Beispiel aus den JavaDocs:

abstract class IKnowMyType<T> {
    TypeToken<T> type = new TypeToken<T>(getClass()) {};
}
new IKnowMyType<String>() {}.type => String

Aber das ist auch nur eine Art „Wrapper“ um eine Funktionalität, die ansonsten recht unabhängig von der TypeToken-Klasse selbst sein kann - und die in der Types-Library auch weitgehend unabhängig davon ist:

import de.javagl.types.Types;

abstract class IKnowMyType<T> {}
       
public class NoTypeToken
{
    public static void main(String[] args)
    {
        IKnowMyType<String> t = new IKnowMyType<String>() {};
        System.out.println(Types.getTypeArguments(
            t.getClass().getGenericSuperclass())); // [class java.lang.String]
    }
}

Jetzt könnte man argumentieren das das nicht so bequem ist, wie die TypeToken-Variante, aber das hängt wohl auch von der angedachten Verwendung ab.


Ich war bei der Verwendung von TypeToken noch an andere Grenzen gestoßen. Zum Beispiel kann es einen Typ wie ? extends Number nicht repräsentieren. Das ist eben ein WildcardType. D.h. wenn man das braucht, muss man doch wieder auf die rohen Type-Objekte zurückgreifen. Und genau die bietet die Library (unter anderem) an:

    Type n = Types.createWildcardType(
        null, new Type[] { Number.class });
    System.out.println(n); // ? extends java.lang.Number

Schon ein kleines update. (Das, was bisher auf GitHub liegt, ist und war (wie gesagt) nur ein Fragment…).

Wer schon immer mal programmatisch rausfinden wollte, dass ArrayList<Integer> insgesamt 40 supertypes hat, nämlich …

java.util.ArrayList<java.lang.Integer>
java.util.ArrayList
java.util.AbstractList
java.util.AbstractCollection
java.lang.Object
java.util.Collection
java.lang.Iterable
java.util.List
java.util.RandomAccess
java.lang.Cloneable
java.io.Serializable
java.util.ArrayList<? extends java.lang.Number>
java.util.ArrayList<?>
java.util.ArrayList<? extends java.io.Serializable>
java.util.ArrayList<? extends java.lang.Comparable>
java.util.AbstractList<java.lang.Integer>
java.util.AbstractList<? extends java.lang.Number>
java.util.AbstractList<?>
java.util.AbstractList<? extends java.io.Serializable>
java.util.AbstractList<? extends java.lang.Comparable>
java.util.AbstractCollection<java.lang.Integer>
java.util.AbstractCollection<? extends java.lang.Number>
java.util.AbstractCollection<?>
java.util.AbstractCollection<? extends java.io.Serializable>
java.util.AbstractCollection<? extends java.lang.Comparable>
java.util.Collection<java.lang.Integer>
java.util.Collection<? extends java.lang.Number>
java.util.Collection<?>
java.util.Collection<? extends java.io.Serializable>
java.util.Collection<? extends java.lang.Comparable>
java.lang.Iterable<java.lang.Integer>
java.lang.Iterable<? extends java.lang.Number>
java.lang.Iterable<?>
java.lang.Iterable<? extends java.io.Serializable>
java.lang.Iterable<? extends java.lang.Comparable>
java.util.List<java.lang.Integer>
java.util.List<? extends java.lang.Number>
java.util.List<?>
java.util.List<? extends java.io.Serializable>
java.util.List<? extends java.lang.Comparable>

der kann das nun, mit der Supertypes-Klasse.

(Tatsächlich hat sie unendlich viele supertypen. Das ist etwas, worüber ich damals recht heftig gestolpert bin. Schon der Typ Integer hat als supertypen eigentlich

Comparable<? super Integer>
Comparable<? super Comparable<? super Integer>>
Comparable<? super Comparable<? super Comparable<? super Integer>>>
...

deswegen werden bei der Berechnung der Supertypen dort nur die Raw-Types für die Typparameter in Betracht gezogen…)

Jetzt auch gleich in der Maven Central:

<dependency>
  <groupId>de.javagl</groupId>
  <artifactId>types</artifactId>
  <version>0.0.1</version>
</dependency>