【问题标题】:"Double casting"“双重铸造”
【发布时间】:2021-01-30 00:54:54
【问题描述】:

我目前正在辅导一名 AP Java 高中生,她问我一个关于“双重铸造”的问题。我以前从未听说过这个学期,但显然她的老师希望她在即将到来的期末考试中知道它。

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

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

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

老师只提供了导致运行时错误的示例。另外,我从来不知道有这个术语,但这样做似乎是个坏主意,因为当这两种类型不兼容时只会出现错误。

谢谢!

【问题讨论】:

  • 该代码肯定会导致ClassCastException,而Integer 可以“向上转换”为Object,对象仍然知道它是Integer...第二次转换将失败。她的老师抽什么烟? :p
  • @fge 是的.. 当她今天问我这个问题时,我想不出任何有意义的例子,所以我告诉她我会回复她.. 很高兴知道这只是一个坏主意,我没疯。 =]
  • 没有理由编写这样的代码。如果您知道投射后所需的最终对象类型,那就是您要将对象投射到的对象。你确定老师的意思不是像 (String)((Object)i).doSomething()) 吗?
  • @aishwarya 你的例子会更有意义,但我提供的例子直接来自我的学生从课堂上得到的笔记。
  • 实际上,在处理原始类型时,双重转换很有用。假设Object get() 返回一个整数/整数。现在考虑int i = (int) (Integer) get()。当然这个可以用int i = ((Integer)get()).intValue()来规避,稍微麻烦一点。

标签: java casting


【解决方案1】:

虽然“双重转换”当然不是一个常用术语,而且您似乎也不应该过多地转换引用,但您应该知道会发生什么(ClassCastException)。

为了完整起见,在某些情况下它不会 CCE:

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

【讨论】:

  • Ooooooh,关于 int-byte-char 的好点 - 这实际上是一个示例,它会做一些(可能)有用的事情,即使它可以通过其他方式完成。此外,使用泛型类型选项,是否允许您在每次 get() 时使用单个 List<Object> 来处理多个类型而无需强制转换?
  • @Jeff 请不要!尽管Collection.emptyXyz() 系列方法使用了类似的东西。
  • 哦,相信我,我不会,但我的答案目前严重不正确,我不喜欢接受错误的答案。
【解决方案2】:

是的,我很确定这不是一回事。没有理由需要双重强制转换 - 它可能会消除有关不安全强制转换的编译警告(在这种情况下,您可能做错了),但否则就是不对的。

我的意思是,有自动 toString 呼叫例如println("" + i),但即便如此,您也不需要先强制转换为对象...

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

我将坚持没有(或至少极少且相差甚远)很好理由这样做,但是,提供的示例肯定与此无关用它。

【讨论】:

  • @Jeff 我认为它会添加有关不安全投射的警告(如果有的话)。它可能做的是让它完全编译(尽管这不一定是一件好事)。
  • @TomHawtin-tackline 可能 - 我现在没有任何试验方法。你说得对,这不是一件好事 - 任何时候它允许代码编译,否则它抛出运行时异常(除非编译器中的错误等当然)
【解决方案3】:

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

例如,我将使用Mixin 框架。以下代码将在运行时注入Foo 类。我不会过多介绍它的工作原理。

@Mixin(Foo.class)
public class MixinFoo {

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

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

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

当然,这远远超出了 Java 入门的技能水平。

【讨论】:

【解决方案4】:

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

即使在上面的问题中,最终还是需要一个API方法来转换对象。

【讨论】:

    【解决方案5】:

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

    问题在于,当我将整数值转换为 double 时会出错,因为 Java 会自动将其检测为 Integer object 而不是 int 原始类型,并且您无法从 Integer 转换为 double。

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

    总结一下,如果您将 int 存储为对象,并且您想将其转换为 double 以进行某些除法,java 会自动将其转换为 Integer,从而无法进行除法。

    【讨论】:

    • 或者你可以做value.doubleValue()
    • 我认为一些代码来演示您描述的问题和解决方案会更容易理解(并且您可以摆脱所有文字解释)
    【解决方案6】:

    一个真正的用例是:

    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
    }
    

    【讨论】:

    • 这是什么意思? :))
    • 这是一个不安全的演员阵容,应该避免。
    • 避免是的,但仍然是真实的:)
    【解决方案7】:

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

    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。

    【讨论】:

      【解决方案8】:

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

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

      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&lt;String&gt;,会发生什么?好吧,它的语法是一个非常丑陋的双重转换。它还要求数据源能够自行确定擦除的类型,因为它在运行时不存在于类型标记中,尽管进行了强制转换:

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

      为什么 Java 不允许一步将 List.class 直接转换为(虚构的)类型 Class&lt;List&lt;String&gt;&gt; 我无法告诉你。我希望 Java 在未来的某个时候能够具体化泛型类型。那么List&lt;String&gt;.class 将是一个有效的类型标记,你不需要任何转换,更不用说奇怪的双重原始到假通用转换了。

      【讨论】:

        【解决方案9】:

        也许这个问题是逻辑?我的意思是,有一种方法可以计算一些东西(使用整数)并返回对象,以及将结果转换为字符串的方法的使用,通常这个方法可以被覆盖几次,像这样的功能在 Java 1.5 之前使用过你是否定义了 Generic calss(但没有泛型)并且每个方法的返回都是 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
            }
        }
        

        【讨论】:

          【解决方案10】:

          虽然从程序员的角度来看,双重转换似乎是多余的,但至少了解语法的工作原理可以帮助您理解 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,因此不会发生错误。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-03-26
            • 2021-06-14
            • 1970-01-01
            相关资源
            最近更新 更多