“双重铸造”

时间:2013-01-21 02:51:19

标签: java casting

我目前正在为AP Java中的一名高中生辅导,她向我问了一个关于" double casting"的问题。我以前从未听过这个词,但显然她的老师希望她知道即将到来的决赛。

她的老师提供的示例是,如果您想将Integer转换为String,则必须执行以下操作以避免编译错误:

Integer i = new Integer(5);
String s = (String)(Object) i;

问题是:你想在现实生活中什么时候做这件事?

教师仅提供导致运行时错误的示例。此外,我从来没有真正知道有这个术语,但这似乎是一个坏主意,因为当两种类型不兼容时,只有一个错误。

谢谢!

10 个答案:

答案 0 :(得分:7)

是的,我很确定这不是一件事。没有必要进行双重铸造 - 它可能会摆脱关于不安全铸造的编译警告(在这种情况下你可能做错了),但除此之外这是不对的。

我的意思是,有自动toString来电,例如println("" + i),但即使这样,你也不需要先投射到一个物体......

编辑:在阅读了Tom的回答后,我突然不确定这个答案 - 原语和(特别是)泛型实际上可以使用它。我现在没有能力测试任何东西,但是任何读过这个答案的人都应该肯定看一下他的(并且可能是赞成它)。

我会坚持认为没有(或者至少极少数和很少)良好原因的行,但是,提供的示例当然无关用它。

答案 1 :(得分:7)

虽然“双重演员”当然不是一个常用的术语,你不应该看到任何类型的参考文献,你应该知道会发生什么(ClassCastException)。

为了完整起见,有些情况下不会有CCE:

  • 如果该值实际为null
  • 涉及原语的东西。 (呃,ObjectInteger到[拆箱] int,或int到[有损] byte到[正] char)< / LI>
  • 更改泛型类型参数。 List<String>ObjectList<Integer>

答案 2 :(得分:1)

在大多数情况下,双重投射确实会导致ClassCastException,但有一些特殊情况。正如其他答案中所提到的,它可以用于处理原语和泛型,但是当编译的类不必代表运行时类时也可以使用它。当涉及ASM字节码类变换器时尤其如此。

举个例子,我将使用Mixin框架。以下代码将在运行时注入Foo类。我不会深入了解这是如何工作的具体细节。

@Mixin(Foo.class)
public class MixinFoo {

    public Foo bar() {
        return (Foo) (Object) this;
    }
}

this的编译时类型似乎是MixinFoo,但实际上这个方法是动态插入的,因此在运行时,它将是Foo。编译器不知道这一点;我们必须将其强制转换为Foo,但这也会导致编译错误。

首先转换为Object然后转换为Foo(双重转换)可以解决编译器的问题。请记住,如果该方法未在运行时应用,则会导致运行时错误。

当然,这远远超出了Java介绍的技能水平。

答案 3 :(得分:0)

当然,虽然这只是避免编译器错误,但必然会发生运行时错误。此外,如果两个类在继承层次结构中,则无需向下转换为基类并再次向上转发。

即使在上述问题中,最终也需要一种API方法来转换对象。

答案 4 :(得分:0)

我最近遇到过这个问题并且使用它。我有一个包含2个整数值和一个字符串的对象数组。当我来解压缩它时,我需要将整数转换为double以进行一些除法。

问题在于,当我来转换整数值以使其错误加倍时,因为Java会自动将其检测为Integer对象而不是int原语,并且您无法从Integer转换为double

因此,解决方案是在进行划分之前使用(double)(int)value

总而言之,如果您将int存储为对象并且想要将其转换为某个除法的double,则java会自动将其转换为Integer,从而无法进行除法。

答案 5 :(得分:0)

一个真实的用例是:

static class Y extends X<Date> {
//some code
}

List<Y> n = new ArrayList<>();

someMethod((List<X<Date>>)(List<?>) n);

,其中

void someMethod(List<X<Date>>){
//some code
}

答案 6 :(得分:0)

