2013-04-20 184 views
19

我持有不同的Java类型的值一起制作低劣的容器对象(字符串,布尔值等)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 generic function: how to return Generic type走了,Java generic return type个个好像说的这种类型转换是危险的,但我没有看到三者之间相差太大,给你希望这个哪种方法?

感谢

回答

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

我宁愿使用通用转换。为什么?

  • 表达式转换总是比较难以维护。当你阅读代码时,你根本不知道这个方法可能会返回什么。更重要的是,该方法将在错误的方式runtimme过程中使用一些ClassCastException将ocurr的概率是相当高的。

  • 类投更是难以维持。在我看来,你用这种方式创建了一种可称为意大利面代码的东西。

  • 当您创建像getStringgetDate这样的方法时,您可以为班级提供非常清晰的界面。更重要的是,它总是可能得到其他类的对象比StringDate,因为你还提供了通用的方法。

1

正如你已经提到的所有上述两种方法是危险的,在运行时可能会导致ClassCastException秒。

如果确实有必要我宁愿在“一般投”方法,因为它的界面使得明确和自我expanatory(使genericGet私人在这种情况下)。当然,您必须为容器中的每个class创建一个boilderplate代码。所以'Class.cast'的优点是你不需要这些样板方法。

结论:如果容器中有明确定义数量的类,我会使用'generic cast'。如果您需要支持类数之不尽我会用“Class.cast”

更新去:“显式转换”确实有一个优势 - 来电(容器的用户)获取提醒那是一种类风险!

只是一个意见了......

3

就个人而言,保持许多不同的对象在一个地方,然后检查你想回到什么好像有点不对劲。也许你可以将持有者存储在该BadlyCreatedClass中。

喜欢的东西:

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

    //getters and setters 
} 

然后检索根据ID,因此不需要铸造。

您也可以告诉我们您正在尝试做什么。

1

由于所有选项都涉及类型转换,它们都以某种方式“不安全”,并可能因ClassCastExceptions而失败。

我肯定会推荐使用像getString(),getDate()这样的帮助器方法来处理通常存储在这个对象中的常见类型。这三种方法对所有三个选项都很有用,因为减少了对象用户必须编写的代码。

但是,您仍然需要一种方法来从对象中接收“不常见”类型。为此,我会选择明确演员或者班级演员。原因是我认为这两种方式是最常用的方式。即使泛型方法调用可以正常工作,我认为像obj.genericGet<String>("myproperty");这样的方法调用并不是每个Java开发人员都知道的。这在实践中很少见到。

即使我不喜欢将类型转换为对象的用户,个人情况下也会将getObject()方法添加到帮助器方法中。这样做的好处是您可以拥有一致的界面,并且如果我看到像getString()getDate()这样的方法,这就是我所期望的。

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

因此,最简单和最灵活的安全解决方案就是普通铸造。