简体   繁体   English

将通用对象转换为通用类

[英]Cast generic object to generic class

I would like to pass in a generic object into my method and have it get the property name, type, and value. 我想将一个通用对象传递到我的方法中,并让它获取属性名称,类型和值。

Here is my class 这是我的课

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

I would like the output to look like this 我希望输出看起来像这样

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

The code I have right now gives me issues. 我现在拥有的代码给了我一些问题。 Here is the code: 这是代码:

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

Getting the subObject, in this case Class1 or Class2 , and passing it to the method is what Im having an issue with. Im遇到了问题,获取子对象(在本例中为Class1Class2 )并将其传递给方法。 I have tried with a class instead of an object but then I can't get the object from the class. 我尝试使用类而不是对象,但是后来我无法从类中获取对象。

Is there anyway to cast the object I pass in to the subclass? 无论如何,有没有将我传递给子类的对象强制转换?

Thanks 谢谢

You have a few options: 您有几种选择:


One option is to consider defining some interface that defines an object that provides user preferences, eg: 一种选择是考虑定义一些接口,该接口定义提供用户首选项的对象,例如:

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

Then you can make your classes implement that interface, eg: 然后,您可以使您的类实现该接口,例如:

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

Where their getUserPreferences() implementations return the preferences to write. 他们的getUserPreferences()实现在何处返回要写入的首选项。

Then you can change addUserPreferences() to take a UserPreferenceProvider , and when you are traversing fields, check if you find a UserPreferenceProvider and, if so, cast it to that and pass it off to addUserPreferences() . 然后,您可以将addUserPreferences()更改为UserPreferenceProvider ,并在遍历字段时检查是否找到UserPreferenceProvider ,如果是,则将其addUserPreferences()转换为该值并将其传递给addUserPreferences()

This would more accurately represent your intentions, as well. 这也将更准确地代表您的意图。 I believe the fundamental issue here is you have these arbitrary objects that you're trying to work with, and while conceptually they have something in common, your code is not representing that concept; 我相信这里的根本问题是,您要尝试使用这些任意对象,虽然从概念上讲它们具有某些共同点,但是您的代码并不代表该概念。 I know that's a bit vague but by not having your code reflect that, you are now faced with the awkward task of having to find a way to force your arbitrary objects to be treated in a common way. 我知道这有点含糊,但是由于没有让您的代码反映出来,您现在面临着笨拙的任务,即必须找到一种方法来强制以通用方式对待任意对象。


A second option could be to create a custom annotation, eg @UserPreference , and use that to mark the fields you want to write. 第二种选择是创建一个自定义批注,例如@UserPreference ,并使用它来标记您要编写的字段。 Then you can traverse the fields and when you find a field with this annotation, add it's single key/value to the user preferences (that is, operate on the fields themselves, instead of passing entire container classes to addUserPreferences() ). 然后,您可以遍历字段,并在带有此批注的字段中找到时,将其单个键/值添加到用户首选项(即,对字段本身进行操作,而不是将整个容器类传递给addUserPreferences() )。

This may or may not be more appropriate than the first option for your design. 这可能比您设计的第一个选项更为合适。 It has the advantage of not forcing you to use those interfaces, and not having to write code to pack data into maps or whatever for getUserPreferences() ; 它的优点是不必强迫您使用这些接口,也不必编写代码即可将数据打包到映射或getUserPreferences() it also gives you finer grained control over which properties get exported -- essentially this shifts your focus from the objects to the individual properties themselves. 它还可以让您更精细地控制导出哪些属性的方法-本质上,这会将您的关注点从对象转移到各个属性本身。 It would be a very clean approach with minimal code. 这将是使用最少的代码的一种非常干净的方法。

A possible way to make this more convenient if you already have bean-style getters is to use eg Apache BeanUtils to get the values instead of rolling your own; 如果您已经具有bean风格的getter,则使此操作更方便的一种可能方法是使用例如Apache BeanUtils获取值,而不是滚动自己的值。 but for your situation it's a pretty basic use of reflection that may not be worth an additional dependency. 但是对于您的情况,这是反射的相当基本的用途,可能不值得附加的依赖。


Here is an example of getting names and values of the fields of an object tagged with a custom annotation. 这是获取使用自定义注释标记的对象的字段名称和值的示例。 A second annotation is used to mark fields that contain objects that should be recursively descended into and scanned. 第二个注释用于标记包含应递归地放入并扫描的对象的字段。 It's very straightforward: 非常简单:

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

}

The output is: 输出为:

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

Note that "int1" is not present in the output, as it is not tagged. 请注意,输出中不存在“ int1”,因为它没有被标记。 You can run the example on ideone . 您可以在ideone上运行该示例

The original basic annotation example can still be found here . 原始的基本注释示例仍可以在此处找到。

You can do all sorts of fun things with annotations, by the way, eg add optional parameters that let you override the field name in the preferences, add a parameter that lets you specify a custom object -> user preference string converter, etc. 顺便说一下,您可以使用批注来做各种有趣的事情,例如,添加可让您覆盖首选项中的字段名称的可选参数,添加可让您指定自定义对象的参数->用户首选项字符串转换器,等等。

I have figured out a simplistic way to do this. 我已经想出一种简单的方法来做到这一点。 Anyone who has suggestions to make this better or has issues with the code please comment. 如果有人对您的建议有所改进,或者对代码有疑问,请发表评论。 The code below does work for me 下面的代码对我有用

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

The isWrapperType come from code I found in this stack overflow question. isWrapperType来自我在堆栈溢出问题中找到的代码。 All i did was add String and int to the set. 我所做的就是将Stringint添加到集合中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM