为什么我们不能在Java 7+中打开类?

Sni*_*las 5 java class switch-statement java-7 java-8

在我看来,这样的switch语句会很有意义,但它会产生编译错误:

public void m(Class c) {
   switch (c) {
       case SubClassOfC1.class : //do stuff; break;
       case SubClassOfC2.class : //do stuff; break;
   }
} 
Run Code Online (Sandbox Code Playgroud)

但是,不支持打开类.是什么原因?

我不是试图解决方法instanceof,它实际上是在类级别,我有一些操作要执行,没有实例.

编译错误在SubClassOfC1和SubClassOfC2附近: constant expression required.

dur*_*597 10

这是因为我们只能打开常量表达式(§15.28)枚举常量(§8.9.1).

来自JLS:

Expression的类型必须是char,byte,short,int,Character,Byte,Short,Integer,String或枚举类型(第8.9节),否则会发生编译时错误.

想象一下为什么会出现这种情况,请考虑Java编译器尝试编译switch语句时发生的优化.

  • 它希望绝对,积极地保证平等
  • 它希望能够最大化所有情况的性能(分支预测)
  • 它希望有改造常量表达式转换成整数的查找表的一个有效的,一致的方式(这就是为什么longfloatdouble不支持,但是String是).

请注意,语句String中支持仅在Java 7中添加.这是因为编译器采用的是背后的场景转换到,因为在这篇文章中详细介绍switchswitch Stringswitch int.快速摘要:

这段代码:

public class StringInSwitchCase {
    public static void main(String[] args) {
        String mode = args[0];
        switch (mode) {
        case "ACTIVE":
            System.out.println("Application is running on Active mode");
            break;
        case "PASSIVE":
            System.out.println("Application is running on Passive mode");
            break;
        case "SAFE":
            System.out.println("Application is running on Safe mode");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

成为这个代码:

public class StringInSwitchCase {
    public StringInSwitchCase() {}

    public static void main(string args[]) {
        String mode = args[0];
        String s;
        switch ((s = mode).hashCode()) {
        default:
            break;
        case -74056953:
            if (s.equals("PASSIVE")) {
                System.out.println("Application is running on Passive mode");
            }
            break;
        case 2537357:
            if (s.equals("SAFE")) {
                System.out.println("Application is running on Safe mode");
            }
            break;
        case 1925346054:
            if (s.equals("ACTIVE")) {
                System.out.println("Application is running on Active mode");
            }
            break;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我们无法Class以相同的方式可靠地将对象转换为int.类不会覆盖hashCode,它使用System.identityHashCode.

另请注意Class,如果已加载了不同的类,则相同的类并不总是相同的ClassLoader.

  • 那很有意思.如果`A.class`不是常量表达式,那是什么? (2认同)
  • @Snicolas:标识哈希码是一个通常从对象的内存位置(指针值)派生的值,或者简单地说,在每个运行时都是不同的.因此,编译器无法创建依赖于对象的标识哈希码的代码.但是,当然,每个类都有一个名称,切换名称的哈希码并不难. (2认同)

Jas*_*zun 9

根据声明中的文档switch:

交换机使用byte,short,char和int原始数据类型.它还适用于枚举类型(在枚举类型中讨论),String类,以及一些包含某些基本类型的特殊类:Character,Byte,Short和Integer.

从本质上讲,它只能用于那些类型,而不是其他任何类型.Class不是其中之一.

正如您在评论中所说的那样,限制的原因是切换表被索引int.所有上述类型都可以轻松转换为int(包括String通过散列),而Class不是.

  • @Snicolas - 类文字在运行时可能显示为不同的对象; 每次调用你的方法`m(c)`都可能在不同的类加载器中. (4认同)
  • @MCEmperor意味着长时间不能被打开.这可能是有意义的,因为切换表存储有数组(大小为int). (3认同)
  • @Snicolas转换为数字引用必须在编译时进行.字符串可能的唯一原因是因为java.lang.String的API指定了为所有JVM实现计算哈希码的机制. (3认同)

Hol*_*ger 7

有趣的是,到目前为止所有的答案基本上都是"因为规范是这样说的",这是正确的,但并不令人满意.在Java 7之前,String不允许使用s,这通常被视为刻在石头上.

但技术障碍不应该推动语言设计.如果没有办法将它编译成有效的代码,它仍然可以编译为相当于if … else …子句,并且仍然在源代码简洁方面获胜.在String价值观的情况下,有一种有效的方法.只需切换不变的哈希码并对equals匹配候选项执行检查.事实上,规范并没有强制要求使用哈希码,它可以是任何不变的int属性,例如长度或第一个字符,但它的值应该是不同的String.

同样,switch过度Class对象也是可能的.它的哈希码保证是相同的,但每个类都有一个带有常量哈希码的常量名.例如以下作品:

public class ClassSwitch {
    static final class Foo {}
    static final class Bar {}
    static final class Baz {}

    public static void main(String... arg) {
        Class<?> cl=Bar.class;
        switch(cl.getSimpleName().hashCode()) {
            case 70822: 
                if(cl==Foo.class) {
                    System.out.println("case Foo:");
                }
                break;
            case 66547: 
                if(cl==Bar.class) {
                    System.out.println("case Baz:");
                }
                break;
            case 66555: 
                if(cl==Baz.class) {
                    System.out.println("case Baz:");
                }
                break;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用简单名称而不是限定名称,因此此示例代码与包无关.但我认为,情况很清楚.可以switch为任何类型的对象实现有效的语句,该对象具有int可在编译时预测的常量属性.也就是说,也没有理由不支持long交换机.有很多方法可以intlong...中计算出合适的


所以还有另外一个重要决定要做.

这个功能真的有益吗?它看起来像代码味道 - 即使添加String支持也是有争议的.它不会增加新的可能性,因为您可以使用相同的if- else少数类或HashMap<Class,SomeHandlerType>更大的数字.并且它看起来并不像经常需要的东西,值得扩展语言规范,即使它只是一个必须添加的单句.

这些是推动语言设计的因素,但并不是思想和平衡无法改变.所以我并不是说未来的版本不可能获得这个功能.

但是,看看生成String switch代码的质量,我宁愿switches手动编写代码......