Problem mit generischen Typen

Muepe32

Erfahrenes Mitglied
Hallo zusammen,

Nachdem ich mich in letzer Zeit nur oberflächlich mit Java beschäftigt habe hat mich nun das Interesse gepackt mein nächstes Projekt, das ich eigentlich in C# - wie immer - programmieren wollten mal mit Java anzupacken.

Erwartungsgemäss funktioniert vieles eigentlich problemlos, da es doch nicht so gewaltige Unterschiede gibt zwischen den zwei Sprachen. Jedoch gibt es jetzt ein Problem mit generischen Typen. In C# ist es problemlos möglich von einem generischen Typen eine Instanz zu erstellen durch ein Constraint:
C#:
class Generic<T> where T : new()
{
public:
   T Create() { return new T(); }
}

Der Typ den man nun an Generic übergibt muss nun einen parameterlosen Konstruktor besitzen und auch instanzierbar sein (kein Interface, keine abstrakte Klasse, ...). In Java habe ich das bisher nicht hinbekommen. Ist tatsächlich so, dass so etwas elementares nur über Umwege möglich ist, oder gibt es etwas vergleichbares? Bzw um genau zu sein habe ich es bisher noch nicht mal über Umwege hinbekommen eine Instanz von dem generisch angegebenen Typen zu erstellen.

Gruss
Muepe
 
Also ohne mich jetzt selbst mit generischen Typen auszukennen würde ich meinen das sowas in Java nur mit Reflections möglich sein wird.
 
Hallo,

das was du machen willst (Erzeugung von generischen Typen) geht mit Java eigentlich nicht bzw. wird es von Java nicht direkt unterstützt. Jedoch ist es "prinzipiell" möglich das von dir gewünschte Verhalten nachzuahmen.

Generics in Java und C# sind unterschiedlich implementiert. Bei Java gibt es zur Laufzeit keine Informationen mehr zu Generics, denn diese werden vom Java Compiler (bis auf wenige Ausnahmen) entfernt (Stichwort: Type Erasure). Bei C# bleiben die Informationen zu Generics nach dem kompilieren erhalten und lassen sich zur Laufzeit auswerten.

Es gibt für Java ein paar Tricks wie man den Compiler dazu bringen kann Code zu generieren der die generischen Typinformationen in machen Situationen erhält.

Ein Trick ist beispielsweise einer Methode mit generischem Varargs (..., ellipse) Parameter und dem selben Rückgabetyp zu erstellen und diese Methode dann im Code aufzurufen. Der Java Compiler fasst dann die beim Methodenaufruf übergebenen Parameter zusammen, indem er mit Type Inferenz den Typ ermittelt welcher den übergebenen Parametern zugrundeliegt und anschließend ein Array vom dem Typ erzeugt.

Siehe Beispiel.

Beispiel:
Java:
package de.tutorials.training;

import javax.swing.JButton;
import javax.swing.table.DefaultTableModel;

public class GenericTypeFactoryExample {

  public static void main(String[] args) {
    System.out.println(new Generic<Object>().create());
    System.out.println(new Generic<DefaultTableModel>().create());
    
    
    JButton button = create(); 
    // Vom Java Compiler wird nun folgender Code erzeugt:
    //JButton button = create(new JButton[0]);
    //Durch Type Inference zum Rückgabetyp der create Methode wird der generische Parameter Typ auf JButton gesetzt 
    
    //Hier ein Beispiel für die explizite Angabe des zu verwendenden Typs 
    System.out.println(GenericTypeFactoryExample.<JButton>create());
    //Vom Java Compiler wird nun folgender Code erzeugt:
    //JButton button = create(new JButton[0]);
    
    
    
  }
  
  public static <T> T create(T...ts){
    try {
      return (T)ts.getClass().getComponentType().newInstance();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  
  static class Generic<T>{
    @SuppressWarnings("unchecked")
    public T create(T...ts){
      try {
        return (T)ts.getClass().getComponentType().newInstance();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }  
  }
}

Ausgabe:
Code:
java.lang.Object@5d0385c1
javax.swing.table.DefaultTableModel@164f1d0d
javax.swing.JButton[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.BorderUIResource$CompoundBorderUIResource@1cf11404,flags=296,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIResource[top=2,left=14,bottom=2,right=14],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=,defaultCapable=true]

Gruß Tom
 
Hallo Tom,

Besten Dank für deine Infos, das hat mich schon viel weiter gebracht! Allerdings habe ich jetzt noch ein Problem bezüglich Vererbung. Meine Klasse sieht folgendermassen aus:
Java:
public class ListenServer<T extends ClientSocket> {
//...
	@SuppressWarnings("unchecked")
	private T createSocketWrapper(T...ts) {
		try {
			return (T)ts.getClass().getComponentType().newInstance();
		} catch (Exception e) {
			System.out.println(ts.getClass().getComponentType().toString());
			throw new java.lang.RuntimeException(e);
		}
	}
//...
}

In meinem Testprojekt habe ich dann folgende Versuchsklasse gemacht:
Java:
public class LogonClient extends ClientSocket {
	@Override
	protected void OnConnect() {
		System.out.println("Client connected: " + mClientSocket.toString());
	}
}

Entsprechend ist ClientSocket eine abstrakte Klasse. In der Verwendung sieht es folgendermassen aus:
Java:
ListenServer<LogonClient> listener = new ListenServer<LogonClient>();

Das Problem nun ist jetzt aber, dass in der Klasse createSocketWrapper Methode versucht wird eine Instanz der Klasse ClientSocket zu erstellen und nicht von LogonClient. Das schlägt natürlich (rückwirkend betrachtet zum Glück) fehl da ClientSocket abstrakt ist. Aber warum das?

Gruss
Muepe
 
Nachdem ich mir das über Nacht mal bisschen durch den Kopf habe gehen lassen ist mir eigentlich auch klar geworden warum das obige Verhalten auftritt:
die createSocketWrapper Methode wird innerhalb der Klasse LogonClient aufgerufen und das generische vararg Element wird daher bereits beim Compilieren der Klasse LogonClient ausgefüllt. Zu diesem Zeitpunkt kennt der Compiler den finalen Typ LogonClient natürlich noch nicht und muss einen möglichst guten Treffer, der aber immer funktioniert für T finden. Das ist dann natürlich ClientSocket, da T sicher immer von ClientSocket erbt. Ich habe es nun so gemacht, dass der Konstruktor von LogonClient noch variable generische Parameter hat, die dann verwendet werden können.
Java:
	public ListenServer(String addr, int port, T... ts) throws IOException {
		//...		
		mGenericCreator = new Generic<T>(ts.getClass().getComponentType());
	}

Wobei dann mGenericCreator eine Instanz der folgenden privaten Klasse ist:
Java:
	private class Generic<S> {
		public Generic(Class<?> typeClass) {
			mTypeClass = typeClass;
		}	
		
		@SuppressWarnings("unchecked")
		public S createSocketWrapper() {
			System.out.println(mTypeClass.toString());
			try {
				return (S)mTypeClass.newInstance();
			} catch (Exception e) {
				throw new java.lang.RuntimeException(e);
			}
		}
		
		private Class<?> mTypeClass;
	}

Damit funktioniert jetzt eigentlich alles so wie ich es möchte. Ist zwar ziemlich umständlich (leider, da ich als von C++ und C# kommender natürlich eine viel grössere Flexibilität von Generics gewohnt bin und mein Code exzessiven Gebrauch davon macht), aber am Ende zählt ja, dass es funktioniert :D

Gruss
Muepe
 
Zurück