为什么我们不能在 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
则不然。
嗯,根据documentation,switch
只支持一组有限的数据类型:
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 code 到 switch
。
因为只能开启Constant Expressions (§15.28) or Enum constants (§8.9.1).
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(这就是为什么
long
和 float
和 double
不支持,但 String
支持)。
请注意 switch
语句支持 String
是 added 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
分支。巧妙的是类型转换是自动执行的。
在我看来,这样的 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
则不然。
嗯,根据documentation,switch
只支持一组有限的数据类型:
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 code 到 switch
。
因为只能开启Constant Expressions (§15.28) or Enum constants (§8.9.1).
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(这就是为什么
long
和float
和double
不支持,但String
支持)。
请注意 switch
语句支持 String
是 added 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
分支。巧妙的是类型转换是自动执行的。