Frage zu generischer Methode

DarthShader

Erfahrenes Mitglied
Hallo,

ich habe eine Frage zu Java's generischen Methoden. Ich habe hier folgende statische Methode:

Java:
public static <T> T getInstance( Class< ? extends T > clazz )
{
	Object s = null;
	
	// ...einiges an Code...
	
	return clazz.cast( s );
}

Ich kann sie so verwenden, dass ich sage:


Java:
AndereKlasse andereKlasse =  Service.getInstance( AndereKlasse.class );

oder

Java:
GanzAndereKlasse ganzAndereKlasse =  Service.getInstance( GanzAndereKlasse.class );

Man sieht also, dass ich den zurückgegebenen Typ von getInstance nicht casten muss, da er generisch ist.

Was ich nun aber möchte:

Man kann der Methode getInstance momentan alle möglichken Klassen übergeben, egal was. Gibt es nicht eine Möglichkeit, das zu beschränken? Sagen wir, getInstance soll als Parameter nur Klassen entgegeben nehmen, die das Interface "Test" implementieren (also vom Typ "Test" sind)? Ich denke dazu muss man die Signatur der Methode irgendwie entsprechend verändern, speziell dieses "Class< ? extends T > clazz" - aber ich weiß leider nicht wie.

Ich hoffe, mir kann dabei jemand helfen.


Vielen Dank!
 
Java:
public static <T extends Test> T getInstance( Class<T> clazz )

Oder
Java:
public static <T extends Test> T getInstance( )

mit Aufruf:
Java:
AndereKlasse andereKlasse =  Service.getInstance<AndereKlasse>( );
 
Hallo,

schau mal hier:
Java:
/**
 * 
 */
package de.tutorials;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Thomas.Darimont
 * 
 */
public class GenericTypeInstantiationExample {

    /**
     * @param args
     */
    public static void main(String[] args) {
        IBubuService bubuService = Services.getInstance();
        System.out.println(bubuService);
        
        BubuService realBubuService = Services.getInstance();
        System.out.println(realBubuService);
    }

    static class Services {

        final static Map<Class<?>, Class<?>> BINDINGS = new HashMap<Class<?>, Class<?>>();

        static {
            BINDINGS.put(IBubuService.class, BubuService.class);
        }

        public static <TService extends IService> TService getInstance(
                TService... services) {
            Class<?> clazz = services.getClass().getComponentType();
            TService service = null;
            try {
                if (clazz.isInterface()) {
                    clazz = BINDINGS.get(clazz);
                }
                service = (TService) clazz.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return service;
        }
    }

    static interface IService {}
    static class BubuService implements IBubuService {}
    static interface IBubuService extends IService {}
}

Gruß Tom
 
Interessant, das ist quasi auch das, was ich schreiben wollte. Ich nehme mal an, meine Service.getInstance Aufrufe haben mich verraten ;-)
Allerdings gibt es auch Services (Beans) die man noch genauer Parametrisieren muss. Dafür benötige ich dann entsprechende Factories oder konfigurierbare Services... und wo landen wir dann? Genau, bei Spring :) Mal sehen, entweder ich baue meinen "sehr leichtgewichtigen" Service "Provider" noch weiter, oder ich integriere wirklich Spring mit einem Application Context.


Das mit den varargs ist wirklich nett, ich nehme mal an, deshalb muss man bei dem Ausdruck

Java:
IBubuService bubuService = Services.getInstance();

keinen Parameter für getInstance() übergeben? Woher weiß Java dann den generischen Typ, als dem Rückgabetyp der obigen Anweisung?

Nehmen wir mal an, man schreibt

Java:
Services.getInstance();

was ja durchaus ein gültiger Ausdruck ist. Was passiert dann, was für einen "inneren" generischen Typ nutzt die Methode dann?
 
Hallo,

