How can I pass a Class as parameter and return a g

2020-02-17 08:57发布

问题:

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 Fruit.

I would like to have a single method for getting all records of a specific type.

For the moment I have it like this:

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

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

....

But I would like to have a single polymorphic method like this (wrong):

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

Example for uses:

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

How can I do this in Java?

回答1:

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


回答2:

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


回答3:

You are pretty close.

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

This is called a Generic Method.

You will want to specify a parameter like List<T>. Then, based upon the type of the list you pass in, Java will infer the generic type to return.

Edit:

Poly's answer is very good. It should be easy enough for you to do the following and not have to create a TypeReference class.

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


回答4:

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.



回答5:

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


回答6:

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


回答7:

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