【问题标题】:Cast generic object to generic class将泛型对象转换为泛型类
【发布时间】:2014-04-14 18:14:39
【问题描述】:

我想将一个通用对象传入我的方法,并让它获取属性名称、类型和值。

这是我的课

public class Login {

    public String token;   
    public String customerid;
    public Class1 class1;
    public Class2 class2;

    public class Class1 {
        public Class3 class3;
        public String string1;

        public class Class3 {
                public int int1;
                public String string2;
                public String string3;
        }
    }

    public class Class2 {
        public int int1;
        public String string2;
        public String string3;
    }
}

我希望输出看起来像这样

User Preferences customerid - class java.lang.String - 586969
User Preferences token - class java.lang.String - token1
User Preferences string1 - class java.lang.String - string1Value
User Preferences string2 - class java.lang.String - string2Value
User Preferences string3 - class java.lang.String - string3Value

我现在拥有的代码给我带来了问题。代码如下:

    try {
        // Loop over all the fields and add the info for each field
        for (Field field : obj.getClass().getDeclaredFields()) {
            if(!field.isSynthetic()){
                field.setAccessible(true);
                System.out.println("User Preferences " + field.getName() + " - " + field.getType() + " - " + field.get(obj));
            }
        }

        // For any internal classes, recursively call this method and add the results
        // (which will in turn do this for all of that subclass's subclasses)
        for (Class<?> subClass : obj.getClass().getDeclaredClasses()) {
            Object subObject = subClass.cast(obj); // ISSUE
            addUserPreferences(subObject, prefs);
        }
    }catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }catch(ClassCastException e) {
        e.printStackTrace();
    }

获取子对象,在本例中为 Class1Class2,并将其传递给方法是我遇到的问题。我尝试过使用类而不是对象,但我无法从类中获取对象。

有没有将我传入的对象强制转换为子类?

谢谢

