Konkreten Typ eines generischen Parameters einer Klasse ermitteln?

Hallo,

danke an Sascha und Artorius - ja das ist genau das, was ich gesucht habe. Ich hatte es vorher immer via "getSuperclass()" probiert, auf "getGenericSuperclass()" bin ich nicht gekommen - danke!

Wegen der anderen Threads - ich denke die sind thematisch ähnlich, aber nicht gleich, was man ja an der Lösung sieht.
 
Hallo,

wenn ich nochmal eine Frage hinterher stellen dürfte, ich habe nun folgendes Konstrukt:

Java:
public abstract class AbstractConverter< S, T > implements Converter< S, T >
{
	private Class< S >	sourceType;
	private Class< T >	targetType;
	
	@SuppressWarnings( "unchecked" )
	public AbstractConverter()
	{
		ParameterizedType genericSuperclass = 
			(ParameterizedType)getClass().getGenericSuperclass();

		sourceType = (Class< S >)genericSuperclass.getActualTypeArguments()[ 0 ];
		targetType = (Class< T >)genericSuperclass.getActualTypeArguments()[ 1 ];
	}

    // [...]
}

Wenn ich nun eine konkrete Klasse daraus mache:

Java:
public class StringToIntegerConverter extends AbstractConverter< String, Integer >
{
	// [...]
}

dann sind "sourceType" und "targetType" entsprechend "class java.lang.String" und "class java.lang.Integer", sie wurden also korrekt ermittelt.

Wenn ich nun aber folgende konkrete Klasse mache:

Java:
public class StringToClassConverter extends AbstractConverter< String, Class< ? > >
{
	// [...]
}

dann schlägt die Zeile

Java:
targetType = (Class< T >)genericSuperclass.getActualTypeArguments()[ 1 ];

von oben (erstes Listing) fehl mit der Exception:

Java:
java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to java.lang.Class
	at de.[...].converter.AbstractConverter.<init>(AbstractConverter.java:36)


Diese Exception verstehe ich leider nicht, bzw. ich kann nicht ganz den Unterschied zwischen dem funktionierenden und dem nicht funktionierenden Beispiel sehen.

  • Liegt es an dem Wildcard "?" in "Class< ? >"?
  • Wie könnte ich dies lösen, sodass "Class< ? >" als richtiger "targetType" (siehe erstes Listing) erkannt wird?

Über Eure erneute Hilfe würde ich mich sehr freuen!
 
Hallo,

für einen generischen Typ Konverter der mit ParameterizedTypes umgehen kann gibts mehrere Implementierungsmöglichkeiten. Willst du als Konvertierungsziel Parameterisierte Typen verwenden so kannst du den oben angegeben TypVariable Trick verwenden. Eine andere Möglichkeit wäre die Verwendung von Raw Types also Class anstatt Class<?> (im Falle von <?> ist das IMHO sogar legitim....)

Siehe auch:
http://www.tutorials.de/forum/java/340418-generische-type-converter.html

Hier mal ein Beispiel für einen Generischen Converter der mit ParameterizedTypes und RawTypes umgehen kann:
(Siehe StringToClassConversion bzw. StringToGenericClassConversion)
Java:
package de.tutorials;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

public class ConverterExample {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		IConverter converter = new Converter();

		converter.register(new StringToIntegerConversion());
		converter.register(new StringToCollectionStringConversion());
		converter.register(new StringToCollectionIntegerConversion());
		converter.register(new StringToClassConversion());
		converter.register(new StringToGenericClassConversion());

		int value = converter.convert("4711", Integer.class);
		System.out.println(value);

		Collection<String> values = converter.convert("1,2,3,4",new TypeVariable<Collection<String>>() {});
		System.out.println(values + " " + values.iterator().next().getClass());

		Collection<Integer> numbers = converter.convert("1,2,3,4",new TypeVariable<Collection<Integer>>() {});
		System.out.println(numbers + " " + numbers.iterator().next().getClass());
		
		
		Class<?> clazz = converter.convert("java.lang.String", new TypeVariable<Class<?>>(){});
		System.out.println(clazz);
		
