it-swarm.com.de

Wie kann ich eine Klasse als Parameter übergeben und eine generische Sammlung in Java zurückgeben?

Ich entwerfe ein einfaches Datenzugriffsobjekt für meine Java-Anwendung. Ich habe einige Klassen (Datensätze), die eine einzelne Zeile in Tabellen wie User und Fruit darstellen.

Ich hätte gerne eine einzige Methode, um alle Datensätze eines bestimmten Typs zu erhalten.

Im Moment habe ich es so:

public List<User> getAllUsers() {
 ...
}

public List<Fruit> getAllFruits() {
 ...
}

....

Aber ich hätte gerne eine einzige polymorphe Methode wie diese (falsch):

public List<T> getAllRecords(Class<T> type) {
    if(type instanceof User) {
        // Use JDBC and SQL SELECT * FROM user
    } else if(type instanceof Fruit) {
        // Use JDBC and SQL SELECT * FROM fruit
    }
    return collection;
}

Beispiel für Verwendungen:

List<Fruit> fruits = myDataAccessObject.getAllRecrods(Fruit.class);
List<User> users = myDataAccessObject.getAllRecords(User.class);

Wie kann ich das in Java machen?

34
Jonas

Da Sie sagen, dass Sie keine Datenzugriffsmethoden in verschiedenen Klassen wünschen (im Kommentar zu einer Antwort von Anish), dachte ich mir, warum versuchen Sie nicht so etwas.

public class Records {

    public interface RecordFetcher<T>{
        public List<T> getRecords();
    }
    static RecordFetcher<Fruit> Fruit=new RecordFetcher<Fruit>(){
        public List<Fruit> getRecords() {
            ...
        }
    };


    static RecordFetcher<User> User=new RecordFetcher<User>(){
        public List<User> getRecords() {
            ...
        }   
    };

    public static void main(String[] args) {
        List<Fruit> fruitRecords=Records.Fruit.getRecords();
        List<User> userRecords=Records.User.getRecords();

    }
}

BEARBEITEN:

Ich möchte noch eine weitere meiner Implementierung hinzufügen.

public class Test 
{ 
    public static void main(String[] args) 
    { 
       Test dataAccess=new Test();
       List<Fruit> FruitList=dataAccess.getAllRecords(Fruit.myType);
       List<User> UserList=dataAccess.getAllRecords(User.myType);
    } 
    <T> List<T> getAllRecords(T cl)
    {
        List<T> list=new ArrayList<T>();
        if(cl instanceof Fruit)
        {
             // Use JDBC and SQL SELECT * FROM fruit
        }
        else if(cl instanceof User)
        {
            // Use JDBC and SQL SELECT * FROM user
        }
        return list;
    }
}
class Fruit
{
    static final Fruit myType;
    static {myType=new Fruit();}
}
class User
{
    static final User myType;
    static {myType=new User();}
}

EDIT:

Ich denke, diese Implementierung ist genau so, wie Sie gefragt haben

public class Test 
{ 
    public static void main(String[] args) throws InstantiationException, IllegalAccessException 
    { 
       Test dataAccess=new Test();

       List<Fruit> FruitList=dataAccess.getAllRecords(Fruit.class);

       List<User> UserList=dataAccess.getAllRecords(User.class);

    } 
    <T> List<T> getAllRecords(Class<T> cl) throws InstantiationException, IllegalAccessException
    {
        T inst=cl.newInstance();
        List<T> list=new ArrayList<T>();
        if(inst instanceof Fruit)
        {
             // Use JDBC and SQL SELECT * FROM user
        }
        else if(inst instanceof User)
        {
            // Use JDBC and SQL SELECT * FROM fruit
        }
        return list;
    }
}
21
Emil

Es sieht so aus, als wollten Sie das anpassen, was Josh Bloch als Typesafe Heterogenous Container -Muster bezeichnet: Sie übergeben ein Typ-Token Class<T> und Sie möchten einen List<T> zurückgeben.

Normalerweise kann THC einen Class<T> einer T auf typsichere Weise zuordnen. Da Sie jedoch stattdessen einen List<T> verwenden möchten, möchten Sie das verwenden, was Neal Gafter den super-Typ tokens nennt.

Der folgende Ausschnitt ist aus dem Code von Crazy Bob Lee in Neal Gafters Blog angepasst:

public abstract class TypeReference<T> {
    private final Type type;

    protected TypeReference() {
        Type superclass = getClass().getGenericSuperclass();
        if (superclass instanceof Class<?>) {
            throw new RuntimeException("Missing type parameter.");
        }
        this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
    }
    public Type getType() {
        return this.type;
    }
}

Jetzt können Sie ein super-Typ-Token wie folgt erstellen:

    TypeReference<String> stringTypeRef =
            new TypeReference<String>(){};

    TypeReference<Integer> integerTypeRef =
            new TypeReference<Integer>(){};

    TypeReference<List<Boolean>> listBoolTypeRef =
            new TypeReference<List<Boolean>>(){};

Im Wesentlichen übergeben Sie einen TypeReference<T> anstelle eines Class<T>. Der Unterschied ist, dass es keinen List<String>.class gibt, aber Sie können einen TypeReference<List<String>> machen.

So können wir unseren Container nun wie folgt herstellen (Folgendes ist aus dem ursprünglichen Code von Josh Bloch angepasst):

public class Favorites {
    private Map<Type, Object> favorites =
        new HashMap<Type, Object>();

