2016-09-25 53 views
-1

我有这样的代码:switch语句中的这个值如何被初始化?

int putReferenceItem(Reference reference) { 
    switch (reference.type) { 
     case CLASS: 
      return putStringItem(CONSTANT_Class, reference.name); 
     case FIELD: 
     case METHOD: 
     case INTERFACE_METHOD: 
      int itemTag; 
      switch (reference.type) { 
       case FIELD: 
        itemTag = CONSTANT_Fieldref; 
        break; 
       case METHOD: 
        itemTag = CONSTANT_Methodref; 
        break; 
       case INTERFACE_METHOD: 
        itemTag = CONSTANT_InterfaceMethodref; 
        break; 
      } 
      return putStringItem(itemTag, reference.owner, reference.name, reference.descriptor); 
     case HANDLE: 
      return put(CONSTANT_MethodHandle, reference.kind, 0, 0, reference.owner, reference.name, reference.descriptor); 
     case TYPE: 
      return putStringItem(CONSTANT_MethodType, reference.descriptor); 
     default: 
      throw new Error("Unreachable code."); 
    } 
} 

就行了,

return putStringItem(itemTag, reference.owner, reference.name, reference.descriptor); 

编译器抱怨的itemTag值可能没有被初始化。对我来说,这似乎很明显,嵌套的switch语句将始终初始化itemTag的值,因此我很难理解为何生成此错误。编译器是不是足够复杂来评估嵌套的switch语句,还是实际上可能对itemTag进行初始化?

我目前的解决方案是简单地添加抛出Error这样一个默认的情况下:

  switch (reference.type) { 
       case FIELD: 
        itemTag = CONSTANT_Fieldref; 
        break; 
       case METHOD: 
        itemTag = CONSTANT_Methodref; 
        break; 
       case INTERFACE_METHOD: 
        itemTag = CONSTANT_InterfaceMethodref; 
        break; 
       default: 
        throw new Error("Unreachable code."); 
      } 

不过,我觉得这是一个非常丑陋的解决方案。 (更新:我刚刚意识到,我可以用default:取代case INTERFACE_METHOD:并仍然得到同样的行为,而不丑我的问题仍然有效,但。)

你可能也注意到了,我已经重新使用该解决方案后在代码中避免必须返回一个值。此开关语句对于在ReferenceType中定义的每个常量都有一个实例,因此我不完全确定代码如何可能缺少return语句。

public enum ReferenceType { 

    CLASS, 
    FIELD, 
    METHOD, 
    INTERFACE_METHOD, 
    HANDLE, 
    TYPE 
} 

为什么编译器会产生这些错误?

+0

为什么不把'int itemTag'放在开关外,然后摆脱嵌套? –

回答

1

编译器不会在switch-statements内部查看是否初始化变量。如果它未在当前范围内初始化(例如,在您创建变量的switch语句之外),则会出现此错误。这样做的目的是在代码中稍后调用变量时保护您不会调用“null”。

如果您知道在switch语句中始终将其设置为正确的值,那么使用任意值初始化变量应该是安全的。无论这是否是最优雅的解决方案,都有待辩论,但它应该可以解决您的问题。我认为这比处理像你这样的错误要好。

编辑:

关于这个问题的下半年,我认为建立一个变量在正确的范围内可以解决你的问题返回。例如。如果初始化一个变量(再次使用任意值),则可以在switch语句中使用正确的值进行设置,然后在方法结尾处返回变量。这应该摆脱错误。

+2

错误是在编译时生成的,所以我对JVM在这个角色中的作用感到困惑。 –

+1

[需要的引证] - 你能提供一些相关的文件吗? – Li357

+1

它是编译器,而不是JVM,它是一个错误,而不是一个警告。 – EJP

0

那么,对我来说,它不是那么明显,价值将被初始化。