		Class<?> clazz1 = converter.convert("java.lang.String", Class.class);
		System.out.println(clazz1);
		

	}
	
	static class StringToClassConversion implements IConversion<String,Class>{
		public Class convert(String input) {
			try {
				return Class.forName(input);
			} catch (ClassNotFoundException e) {
				throw new RuntimeException(e);
			}
		}
	}
	
	static class StringToGenericClassConversion implements IConversion<String,Class<?>>{
		public Class<?> convert(String input) {
			try {
				return Class.forName(input);
			} catch (ClassNotFoundException e) {
				throw new RuntimeException(e);
			}
		}
	}
	

	static class StringToIntegerConversion implements
			IConversion<String, Integer> {
		public Integer convert(String input) {
			return Integer.valueOf(input);
		}
	}

	static class StringToCollectionStringConversion implements
			IConversion<String, Collection<String>> {
		public Collection<String> convert(String input) {
			return Arrays.asList(input.split(","));
		}
	}

	static class StringToCollectionIntegerConversion implements
			IConversion<String, Collection<Integer>> {
		public Collection<Integer> convert(String input) {
			Collection<Integer> numbers = new ArrayList<Integer>();
			for (String item : input.split(",")) {
				numbers.add(Integer.valueOf(item));
			}
			return numbers;
		}
	}

	static interface IConversion<TInput extends Object, TOutput extends Object> {
		TOutput convert(TInput input);
	}

	static interface IConverter {

		void register(IConversion<? extends Object, ? extends Object> conversion);

		<TOutput> TOutput convert(Object o, Type outputType);

		<TOutput> TOutput convert(Object o, TypeVariable<?> typeVariable);
	}

	static class Converter implements IConverter {

		final static String CONVERSION_METHOD_NAME = "convert";
		Map<Type, Map<Type, IConversion<Object, Object>>> inputToOutputConverters;

		public Converter() {
			inputToOutputConverters = new LinkedHashMap<Type, Map<Type, IConversion<Object, Object>>>();
		}

		@Override
		@SuppressWarnings("unchecked")
		public void register(
				IConversion<? extends Object, ? extends Object> conversion) {
			ConversionInfo conversionInfo = analyze(conversion);

			if (conversionInfo != null) {
				Map<Type, IConversion<Object, Object>> outputToConverter = inputToOutputConverters
						.get(conversionInfo.getInputType());

				if (outputToConverter == null) {
					outputToConverter = new LinkedHashMap<Type, IConversion<Object, Object>>();
					inputToOutputConverters.put(conversionInfo.getInputType(),
							outputToConverter);
				}

				outputToConverter.put(conversionInfo.getOutputType(),
						(IConversion<Object, Object>) conversion);
			}
		}

		@Override
		@SuppressWarnings("unchecked")
		public <TOutput> TOutput convert(Object o, TypeVariable<?> typeVariable) {
			return (TOutput) convert(o, typeVariable.getType());
		}

		@Override
		@SuppressWarnings("unchecked")
		public <TOutput> TOutput convert(Object o, Type outputType) {
			Type compatibleInputType = findCompatibleInputType(o.getClass());
			Type compatibleOutputType = findCompatiblOutputType(
					compatibleInputType, outputType);

			TOutput output = null;

			if (compatibleInputType != null && compatibleOutputType != null) {
				IConversion<Object, Object> conversion = inputToOutputConverters
						.get(compatibleInputType).get(compatibleOutputType);
				System.out.println(String.format(
						"Using conversion: %s to convert from: %s to: %s",
						conversion, compatibleInputType, compatibleOutputType));
				output = (TOutput) conversion.convert(o);

			}

			return output;
		}

		private Type findCompatibleInputType(Type inputType) {
			Type result = null;

			if (inputType != null) {
				if (inputToOutputConverters.containsKey(inputType)) {
					result = inputType;
				} else {
					for (Type candidate : inputToOutputConverters.keySet()) {
						if (typesAreCompatible(candidate, inputType)) {
							result = candidate;
							break;
						}
					}
				}
			}

			return result;
		}

		private Type findCompatiblOutputType(Type inputType, Type outputType) {
			Type result = null;

			if (inputType != null && outputType != null) {
				if (inputToOutputConverters.get(inputType).containsKey(
						outputType)) {
					result = outputType;
				} else {
					for (Type candidate : inputToOutputConverters
							.get(inputType).keySet()) {
						if (typesAreCompatible(candidate, outputType)) {
							result = candidate;
							break;
						}
					}
				}
			}

			return result;
		}

		@SuppressWarnings("unchecked")
		private boolean rawTypesAreCompatible(Type candidate, Type inputType) {
			return ((Class) candidate).isAssignableFrom((Class) inputType);
		}

		private boolean typesAreCompatible(Type candidate, Type type) {
			boolean result = false;

			if (type instanceof Class && candidate instanceof Class
					&& rawTypesAreCompatible(candidate, type)) {
				result = true;
			}

			if (!result && type instanceof ParameterizedType
					&& candidate instanceof ParameterizedType) {
				ParameterizedType pType = (ParameterizedType) type;
				ParameterizedType pCandidate = (ParameterizedType) candidate;

				if (rawTypesAreCompatible(pCandidate.getRawType(), pType
						.getRawType())
						&& Arrays.equals(pCandidate.getActualTypeArguments(),
								pType.getActualTypeArguments())) {
					result = true;
				}
			}

			return result;
		}

		private ConversionInfo analyze(
				IConversion<? extends Object, ? extends Object> converter) {
			ConversionInfo conversionInfo = null;
			for (Method conversionMethodCandidate : converter.getClass()
					.getDeclaredMethods()) {
				if (isConversionMethod(conversionMethodCandidate)) {

					Type returnType = conversionMethodCandidate.getReturnType();
					Type parameterType = conversionMethodCandidate
							.getParameterTypes()[0];

					if (conversionMethodCandidate.getGenericReturnType() instanceof ParameterizedType) {
						returnType = (ParameterizedType) conversionMethodCandidate
								.getGenericReturnType();
						if (conversionMethodCandidate
								.getGenericParameterTypes()[0] instanceof ParameterizedType) {
							parameterType = (ParameterizedType) conversionMethodCandidate
									.getGenericParameterTypes()[0];
						}
					}

					conversionInfo = new ConversionInfo(parameterType,
							returnType);

					break;
				}
			}

			return conversionInfo;
		}

		private boolean isConversionMethod(Method conversionMethodCandidate) {
			return CONVERSION_METHOD_NAME.equals(conversionMethodCandidate
					.getName())
					&& conversionMethodCandidate.getParameterTypes().length == 1
					&& !Void.class.equals(conversionMethodCandidate
							.getReturnType())
					&& !conversionMethodCandidate.isBridge();
		}

	}

	static class ConversionInfo {
		Type inputType;
		Type outputType;

		public ConversionInfo(Type inputType, Type outputType) {
			this.inputType = inputType;
			this.outputType = outputType;
		}

		public Type getInputType() {
			return inputType;
		}

		public Type getOutputType() {
			return outputType;
		}
	}

	static class TypeVariable<TType> {
		Type type;

		public TypeVariable() {
			this.type = ((ParameterizedType) getClass().getGenericSuperclass())
					.getActualTypeArguments()[0];
		}

		public Type getType() {
			return type;
		}
	}

}