这是我遇到的一个简单的真实示例,您需要帮助编译器理解它在强制转换之前应自动装箱:

class NumberConverter<F extends Number, T> {

    public T convertNumber(F value) {
        if (targetType.equals(Integer.class) || targetType.equals(int.class)) {
            return (T) (Object) value.intValue();
        }

        (..)
    }
}

您不能直接转换为T,但是可以转换为Object,这会使JVM在将其转换为Object之前对其进行自动装箱。然后您可以毫无问题地转换为T。

答案 7 :(得分:0)

是的,当您需要处理擦除的泛型和类型标记的交集时。

假设您想要一个在编译时和运行时都是类型安全的泛型函数。所以你用一个类型标记声明它:

public static <T> T getT(
    ExampleDataSource exampleDataSource,
    String key,
    Class<T> typeToken) {

    // code goes here...
}

只要您仅尝试从数据源中检索非泛型类型,此方法就非常有效。例如:

String string = getT(source, "some-string", String.class);
int integer = getT(source, "some-integer", Integer.class);
DateTime dateTime = getT(source, "some-datetime", DateTime.class);

但是如果您想从数据源中获取 List<String> 会怎样?好吧,它的语法是一个非常丑陋的双重转换。它还要求数据源能够自己找出被擦除的类型,因为它在运行时并不存在于类型标记中,尽管进行了强制转换:

List<String> listOfString = getT(
    source,
    "some-list-of-strings",
    (Class<List<String>>) (Class) List.class
);

我不知道为什么 Java 不允许一步将 List.class 直接转换为(虚构的)类型 Class<List<String>>。我希望 Java 在未来的某个时候能够具体化泛型类型。那么 List<String>.class 将是一个有效的类型标记,并且您不需要任何类型转换,更不用说奇怪的双重原始到假泛型转换了。

答案 8 :(得分:-1)

也许这个问题是逻辑?我的意思是,有一些方法可以计算某些东西(使用整数)并返回Object,并使用方法将结果转换为String,通常这个metod可以被覆盖几次,这样的功能在Java 1.5之前使用你定义了Generic calss(但没有Generics)并且每个方法的返回都是Object因为有几个孩子并且每个都可以返回自己的类型,我不知道但是可以回答,并且像这样双重投射

public class Main {
    public static void main(String[] args) {

        AbstractToDO abstractToDO2 = new SomeToDoTwo();
        String result2 = (String) abstractToDO2.toDo(); // here is second, all is good

        System.out.println(result2);

        AbstractToDO abstractToDO1 = new SomeToDoOne();
        String result1 = (String) abstractToDO1.toDo(); // here is second, Runtime error

        System.out.println(result1);

    }

    Object onePlusOne(){

        return 1+1 +" ";
    }
}

interface AbstractToDO{
     Object toDo();
}

class SomeToDoOne implements AbstractToDO{
    @Override
    public Object toDo() {
        return (Object)(1+1); // here is first casting, // we can use without (Object) casting
    }
}class SomeToDoTwo implements AbstractToDO{
    @Override
    public Object toDo() {
        return (Object)"1+1"; // here is first casting, // we can use without (Object) casting
    }
}

答案 9 :(得分:-1)

虽然双重演员在程序员的前瞻性中可能看起来更加重要,但至少知道语法的工作方式可能有助于您理解Java的基本工作原理。

例如:

假设AnimalDog类的超类。我们知道Object类是所有类的超类。

Dog d = new Dog(); 
Object a = d;          //no errors occur

此处,d被隐式地转换为Object对象。明确地,它将是Object a = (Object)d;

这里怎么样?

Dog d = new Dog(); 
Object a = (Animal)d;  //no errors occur

我们将d转换为Animal对象,但编译器隐式将其作为Object对象进行转换。双重铸造。

哪一个d最终被投放为?

明确地说,它是Object a = (Object)(Animal)d;

了解双重拼接的语法,我们知道d最终会被归为Object,因此不会发生错误。