How can I pass a Class as parameter and return a generic collection in Java?

后端 未结 7 1104
悲哀的现实
悲哀的现实 2020-12-24 05:18

I am designing a simple Data Access Object for my Java application. I have a few classes (records) that represents a single row in tables like User and Fr

相关标签:
7条回答
  • 2020-12-24 05:47

    Well, I really don't know if you need it this way. But here is a polymorphic approach. It might help somewhere somehow.

    Create different objects for different tables all implementing a common interface. This means you represent each table as an object.

    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;
        }
    }
    
    0 讨论(0)
  • 2020-12-24 05:54

    Since you say that you don't want you data access methods in different classes(in the comment to anish's answer),I thought why not try something like this.

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

    EDIT:

    I would like to add one more of my implementation.

    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:

    I think this implementation is just as you have asked

    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;
        }
    }
    
    0 讨论(0)
  • 2020-12-24 06:02

    It looks like you want to adapt what Josh Bloch calls a Typesafe Heterogenous Container pattern: you are passing a type token Class<T>, and you want back a List<T>.

    Plain old THC can map a Class<T> to a T in a typesafe manner, but since you actually want a List<T> instead, then you want to use what Neal Gafter calls the super type tokens.

    The following snippet is adapted from Crazy Bob Lee's code posted in Neal Gafter's blog:

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

    Now you can create a super type token like these:

        TypeReference<String> stringTypeRef =
                new TypeReference<String>(){};
    
        TypeReference<Integer> integerTypeRef =
                new TypeReference<Integer>(){};
    
        TypeReference<List<Boolean>> listBoolTypeRef =
                new TypeReference<List<Boolean>>(){};
    

    Essentially you pass a TypeReference<T> instead of a Class<T>. The difference is that there is no List<String>.class, but you can make a TypeReference<List<String>>.

    So now we can make our container as follows (the following is adapted from Josh Bloch's original code):

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

    Now we can put the two together:

        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 argued in his blog that with some more bells and whistles, TypeReference for super type tokens will make a worthy inclusion in the JDK.

    Attachments

    • Complete source code on ideone.com

    References

    • Neal Gafter's Blog - Super Type Tokens
    0 讨论(0)
  • 2020-12-24 06:02

    I've actually done this in a generic data access library. See Norm. Full source code on Github.

    0 讨论(0)
  • 2020-12-24 06:05

    I believe what you are trying to do is possible with a bit of generics magic. I had to solve the same problem just now and this is what I did:

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

    And 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));
       }
    }
    
    0 讨论(0)
  • 2020-12-24 06:08

    Depending on how you actually retrieve your data, you can do something like this:

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

    This requires your sql object to return the correct type of list, which O/R mappers like iBatis do.

    If you need to differentiate between the passed types, you can still do a switch/case on cls.

    0 讨论(0)
提交回复
热议问题