19

我有一个创建错误的容器对象,它将不同 Java 类型的值(字符串、布尔值等 ..)保存在一起

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 泛型函数的相关问题:how to return Generic typeJava generic return type他们似乎都说这种类型转换是危险的,虽然我没有看到这三者之间有太大的区别,鉴于这种方法会你比较喜欢 ?

谢谢

4

6 回答 6

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不编译......)。

因此,最简单、最灵活的安全解决方案是普通演员。

于 2013-04-20T11:16:40.997 回答
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 进行其他检查,以确保它是返回的日期。

ETC...

于 2013-04-20T09:18:58.613 回答
3

就个人而言,将许多不同的对象放在一个地方,然后检查您想要返回的内容似乎有点错误。也许您可以将持有人存储在 BadlyCreatedClass 中。

就像是:

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

    //getters and setters
}

然后根据 id 检索,因此不需要强制转换。

你也可以告诉我们你想要做什么。

于 2013-04-20T09:25:08.450 回答
1

因为所有选项都涉及类型转换,所以它们都以某种方式“不安全”并且可能因ClassCastExceptions.

对于经常存储在此对象中的常见类型,我肯定会建议使用辅助方法,例如getString(), 。getDate()这些方法对所有三个选项都很有用,因为减少了对象用户必须编写的代码。

但是您仍然需要一种从对象接收“不常见”类型的方法。为此,我将使用显式强制转换或类强制转换。这样做的原因是我认为这两种方式是使用最多的方式。即使通用方法调用可以正常工作,我认为方法调用obj.genericGet<String>("myproperty");并不是每个 Java 开发人员都知道的。在实践中很少见到。

getObject()即使我不喜欢将类型转换移动到对象的用户,个人也会向辅助方法添加一个方法。getString()这样做的好处是您将拥有一致的界面,如果我看到类似or的方法,这就是我所期望的getDate()

于 2013-04-20T09:31:22.460 回答
1

我更喜欢使用通用演员表。为什么?

  • 显式演员表总是更难维护。当您阅读代码时,您根本不知道此方法可能返回什么。更重要的是,该方法在运行时被错误使用的概率ClassCastException很高,有些会在运行时发生。

  • 班级演员表更难以维护。在我看来,您以这种方式创建了一些可能被称为意大利面条代码的东西。

  • 当您创建类似的方法getStringgetDate为您的类提供非常清晰的接口时。更重要的是,总是可以获得其他类的对象,String因为Date您还提供了泛型方法。

于 2013-04-20T09:19:20.257 回答
1

正如您已经提到的,上述所有方法都是危险的,并且可能在运行时导致ClassCastExceptions 。

如果确实有必要,我更喜欢“通用转换”方法,因为它使接口变得明确且不言自明(genericGet在这种情况下是私有的)。当然,您必须为class容器中的每个创建样板代码。所以'Class.cast'的优点是你不需要那些样板方法。

结论:如果容器中有明确定义的类数量,我会选择“通用转换”。如果您需要支持无数的课程,我会选择“Class.cast”

更新:“显式转换”确实有一个优势——调用者(容器的用户)会收到一个提醒,提示存在类转换风险!

只是一个意见...

于 2013-04-20T09:24:34.570 回答