was ja durchaus ein gültiger Ausdruck ist. Was passiert dann, was für einen "inneren" generischen Typ nutzt die Methode dann?
In diesem Fall nimmt der Compiler den nach der generischen Parameterdefinition der Methode allgemeinsten Rückgabetyp.
In unserem Fall ist der Parameter TService nach oben durch IService (TService extends IService) beschränkt. Hat der Compiler keine
möglichkeit den entsprechenden Rückgabetyp zu ermitteln so setzt er einfach den entsprechenden allgemeinen Typ ein in unserem Fall
ist das eben IService. Würden wür die generische Methodendefinition umschreiben in:
public static <TService> TService getInstance(TService... services) so würde der Kompiler als allgemeinsten Typ Object angeben.



hier mal ein erweitertes Beispiel:
Java:
/**
 * 
 */
package de.tutorials;

import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Thomas.Darimont
 * 
 */
public class GenericTypeInstantiationExample {

    /**
     * @param args
     */
    public static void main(String[] args) {
        IBubuService bubuService = Services.getInstance();
        System.out.println(bubuService);

        BubuService realBubuService = Services.getInstance();
        System.out.println(realBubuService);
        
        System.out.println(Services.getInstance());

        System.out.println(Services
                .getInstance(new TypeVariable<IBubuService>() {
                }));
        
    }

    static class Services {

        final static Map<Class<?>, Class<?>> BINDINGS = new HashMap<Class<?>, Class<?>>();

        static {
            BINDINGS.put(IBubuService.class, BubuService.class);
            BINDINGS.put(IService.class, DefaultService.class);
        }

        public static <TService extends IService> TService getInstance(
                TService... services) {
            Class<?> clazz = services.getClass().getComponentType();
            TService service = create(clazz);
            return service;
        }

        /**
         * @param <TService>
         * @param clazz
         * @return
         */
        @SuppressWarnings("unchecked")
        private static <TService> TService create(Class<?> clazz) {
            TService service = null;
            try {
                if (clazz.isInterface()) {
                    clazz = BINDINGS.get(clazz);
                }
                service = (TService) clazz.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return service;
        }

        public static <TService extends IService> TService getInstance(
                TypeVariable<?> typeVariable) {
            return create((Class<?>) typeVariable.getType());
        }
    }

    static interface IService {
    }
    
    static class DefaultService implements IService{}

    static class BubuService implements IBubuService {
    }

    static interface IBubuService extends IService {
    }

    static class TypeVariable<TType> {
        TType type;

        @SuppressWarnings("unchecked")
        protected TypeVariable() {
            this.type = (TType) ((ParameterizedType) getClass()
                    .getGenericSuperclass()).getActualTypeArguments()[0];
        }

        public TType getType() {
            return type;
        }

        public void setType(TType type) {
            this.type = type;
        }
    }
}

Mit dem obigen (TypeVariable) Trick arbeitet übrigens auch das Dependency Injection Framework Guice von google.
http://code.google.com/p/google-guice/

Damit hat man die Möglichkeit bestimmte Implementierungen an Interfaces mit unterschiedlichen Parameterisierungen
zu binden.

Zum Beispiel:
IFactory<String> -> StingFactory
IFactory<Integer> -> IntegerFactory

Gruß Tom
 
Hallo,

ich habe nun versucht, einen primitiven "Singleton-Container" zu schreiben, der mir bestimmte Klassen global zugänglich machen kann. Eine Methode daraus sieht so aus:

Java:
public static < T extends Object > T getInstance( T... types ) {
	Class< ? extends Object > clazz = types.getClass().getComponentType();
		
	for ( Object o : instances ) {
		if ( o.getClass().equals( clazz ) )
			return (T)o;       // warning
	}

	return null;
}

Die Zeile mit dem Kommentar "// warning" verursacht folgende Compiler-Warning:

"Type safety: Unchecked cast from Object to T"

Ich verstehe nicht, warum diese Warnung angezeigt wird bzw. was sie genau bedeutet. Kann mir das vielleicht jemand erklären? Was kann ich dagegen tun (ausser ein @SuppressWarnings ;)?

Vielen Dank für Eure Hilfe!
 
Zurück