如何将 Class 作为参数传递并在 Java 中返回泛型集合?

Posted

技术标签:

【中文标题】如何将 Class 作为参数传递并在 Java 中返回泛型集合?【英文标题】:How can I pass a Class as parameter and return a generic collection in Java? 【发布时间】:2011-03-24 18:05:24 【问题描述】:

我正在为我的 Java 应用程序设计一个简单的数据访问对象。我有几个类(记录)代表UserFruit 等表中的一行。

我想要一个方法来获取特定类型的所有记录。

暂时我是这样的:

public List<User> getAllUsers() 
 ...


public List<Fruit> getAllFruits() 
 ...


....

但我想要一个像这样的单一多态方法(错误):

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;

使用示例:

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

如何在 Java 中做到这一点?

【问题讨论】:

Effective Java 2nd Edition,Item 52:通过接口引用对象;你应该更喜欢List&lt;Fruit&gt; 而不是LinkedList&lt;Fruit&gt; @polygene:谢谢,我现在更新了我的代码并阅读了第 52 条 :) 【参考方案1】:

您似乎想要适应 Josh Bloch 所说的 Typesafe Heterogenous Container 模式:您正在传递一个类型标记 Class&lt;T&gt;,并且您想要返回一个 List&lt;T&gt;

普通的旧 THC 可以以类型安全的方式将 Class&lt;T&gt; 映射到 T,但由于您实际上想要一个 List&lt;T&gt;,所以您想使用 Neal Gafter 所称的 超级类型令牌。

以下 sn-p 改编自 Neal Gafter 博客中发布的 Crazy Bob Lee 的代码:

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;
    

现在你可以像这样创建一个超类型令牌

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

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

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

本质上,您传递的是 TypeReference&lt;T&gt; 而不是 Class&lt;T&gt;。不同的是没有List&lt;String&gt;.class,但是你可以发一个TypeReference&lt;List&lt;String&gt;&gt;

所以现在我们可以将我们的容器制作成如下(以下改编自 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;
    

现在我们可以将两者放在一起:

    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 在他的博客中指出,如果有更多的花里胡哨,TypeReference 用于超级类型令牌将值得包含在 JDK 中。

附件

Complete source code on ideone.com

参考文献

Neal Gafter's Blog - Super Type Tokens

【讨论】:

另请参阅:gafter.blogspot.com/2007/05/… - 如果您将 TypeReference 与类型参数一起使用,则未经检查的强制转换实际上是不安全的。 感谢您的良好回答和良好参考。但解决方案很复杂。似乎是 Java 语言的一个非常薄弱的​​部分。我想我跳过实现这个,它很复杂。 Java 教程上也有解决方案,但使用反射也很复杂:download-llnw.oracle.com/javase/tutorial/extra/generics/… 太棒了。有些语言不需要所有这些机制,但在 Java 中使其成为可能是令人钦佩的。【参考方案2】:

既然你说你不希望你在不同类中的数据访问方法(在对 anish 答案的评论中),我想为什么不尝试这样的事情。

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

    

编辑:

我想再添加一个我的实现。

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

编辑

我认为这个实现就像你问的那样

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;
    

【讨论】:

是的,这是另一种选择。但是,与我已有的代码相比,这段代码有什么优势?请参阅我的问题中的第一个代码清单。 不会有太多优势。我能想到的唯一优势是您可以将所有记录获取代码分离到一个类中并使用相同的函数调用而不是不同对象的不同函数名。 查看此链接:download-llnw.oracle.com/javase/tutorial/extra/generics/… 太棒了!我喜欢你的第二个解决方案,第三个也很好!我已经阅读了该链接,实际上我几天前将其作为对 polygenelubricants 答案的评论发布了,但我没有意识到这个技巧。谢谢!【参考方案3】:

你已经很接近了。

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

这称为Generic Method。

您需要指定一个参数,如List&lt;T&gt;。然后,根据您传入的列表的类型,Java 将推断要返回的泛型类型。

编辑:

Poly 的回答非常好。您应该很容易执行以下操作,而不必创建 TypeReference 类。

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

【讨论】:

如果您将参数的类型更改为 T 而不是 List&lt;T&gt;... @poly,我不完全理解你从 typereference 类中获得了什么,以及为什么我的解决方案不起作用。 @poly,哈哈。我只是要继续前进......我有工作要做。 :( @Justin:我找出了我的困惑来源:我回答了问题的主题而不是它的正文。也就是说,我们如何传递Class——一个类型标记——并获得该类型的通用集合?答案当然是因为Class 不能捕获泛型类型(因为它是不可具体化的),所以我们必须使用像超类型标记这样的东西。但是,深入到问题本身,我现在认为 HTC+STT 可能是矫枉过正。 @Jonas,看看这个问题:***.com/questions/1942644/…【参考方案4】:

根据您实际检索数据的方式,您可以执行以下操作:

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;

这需要您的 sql 对象返回正确类型的列表,像 iBatis 这样的 O/R 映射器会这样做。

如果您需要区分传递的类型,您仍然可以在 cls 上进行 switch/case。

【讨论】:

【参考方案5】:

嗯,我真的不知道你是否需要这种方式。 但这是一种多态方法。它可能会在某个地方有所帮助。

为不同的表创建不同的对象,它们都实现了一个通用的接口。这意味着您将每个表表示为一个对象。

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;
    

【讨论】:

+1:这使您可以指定如何按数据类型准确加载数据。让我想起了策略模式。 谢谢,但有了这个解决方案,我最终会在许多类中拥有数据访问对象。不是我想要的。 @Jonas:只是出于好奇,不同的表会有不同的列,对吧?如果有任何更改或添加列,则仅在上述代码中修改特定对象。尽管每个表都有一个类,但它是松散耦合的。但是表的属性再次在类中表示。【参考方案6】:

我相信你想要做的事情可以通过一些泛型魔法来实现。我刚才必须解决同样的问题,这就是我所做的:

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;
          

还有单元测试:

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】:

我实际上已经在通用数据访问库中完成了这项工作。见Norm。 Github 上的完整源代码。

【讨论】:

以上是关于如何将 Class 作为参数传递并在 Java 中返回泛型集合?的主要内容,如果未能解决你的问题,请参考以下文章

如何在Java中将类作为参数传递?

在java中将接口类型(不实现类类型)作为参数传递

将 R 函数作为 Java 方法参数传递

Java参数传递

Laravel - 如何将参数从控制器传递到路由并在另一个控制器中使用它?

java 中 如何将“一个类的方法 ”作为参数传到“另一个类的方法”中