Java泛型和类型转换

时间:2013-04-20 09:09:13

标签: java oop generics casting

我有一个创建严重的容器对象,它将不同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 generic function: how to return Generic typeJava generic return type上经历了几个相关的问题,他们似乎都说这种类型转换是危险的,虽然我看不出三者之间有太大的区别,考虑到这种方法会你更喜欢吗?

由于

6 个答案:

答案 0 :(得分: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进行其他检查,以确保它将是返回的日期。

等等...

答案 1 :(得分:3)

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

类似的东西:

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

    //getters and setters
}

然后根据id检索,不需要任何转换。

您也可以告诉我们您要做的事情。

答案 2 :(得分:3)

通用强制转换方法会导致编译器发出未经检查的警告。未经检查的警告表示在运行时没有(完全)检查强制转换,即使该值不是正确类型,它也可能成功。这可能导致变量保存与其声明类型不兼容的值,这是Java语言规范调用heap pollution的情况。

以下程序演示了这一点:

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

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

答案 3 :(得分:1)

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

  • 表演总是难以维持。当您阅读代码时,您根本不知道此方法可能会返回什么。更重要的是,在运行期间以错误的方式使用该方法并且某些ClassCastException会出现的概率非常高。

  • 类别演员更难维护。在我看来,你用这种方式创造了一些可能被称为意大利面条代码的东西。

  • 当您创建getStringgetDate等方法时,您可以为您的班级提供非常清晰的界面。除了StringDate之外,还可以获得其他类的对象,因为您还提供了通用方法。

答案 4 :(得分:1)

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

如果确实有必要,我更喜欢'泛型'方法,因为它使界面显式且自扩展(在这种情况下使genericGet成为私有)。当然,您必须为容器中的每个class创建boilderplate代码。所以'Class.cast'的优点是你不需要那些样板方法。

结论:如果容器中有明确定义的类数,我会使用'generic cast'。如果你需要支持无数的课程,我会选择'Class.cast'

更新:'Explicit Cast'确实有一个优点 - 调用者(容器的用户)会收到一个提醒,即存在分类播放风险!

只是意见......

答案 5 :(得分:1)

因为所有选项都涉及类型转换,所以它们都在某种程度上“不安全”,并且可能会因ClassCastExceptions而失败。

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

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

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