【问题讨论】:

    标签: java android reflection generic-programming


    【解决方案1】:

    你有几个选择:


    一种选择是考虑定义一些接口来定义提供用户偏好的对象,例如:

    interface UserPreferenceProvider {
        Map<String,Object> getUserPrefences();
    }
    

    然后你可以让你的类实现那个接口,例如:

    public class Login implements UserPreferenceProvider {
        ...
        public class Class1 implements UserPreferenceProvider {
            ...
            public class Class2 implements UserPreferenceProvider {
                ...
            }
        }
    }
    

    他们的getUserPreferences() 实现返回要写入的首选项。

    然后您可以将addUserPreferences() 更改为UserPreferenceProvider,并在遍历字段时检查是否找到UserPreferenceProvider,如果找到,请将其转换为addUserPreferences()

    这也将更准确地代表您的意图。我相信这里的根本问题是你有这些你正在尝试使用的任意对象,虽然在概念上它们有一些共同点,但你的代码并不代表那个概念;我知道这有点含糊,但由于您的代码没有反映这一点,您现在面临着必须找到一种方法来强制以通用方式处理您的任意对象的尴尬任务。


    第二个选项可能是创建自定义注释,例如@UserPreference,并使用它来标记您要编写的字段。然后您可以遍历字段,当您找到带有此注释的字段时,将其单个键/值添加到用户首选项中(即对字段本身进行操作,而不是将整个容器类传递给addUserPreferences())。

    这可能比第一个选项更适合您的设计。它的优点是不强迫您使用这些接口,并且不必编写代码将数据打包到地图或getUserPreferences() 的任何东西中;它还可以让您更精细地控制导出哪些属性——本质上这会将您的注意力从对象转移到单个属性本身。这将是一种非常简洁且代码最少的方法。

    如果您已经拥有 bean 样式的 getter,那么一种可能的方法是使用例如Apache BeanUtils 获取值而不是自己滚动;但对于您的情况,反射是一种非常基本的使用,可能不值得额外依赖。


    这是一个获取带有自定义注释标记的对象的字段的名称和值的示例。第二个注释用于标记包含应该递归下降和扫描的对象的字段。这很简单:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.lang.reflect.Field;
    
    // @UserPreference marks a field that should be exported.
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    @interface UserPreference {
    }
    
    // @HasUserPreferences marks a field that should be recursively scanned.
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    @interface HasUserPreferences {
    }
    
    
    // Your example Login class, with added annotations.
    class Login {
    
        @UserPreference public String token;      // <= a preference
        @UserPreference public String customerid; // <= a preference
        @HasUserPreferences public Class1 class1; // <= contains preferences
    
        public class Class1 {
            @HasUserPreferences public Class2 class2; // <= contains preferences
            @UserPreference public String string1;    // <= a preference
    
            public class Class2 {
                    public int int1; // <= not a preference
                    @UserPreference public String string2; // <= a preference
                    @UserPreference public String string3; // <= a preference
            }
        }
    
        // Construct example:
        public Login () {
            token = "token1";
            customerid = "586969";
            class1 = new Class1();
            class1.string1 = "string1Value";
            class1.class2 = class1.new Class2();
            class1.class2.string2 = "string2Value";
            class1.class2.string3 = "string3Value";
        }
    
    }
    
    
    public class ValueScanExample {
    
        // Recursively print user preferences. 
        // Fields tagged with @UserPreference are printed.    
        // Fields tagged with @HasUserPreferences are recursively scanned.
        static void printUserPreferences (Object obj) throws Exception {
            for (Field field : obj.getClass().getDeclaredFields()) { 
                // Is it a @UserPreference?
                if (field.getAnnotation(UserPreference.class) != null) {
                    String name = field.getName();
                    Class<?> type = field.getType();
                    Object value = field.get(obj);
                    System.out.println(name + " - " + type + " - " + value);
                }
                // Is it tagged with @HasUserPreferences?
                if (field.getAnnotation(HasUserPreferences.class) != null) {
                    printUserPreferences(field.get(obj)); // <= note: no casts
                }
            }
        }
    
        public static void main (String[] args) throws Exception {
            printUserPreferences(new Login());
        }
    
    }
    

    输出是:

    token - class java.lang.String - token1
    customerid - class java.lang.String - 586969
    string2 - class java.lang.String - string2Value
    string3 - class java.lang.String - string3Value
    string1 - class java.lang.String - string1Value
    

    请注意,输出中不存在“int1”,因为它没有被标记。你可以run the example on ideone

    here 仍然可以找到原始的基本注解示例。

    顺便说一句,您可以使用注释做各种有趣的事情,例如添加可让您覆盖首选项中的字段名称的可选参数,添加可让您指定自定义对象的参数 -> 用户首选项字符串转换器等。

    【讨论】:

    • 对于选项 2,我如何从注释 @UserPreference 中调用 addUserPreferences()?我对注释不熟悉,所以我有点迷茫。
    • @BigT 它的工作方式是您将使用反射扫描对象的所有字段。您将检查每个字段以查看它是否具有该注释,例如通过使用Field.getAnnotation(UserPreference.class)。如果是这样,那么您现在拥有该字段的 String 名称,该字段的 Object 值,您可以将其写为个人偏好。当然,您仍然会通过内部类递归并继续像现在一样查找这些字段。这更有意义吗?
    • @BigT 查看我添加的注释示例。
    • 虽然这是一个很好的例子,但我不会遇到同样的问题吗?就像我在问题中详述的那样,我的班级里面有课程。所以如果Example 里面有类。如果我要使用这个例子,当我进入Class1Class2 时,我将如何获取值的对象。这就是我的问题。希望我之前清楚
    • @BigT 不,你不会有这个问题。反射在Object 上运行,不管它是什么类型。您最初的问题之一源于您试图将obj 转换为子类,但a)它不是(例如,如果objLogin,您不能将其转换为 Class1) 和 b) 即使是,您也在尝试分析这些内部类字段的 。您实际上根本不需要获取声明的内部类列表(我想您很困惑,getDeclaredClasses() 返回内部类types,它与字段值无关)。
    【解决方案2】:

    我想出了一个简单的方法来做到这一点。任何有建议以使其更好或对代码有问题的人请发表评论。下面的代码对我有用

        try {
            Class<?> objClass = obj.getClass();
            List<Object> subObjectList = new ArrayList<Object>();
            // Loop over all the fields and add the info for each field
            for (Field field: objClass.getDeclaredFields()) {
                if(!field.isSynthetic()){
                    if(isWrapperType(field.getType())){
                        System.out.println("Name: " + field.getName() + " Value: " + field.get(obj));
                    }
                    else{
                        if(field.getType().isArray()){
                            Object[] fieldArray = (Object[]) field.get(obj);
                            for(int i = 0; i < fieldArray.length; i++){
                                subObjectList.add(fieldArray[i]);
                            }
                        }
                        else{
                            subObjectList.add(field.get(obj));
                        }
                    }
                }
            }
    
            for(Object subObj: subObjectList){
                printObjectFields(subObj);
            }
        }catch(IllegalArgumentException e){
            // TODO Auto-generated catch block
            e.getLocalizedMessage();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.getLocalizedMessage();
        }
    

    isWrapperType 来自我在this 堆栈溢出问题中找到的代码。我所做的只是将Stringint 添加到集合中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多