Wildcard und class type

Hallo zusammen,

ich möchte mir eine Klasse bereitstellen, die das speichern und laden von JSON - Dateien vereinfacht.

Beim Laden aus einer JSON Datei muss ich jedoch den Type der Klasse
Java:
Class<?> classOfT
angeben, damit dieses auch deserialisiert werden kann.

Aktueller Stand der Klasse

Java:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

import com.google.gson.Gson;

public class GsonObjectReader<T> {

    private final String path;
    
    public GsonObjectReader(final String path) {
        this.path = path;
    }
    
    public T read(Class<T> classId) {
        T t = null;
        
        try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
            
            t = (T) new Gson().fromJson(reader, classId);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        return t;
    }
}

Nur stört mich an dieser Stelle, dass ich in der Methode
Java:
read(...)
noch extra die
Java:
Class<T>
mitgeben muss, damit die Methode
Java:
fromJson
passend aufgerufen werden kann.


Meine Frage
Gibt es eine Möglichkeit, den Parameter, den ich der Methode read als Parameter mitgeben, über den Wildcard Type T zu bekommen, oder ist das so, wie ich es mir vorstelle nicht möglich?
 
Es geht nicht direkt, allerdings kannst du einen beliebten Trick anwenden, indem du deine `GsonObjectReader` Klasse als abstract markieren und vom Nutzer/Client mit einem konkreten Typargument ableiten lässt. Das macht aus der Klasse, die der Nutzer verwendet, eine konkrete Typdeklaration und bei abgeleiteten Klassen wird das Typargument erhalten:
Java:
public abstract class GsonObjectReader<T> {
  private final String path;
  protected GsonObjectReader(String path) {
    this.path = path;
  }
  public final T read() {
    Class<?> clazz = myClass();
    // Nutze clazz hier...
  }
  private Class<?> myClass() {
    ParameterizedType t = (ParameterizedType) getClass().getGenericSuperclass();
    Class<?> clazz = (Class<?>) t.getActualTypeArguments()[0];
    return clazz;
  }
}
Nutzung ist dann:
Java:
new GsonObjectReader<MyClass>("./pfad/der/datei.json"){}.read();
Beachte die geschweiften Klammern hier, die daraus eine anonyme Klasse machen, die sofort instanziiert wird und auf deren Instanz wir dann read() aufrufen!

Das ist übrigens dasselbe Konzept wie bei "Type Tokens": https://www.baeldung.com/java-super-type-tokens
 
Zuletzt bearbeitet:
Wozu die Klasse abstract halten? Sehe dafür ehrlich gesagt keinen Grund.
Das dient nur dazu, dass die Klasse selbst nicht instanziiert werden kann, weil die Lösung ansonsten nicht funktioniert.
Die Klasse ohne Subclassing selbst zu instanziieren, erlaubt es eben nicht, das Typargument zu behalten, da Typargumente nur bei Typdeklarationen gespeichert werden. Wir brauchen also die Klasse als Superklasse einer anderen vom Benutzer definierten Klasse mit Typargument.
Das ist der ganze Punkte der Type Tokens Lösung. Hast du dir die Lösung und insbesondere die Methode `myClass()` darin mal angeguckt? Dort wird auf den generischen Supertyp zugegriffen, um sein Typargument zu bekommen.
 
Das ergibt keinen Sinn. Der einzige Unterschied ist, dass bei deiner Lösung die zusätzlichen "{}" geschrieben werden müssen beim Instanziieren da "abstract". Eine einfache Klasse mit Generic Angabe würde genauso funktionieren:

Code:
public class Testing<T>
{
	private String path;
	
	public Testing(String path)
	{
		this.path = path;
	}
	
	public T read()
	{
		ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
	    Class<?> clazz = (Class<?>) type.getActualTypeArguments()[0];
	    
	    T t = null;
        
        try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
            
            t = (T) new Gson().fromJson(reader, clazz);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        return t;
	}
	
	public static void main(String[] args)
	{
		new Testing<Devices>("path/to/json").read();
	}
}
 
Und genau das (also ohne explizite Subklasse) funktioniert eben nicht, wie @mihe7 oben schon geschrieben hat, da hier per Type Erasure die Generics Typparameter und die Typargumente bei der Instanziierung verschwinden.
Deine Lösung funktioniert alleine deswegen schon nicht, weil du jetzt ja auch auf getGenericSuperclass() zugreifst, wo es gar keine generic Superclass (nämlich nur java.lang.Object) mehr gibt.
Der alleinige Grund, warum bei meiner Lösung die geschweiften Klammern stehen, ist der, dass wir eine Typdeklaration (in diesem Fall eine anonyme Klasse) brauchen, um die Typargumente zu behalten!
Ich empfehle dir sehr, dir nochmal die verlinkte Seite zu "Super Type Tokens" durchzulesen.
 
Vielleicht nochmal deutlicher zur Veranschauung:
Das hier:
Java:
new Testing<Devices>("path/to/json");
ist eine Typinstanziierung, bei der sämtliche Typargumente aus dem Bytecode unwiederbringlich verschwinden.

Das hier:
Java:
new Testing<Devices>("path/to/json"){};
sind zwei Dinge:
1. eine Typdeklaration (eine anonyme Klasse, die von Testing<Devices> (per Typargument) ableitet)
2. eine Typinstanziierung (nämlich von dieser anonymen Klassen)
Der Punkt ist, dass die Typdeklaration eben sehr wohl das Typargument im Bytecode behält, weil es eine Klassendeklaration ist und im Bytecode die Typargumente, mit denen generische Supertypen abgeleitet werden, drin stehen bleiben und per Reflection abfragbar sind.
 
Hat mir auch sehr weitergeholfen. Wirklich sehr interessant, wie gut es veranschaulicht wurde.

Bin selbst noch nicht ganz so tief in Generics, Wildcards u.ä. Themen vertieft. Wirkt also für mich immer noch etwas exotisch. Wird aber mit der Zeit sicherlich werden.

Danke noch einmal an jeden hier im Thread für die guten Tipps, Tricks und Ratschläge.
 

Zurück
Oben