看下面我插入的地方<here>。现在想象一下,当执行到此时另一个线程开始并将reference.type更改为HANDLE和voilá时,您的值为switch中没有分支,并且该值将被初始化。

case INTERFACE_METHOD: 
    int itemTag; 
    // <here> 
    switch (reference.type) { 
     case FIELD: 
      itemTag = CONSTANT_Fieldref; 
      break; 
     case METHOD: 
      itemTag = CONSTANT_Methodref; 
      break; 
     case INTERFACE_METHOD: 
      itemTag = CONSTANT_InterfaceMethodref; 
      break; 
    } 
    return putStringItem(itemTag, reference.owner, reference.name, reference.descriptor); 

此外,如果你不想覆盖引用的情况。键入更改,为什么你有一个内部开关呢?那么在外部交换机中代码不会更好吗?

case INTERFACE_METHOD: 
    return putStringItem(CONSTANT_InterfaceMethodref, reference.owner, reference.name, reference.descriptor); 
+0

如果我定义了一个局部变量'ReferenceType type = reference.type;'并在'switch'而不是'reference.type'中使用它,那么并发不会成为问题,但仍然会产生错误。 –

+0

那么,你是在我的问题的第二部分。如果变量'type'不能改变,那么使用内部'switch'语句是没有意义的。为什么产生错误可能是因为编译器笨到某个限制。此外,增加这种智能是没有意义的,因为如果在外部和内部开关的评估之间该值不能改变,则整个内部开关是无意义的,因此不需要如我的答案中所述的内部开关。因此,为废话添加智慧是不值得的。 ;-) – Vampire

1

这是有问题的代码

int itemTag; 
switch (reference.type) { 
    case FIELD: 
     itemTag = CONSTANT_Fieldref; 
     break; 
    case METHOD: 
     itemTag = CONSTANT_Methodref; 
     break; 
    case INTERFACE_METHOD: 
     itemTag = CONSTANT_InterfaceMethodref; 
     break; 
} 

显然itemTag是如果reference.type是这三个enum值中的一个初始化。但其他两个呢?在写的代码中,如果它们发生,switch语句不会初始化itemTag

现在你(程序员)可能能够推断出其他两种情况不会发生。但编译器不能。为了做出这种推论,编译器需要深入理解你的类的语义,以及你如何使用它。它根本无法做到这一点。

的解决方案是上面的改变,以这样的:

int itemTag; 
switch (reference.type) { 
    case FIELD: 
     itemTag = CONSTANT_Fieldref; 
     break; 
    case METHOD: 
     itemTag = CONSTANT_Methodref; 
     break; 
    case INTERFACE_METHOD: 
     itemTag = CONSTANT_InterfaceMethodref; 
     break; 
    default: 
     throw AssertionError("impossible reference type"); 
} 

编译器现在将能够推导出itemTag为所有可能的方式,你可以到达return声明初始化其中变量是用过的。


顺便提一下,以下不是有效的解决方案。

int itemTag; 
switch (reference.type) { 
    case FIELD: 
     itemTag = CONSTANT_Fieldref; 
     break; 
    case METHOD: 
     itemTag = CONSTANT_Methodref; 
     break; 
    case INTERFACE_METHOD: 
     itemTag = CONSTANT_InterfaceMethodref; 
     break; 
    case CLASS: case HANDLE: case TYPE: 
     throw AssertionError("impossible reference type"); 
} 

这不是解决方案的原因是Java中的类可以以任何顺序重新编译。假设上面的代码已经被编译,然后你改变了enum声明来添加一个新的常量,并重新编译它。现在在运行时上面的switch语句不再覆盖所有可能的情况,这就意味着itemTag可以在不初始化的情况下使用。哎呀!

因此,编译器会抱怨上面的代码,说itemTag可能并不总是初始化。

奇怪......和UGLY ......但必要的,由于上述原因。

+0

我也意识到用'default'替换'case INTERFACE_METHOD'也会给出所需的行为。 –