    public <T> void setFavorite(TypeReference<T> ref, T thing) {
        favorites.put(ref.getType(), thing);
    }
    public <T> T getFavorite(TypeReference<T> ref) {
        @SuppressWarnings("unchecked")
        T ret = (T) favorites.get(ref.getType());
        return ret;
    }
}

Jetzt können wir die beiden zusammenstellen:

    Favorites f = new Favorites();
    f.setFavorite(stringTypeRef, "Java");
    f.setFavorite(integerTypeRef, 42);
    f.setFavorite(listBoolTypeRef, Arrays.asList(true, true));

    String s = f.getFavorite(stringTypeRef);
    int i = f.getFavorite(integerTypeRef);
    List<Boolean> list = f.getFavorite(listBoolTypeRef);

    System.out.println(s);    // "Java"
    System.out.println(i);    // "42"
    System.out.println(list); // "[true, true]"

Neal Gafter argumentierte in seinem Blog, dass mit etwas mehr Schnickschnack TypeReference für Supertyp-Tokens eine würdige Aufnahme in das JDK erfolgen wird.

Anlagen

Verweise

24

Du bist ziemlich nah dran.

public <T> LinkedList<T> getAllRecords(List<T> list) {
 ...
}

Dies wird als generische Methode bezeichnet.

Sie müssen einen Parameter wie List<T> angeben. Basierend auf dem Typ der Liste, die Sie übergeben, leitet Java den generischen Typ zurück.

Bearbeiten:

Die Antwort von Poly ist sehr gut. Es sollte für Sie leicht genug sein, die folgenden Schritte auszuführen und keine TypeReference-Klasse erstellen zu müssen.

List<Fruit> fruit = myDataAccessObject.getAllRecrods(new LinkedList<Fruit>());
List<User> users = myDataAccessObject.getAllRecords(new LinkedList<User>());
11
jjnguy

Nun, ich weiß wirklich nicht, ob Sie es auf diese Weise brauchen ... Aber hier ist ein polymorpher Ansatz. Es könnte irgendwo irgendwie helfen.

Erstellen Sie verschiedene Objekte für verschiedene Tabellen, die alle eine gemeinsame Schnittstelle implementieren. Das heißt, Sie repräsentieren jede Tabelle als Objekt.

import Java.util.LinkedList;

public class DataAccessTest 
{

    /**
     * @param args
     */
    public static void main(String[] args) 
    {
        DataAccess myDataAccessObject = new DataAccess();
        Type type1 = new Fruit();
        Type type2 = new User();
        LinkedList<Type> list1 = myDataAccessObject.getAllRecords(type1);
        LinkedList<Type> list2 = myDataAccessObject.getAllRecords(type2);
        LinkedList<Type> list3 = myDataAccessObject.getAllRecords(new Fruit());
        LinkedList<Type> list4 = myDataAccessObject.getAllRecords(new User());
    }
}

class DataAccess
{
    public LinkedList<Type> getAllRecords(Type type)
    {
        return type.getAllRecords();
    }
}

interface Type
{
    public LinkedList<Type> getAllRecords();
}

class Fruit implements Type
{
    public LinkedList<Type> getAllRecords()
    {
        LinkedList<Type> list = new LinkedList<Type>();
        list.add(new Fruit());
        return list;
    }
}

class User implements Type
{
    public LinkedList<Type> getAllRecords() 
    {
        LinkedList<Type> list = new LinkedList<Type>();
        list.add(new User());
        return list;
    }
}
3
aNish

Je nachdem, wie Sie Ihre Daten tatsächlich abrufen, können Sie Folgendes tun:

private static <T> List<T> getAll(Class<T> cls){
  List<T> fromSql = (List<T>) sql.query("SELECT * FROM objects WHERE type="+cls.getName());
  return fromSql;
}

Dies erfordert, dass Ihr sql-Objekt den korrekten Listentyp zurückgibt, den O/R-Mapper wie iBatis verwenden.

Wenn Sie zwischen den übergebenen Typen unterscheiden müssen, können Sie dennoch einen Wechsel von cls durchführen.

3
f1sh

Ich glaube, dass das, was Sie versuchen, mit etwas Generikamagie möglich ist. Ich musste jetzt dasselbe Problem lösen und das tat ich:

public class ListArrayUtils{
   @SuppressWarnings("unchecked") // It is checked. 
   public static <T,E> List<T> filterByType(List<E> aList, Class<T> aClass){
      List<T> ans = new ArrayList<>();
      for(E e: aList){
         if(aClass.isAssignableFrom(e.getClass())){
            ans.add((T)e);
         }
      }
      return ans;
   }       
}

Und Unit-Tests:

public class ListArrayUtilsTest{
   interface IfA{/*nothing*/}
   interface IfB{/*nothing*/}
   class A implements IfA{/*nothing*/}
   class B implements IfB{/*nothing*/}
   class C extends A implements IfB{/*nothing*/}

   @Test
   public void testFilterByType(){
      List<Object> data = new ArrayList<>();
      A a = new A();
      B b = new B();
      C c = new C();
      data.add(a);
      data.add(b);
      data.add(c);

      List<IfB> ans = ListArrayUtils.filterByType(data, IfB.class);

      assertEquals(2, ans.size());
      assertSame(b, ans.get(0));
      assertSame(c, ans.get(1));
   }
}
1
Emily L.

Ich habe das tatsächlich in einer generischen Datenzugriffsbibliothek gemacht. Siehe Norm . Vollständiger Quellcode auf Github.

0
ccleve