Ausgabe:
Code:
Using conversion: de.tutorials.ConverterExample$StringToIntegerConversion@d8a7efd to convert from: class java.lang.String to: class java.lang.Integer
4711
Using conversion: de.tutorials.ConverterExample$StringToCollectionStringConversion@196cd7d5 to convert from: class java.lang.String to: java.util.Collection<java.lang.String>
[1, 2, 3, 4] class java.lang.String
Using conversion: de.tutorials.ConverterExample$StringToCollectionIntegerConversion@6d632c2d to convert from: class java.lang.String to: java.util.Collection<java.lang.Integer>
[1, 2, 3, 4] class java.lang.Integer
Using conversion: de.tutorials.ConverterExample$StringToGenericClassConversion@9e97676 to convert from: class java.lang.String to: java.lang.Class<?>
class java.lang.String
Using conversion: de.tutorials.ConverterExample$StringToClassConversion@3e60420f to convert from: class java.lang.String to: class java.lang.Class
class java.lang.String

Gruß Tom
 
Hallo Thomas,

vielen Dank für das ausführliche Beispiel - ich hätte nicht gedacht, dass das jetzt noch viel komplizierter wird, denn im Prinzip funktioniert meine Konvertierung, wie oben in den Listings, ja bereits. Nur eben dieser eine Cast

Java:
targetType = (Class< T >)genericSuperclass.getActualTypeArguments()[ 1 ];

funktioniert nicht, und ich verstehe nicht genau warum.

Falls die Antwort in deinem größeren Beispiel steckt, magst Du mir einen Hinweis geben, wo? :)
 
Hallo,

ParameterizedType impls lassen sich nicht direkt zu Class casten.Das heißt in deinem Fall: Wenn der ParameterizedType nur ein TypeArgument hat und dieses ein Wildcard ist, so kannst du den RawType das ParameterizedTypes verwenden (getRawType()). In anderen Fällen muss du bei deinem Converter noch ein wenig Zusatzlogik hinterlegen. Wenn du generische Source- und Target-Typen unterstützen möchtest, so kommst du nicht drum herum dir auch die Fälle zu implementieren bei denen Source bzw. Target selbst auch wieder ParameterizedTypes sind. Diese musst du dann analysieren und in ein Format überführen bei dem du die Informationen zum RawType bzw. den ActualTypeArguments bequem auswerten und zugreifen kannst um dann einen passenden Lookup durchführen zu können.

Beispielsweise könnte man sich vorstellen, dass man einen Converter für List<String> nach List<Integer> oder ein List<String> nach List<? extends Number> etc.
Das hab ich so in meinem Beispiel schon ansatzweise drinnen.

Gruß Tom
 
Hallo Thomas,

hm ja, ich verstehe, zumindest Ansatzweise ;) Ich verstehe auf jeden Fall, warum wir hier eine Fallunterscheidung machen müssen. Dein Beispiel ist etwas komplexer, das muss ich für mich noch länger durcharbeiten denke ich.

Allerdings hast Du in Deinem Beispiel, wenn ich das richtig sehe, gegenüber meinem Vorhaben auch noch eine "Flexibilitätsstufe" mehr. Bei mir geht es ja, zusammengefasst, nur darum, dass der Benutzer der API die Methode "getSourceType" und "getTargetType" nicht selbst implementieren muss. Nehmen wir nochmal an, es soll einen Converter geben, der von String nach List<String> konvertiert. Wie müsste dann meine "getTargetType" Methode aussehen? Der Zieltyp ist ja "List<String>", aber wie sähe das denn manuell-implementiert aus? Sowas in der Art:

Java:
@Override
public Class< List< String > > getTargetType()
{
	return List< String >.class;
}

Das Beispiel ist natürlich quaak, es kompiliert ja nichtmal. Aber vielleicht macht es deutlich, was ich in der Methode erreichen will.
 
Zurück