web-dev-qa-db-fra.com

Comment passer d'une classe en paramètre et renvoyer une collection générique en Java?

Je conçois un objet d'accès aux données simple pour mon application Java. J'ai quelques classes (enregistrements) qui représentent une seule ligne dans des tables comme User et Fruit.

J'aimerais avoir une seule méthode pour obtenir tous les enregistrements d'un type spécifique.

Pour le moment je l'ai comme ça:

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

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

....

Mais j'aimerais avoir une seule méthode polymorphe comme celle-ci (fausse):

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;
}

Exemple d'utilisation:

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

Comment puis-je faire cela en Java?

34
Jonas

Puisque vous dites que vous ne voulez pas que vous utilisiez des méthodes d'accès aux données dans différentes classes (dans le commentaire à la réponse de anish), j'ai pensé pourquoi ne pas essayer quelque chose comme ça.

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();

    }
}

MODIFIER:

Je voudrais ajouter un de plus de mon implémentation.

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();}
}

MODIFIER:

Je pense que cette mise en œuvre est juste comme vous avez demandé

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

Il semble que vous souhaitiez adapter ce que Josh Bloch appelle un modèle conteneur hétérogène de Typesafe}: vous transmettez un jeton de type Class<T> et vous souhaitez récupérer un List<T>.

Le vieux THC ordinaire peut mapper un Class<T> sur un T de manière dactylographiée, mais comme vous voulez réellement un List<T>, vous voulez utiliser ce que Neal Gafter appelle les jetons de type super).

L'extrait suivant est adapté du code de Crazy Bob Lee publié sur le blog de Neal Gafter:

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;
    }
}

Vous pouvez maintenant créer un super type token comme celui-ci:

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

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

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

En gros, vous passez un TypeReference<T> au lieu d'un Class<T>. La différence est qu'il n'y a pas de List<String>.class, mais vous pouvez créer un TypeReference<List<String>>.

Nous pouvons donc maintenant fabriquer notre conteneur comme suit (le texte suivant est adapté du code original de Josh Bloch):

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;
    }
}

Maintenant, nous pouvons mettre les deux ensemble:

    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 a expliqué dans son blog que, avec encore plus de cloches et de sifflets, TypeReference pour les jetons de type super constituerait une inclusion digne du JDK.

Les pièces jointes

Références

24
polygenelubricants

Tu es assez proche.

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

Ceci s'appelle une Méthode générique .

Vous voudrez spécifier un paramètre comme List<T>. Ensuite, en fonction du type de liste que vous transmettez, Java déduira le type générique à renvoyer.

Modifier:

La réponse de Poly est très bonne. Il devrait être assez facile pour vous de faire ce qui suit sans avoir à créer une classe TypeReference.

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

Eh bien, je ne sais vraiment pas si vous en avez besoin de cette façon …… Mais voici une approche polymorphe Cela pourrait aider quelque part.

Créez différents objets pour différentes tables, tous implémentant une interface commune. Cela signifie que vous représentez chaque table en tant qu'objet.

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

En fonction de la manière dont vous récupérez vos données, vous pouvez procéder comme suit:

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;
}

Cela nécessite que votre objet sql renvoie le type de liste correct, ce que font les mappeurs O/R comme iBatis.

Si vous devez différencier les types passés, vous pouvez toujours effectuer un changement/une casse sur cls.

3
f1sh

Je crois que ce que vous essayez de faire est possible avec un peu de magie des génériques. Je devais résoudre le même problème tout à l'heure et voici ce que j'ai fait:

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;
   }       
}

Et des tests unitaires:

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.

J'ai effectivement fait cela dans une bibliothèque d'accès aux données génériques. Voir Norm . Code source complet sur Github.

0
ccleve