Java泛型和类型转换

19

我有一个糟糕的容器对象,它包含不同Java类型(String、Boolean等)的值。

public class BadlyCreatedClass {
    public Object get(String property) {
        ...;
    }
};

我们可以通过以下方式从中提取值:
String myStr = (String) badlyCreatedObj.get("abc");
Date myDate = (Date) badlyCreatedObj.get("def");

我被迫使用这个对象编写一些新代码,我正在尝试找到一种干净的方法来完成这个任务。更具体地说,下面的哪种方法是首选?

显式转换

String myStr = (String) badlyCreatedObj.get("abc")
Date myDate = (Date) badlyCreatedObj.get("def");

使用通用转换

public <X> X genericGet(String property) {

}

public String getString(String property) { 
return genericGet(property); 
}

public Date getDate(String property) { 
return genericGet(property); 
}

使用Class.cast方法

<T> T get(String property, Class<T> cls) {
    ;
}

我已经查看了SO上几个相关问题:Java泛型函数:如何返回泛型类型Java泛型返回类型,它们似乎都说这种类型转换是危险的。虽然我没有看到三者之间有太大的区别,但在此情况下,您更喜欢哪种方法呢?
谢谢。
6个回答

3

简单回答一下,不深入讨论良好的编程实践...

我会使用:

private <X> X genericGet(String property) {

}

public String getString(String property) { 
//... checks on property (String specific)...
Object obj = genericGet(property);
//... checks if obj is what is expected and if good return it
return obj; 
}

public Date getDate(String property) { 
//... checks on property (Date specific)...
Object obj = genericGet(property);
//... checks if obj is what is expected and if good return it
return obj
}

请注意私有的genericGet方法。

这样我就可以检查get属性是否是我期望收到的,并以正确的方式处理它。

我可以在getString中添加依赖于属性的检查,以确保答案将是一个String对象。

我可以对getDate中的属性进行其他检查,以确保返回的将是日期。

等等...


这与以下非泛型版本有何不同,使用泛型的好处是什么?`private Object genericGet(String property) {}public String getString(String property) { //... 对属性进行检查(特定于字符串)... Object obj = genericGet(property); //... 检查obj是否符合预期,如果符合则返回 return obj; }public Date getDate(String property) { //... 对属性进行检查(特定于日期)... Object obj = genericGet(property); //... 检查obj是否符合预期,如果符合则返回 return obj }` - AK S
请原谅我对注释格式的无知。我的问题是泛型返回类型的意义是什么。它也可以是一个简单的对象。 - AK S

3

个人认为,把许多不同的对象放在一个地方,然后检查想要返回的内容似乎有些不合适。也许你可以将Holder存储在那个BadlyCreatedClass中。

像这样:

class Holder {
    private long id;
    private String name;
    private Date dateofBirth;

    //getters and setters
}

然后根据id进行检索,无需转换类型。你也可以告诉我们你想做什么。

3
通用的转换方法会导致编译器发出未经检查的警告。未经检查的警告表示在运行时未对问题进行(完全)检查,即使值不属于适当的类型,它也可能成功。这可能会导致变量保存与其声明类型不兼容的值,这种情况被Java语言规范称为堆污染
以下程序演示了这一点:
class Holder {
    Object value;

    Holder(Object value) {
        this.value = value;
    }

    <T> T get() {
        return (T) value;
    }
}

class C<T> {
    T value;

    C(Holder h) {
        value = h.get();
    }
}

public class Test {
    public static void main(String [] args) throws IOException {
        Holder holder = new Holder("Hello");
        C<Integer> c = new C<Integer>(holder);
        System.out.println("I just put a String into a variable of type Integer");

        // much later, possibly in a different part of your program
        c.value.longValue(); // throws ClassCastException
    }
}

因此,我强烈建议使用已检查的转换。普通转换(您的第一种方法)和反射转换(您的第三种方法)都是经过检查的。但是,反射转换无法与参数化类型一起使用(List<String>.class无法编译...)。因此,最简单和最灵活的安全解决方案是普通转换。

1

我更倾向于使用通用转换。为什么?

  • 显式转换总是更难维护。当你阅读代码时,你根本不知道这个方法可能返回什么。而且,方法被错误使用并在运行时发生ClassCastException的概率相当高。

  • 类转换甚至更难维护。在我看来,你以这种方式创建了一些可以称为意大利面条代码的东西。

  • 当你创建像getStringgetDate这样的方法时,你为你的类提供了非常清晰的接口。更重要的是,由于你还提供了通用方法,因此始终可以获取其他类的对象,而不仅仅是StringDate


1
正如您已经提到的,以上所有方法都很危险,并且可能导致运行时的ClassCastException。
如果确实有必要,我更喜欢“通用转换”方法,因为它使接口明确且易于理解(在这种情况下将genericGet设为私有)。当然,您需要为容器中的每个类创建样板代码。因此,“Class.cast”的优点是您不需要这些样板方法。
结论:如果容器中有明确定义的类数量,则使用“通用转换”。如果您需要支持无限数量的类,则使用“Class.cast”。
更新:“显式转换”确实有一个优点-调用者(容器的用户)会得到一个提醒,即存在类转换风险!
只是一种看法...

1
由于所有选项都涉及类型转换,它们都有点“不安全”,并且可能会出现 ClassCastExceptions
我强烈建议使用像getString()getDate()这样的辅助方法来处理通常存储在此对象中的常见类型。这些方法对于所有三个选项都很有用,因为它们减少了对象用户需要编写的代码。
但是,您仍然需要一种从对象中接收“不常见”类型的方法。为此,我会选择显式转换或类转换。原因是我认为这两种方式最常用。即使通用方法调用可以正常工作,像 obj.genericGet<String>("myproperty"); 这样的方法调用并不是每个Java开发人员都知道的。它在实践中很少见。
个人而言,即使我不喜欢将类型转换移动到对象的用户那里,我也会向辅助方法添加一个 getObject() 方法。这样做的好处是您将拥有一致的接口,如果我看到像 getString()getDate() 这样的方法,我会期望如此。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接