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

Why can't we switch on classes in Java 7+?

在我看来,这样的 switch 语句很有意义,但它给出了一个编译错误:

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

但是class不支持开机。这是什么原因?

我不是在尝试解决方法 instanceof,它确实处于 class 级别,我需要执行一些操作,那里没有实例。

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

基于documentation on the switch statement

A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types (discussed in Enum Types), the String class, and a few special classes that wrap certain primitive types: Character, Byte, Short, and Integer.

因此,从本质上讲,它只适用于这些类型,而没有其他类型。 Class 不是其中之一。

限制的原因是,正如您自己在评论中所说,开关表由 int 索引。以上所有类型都可以通过散列轻松转换为 int(包括 String),而 Class 则不然。

嗯,根据documentationswitch只支持一组有限的数据类型:

A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types (discussed in Enum Types), the String class, and a few special classes that wrap certain primitive types: Character, Byte, Short, and Integer

我猜为什么会这样:因为除了相对简单的数据类型之外,编译器很难 generate efficient codeswitch

因为只能开启Constant Expressions (§15.28) or Enum constants (§8.9.1).

From the JLS:

The type of the Expression must be char, byte, short, int, Character, Byte, Short, Integer, String, or an enum type (§8.9), or a compile-time error occurs.

想像为什么会这样,想想当 Java 编译器试图编译 switch 语句时发生的优化。

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

请注意 switch 语句支持 Stringadded only in Java 7. This is because the compiler uses a behind the scenes conversion of switch String to switch int, as detailed in this article。快速总结:

此代码:

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");
        }
    }
}

变成这个代码:

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;
        }
    }
}

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

另请注意,相同的 class 并不总是相同的 Class,如果它加载了不同的 ClassLoader

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

但是技术障碍不应该驱动语言设计。如果没有办法将其编译为高效代码,它可能仍会被编译为等同于 if … else … 子句,并且仍然会在源代码简洁性方面取得优势。在 String 值的情况下,有一种有效的方法。只需切换不变哈希码并对匹配候选者执行 equals 检查。事实上,规范并没有强制使用哈希码,它可以是任何不变量 int 属性,例如长度或第一个字符,但每个 String.

的值应该不同

同样,switch 超过 Class 个对象是可能的。它的哈希码 not 保证相同,但每个 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;
        }
    }
}

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

计算出合适的 int

所以还有另一个重要的决定要做。

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

这些是驱动语言设计的考虑因素,但并不是思想和平衡不能改变。所以我并不是说未来的版本不可能获得此功能。

但是,看起来 at the quality of generated String switch code 我宁愿手动编写 switches 代码……

在 Java 8 中,您可以使用 lambda 表达式创建自己的 "switch-case"。这是一个关于如何打开对象 class 的简单示例(不是您想要的 Class 对象本身,但这似乎更有用):

import java.util.function.Consumer;

public class SwitchClass<T> {
    private static final SwitchClass<?> EMPTY = new SwitchClass<Object>(null) {
        @Override
        public <S> SwitchClass<Object> when(Class<S> subClass,
                Consumer<? super S> consumer) { return this; }

        @Override
        public void orElse(Consumer<? super Object> consumer) { }
    };

    final T obj;

    private SwitchClass(T obj) {
        this.obj = obj;
    }

    @SuppressWarnings("unchecked")
    public <S> SwitchClass<T> when(Class<S> subClass,
            Consumer<? super S> consumer) {
        if (subClass.isInstance(obj)) {
            consumer.accept((S) obj);
            return (SwitchClass<T>) EMPTY;
        }
        return this;
    }

    public void orElse(Consumer<? super T> consumer) {
        consumer.accept(obj);
    }

    public static <T> SwitchClass<T> of(T t) {
        return new SwitchClass<>(t);
    }
}

用法示例:

SwitchClass.of(obj)
    .when(Integer.class, i -> System.out.println("Integer: "+i.intValue()))
    .when(Double.class, d -> System.out.println("Double: "+d.doubleValue()))
    .when(Number.class, n -> System.out.println("Some number: "+n))
    .when(String.class, str -> System.out.println("String of length "+str.length()))
    .orElse(o -> System.out.println("Unknown object: "+o));

如果只执行第一个匹配的分支,那么对于Double对象只会执行Double分支,不会执行Number分支。巧妙的是类型转换是自动执行的。