为什么 javac 允许一些不可能的转换而不是其他的?
Why does javac allow some impossible casts and not others?
如果我尝试将 String
转换为 java.util.Date
,Java 编译器会捕获错误。那么,为什么编译器不将以下内容标记为错误?
List<String> strList = new ArrayList<>();
Date d = (Date) strList;
当然,JVM 在运行时会抛出 ClassCastException
,但编译器不会标记它。
行为与 javac 1.8.0_212 和 11.0.2.
相同
演员在技术上是可行的。 javac 无法轻易证明您的情况并非如此,并且 JLS 实际上将其定义为有效的 Java 程序,因此标记错误是不正确的。
这是因为List
是一个接口。因此,您可以将 Date
的子 class 实际实现 List
伪装成 List
在这里 - 然后将其转换为 Date
就完全可以了。例如:
public class SneakyListDate extends Date implements List<Foo> {
...
}
然后:
List<Foo> list = new SneakyListDate();
Date date = (Date) list; // This one is valid, compiles and runs just fine
检测这种情况可能并不总是可能的,因为如果实例来自某个方法,则需要运行时信息。即使这样,编译器也需要付出更多的努力。由于 class-tree 根本无法匹配,编译器只会阻止绝对不可能的转换。如所见,这里不是这种情况。
请注意,JLS 要求您的代码是有效的 Java 程序。在 5.1.6.1. Allowed Narrowing Reference Conversion 它说:
A narrowing reference conversion exists from reference type S
to reference type T
if all of the following are true:
- [...]
- One of the following cases applies:
- [...]
S
is an interface type, T
is a class type, and T
does not name a final
class.
所以即使编译器能够发现你的案例实际上是不可能的,它也不允许标记错误,因为 JLS 将其定义为有效 Java 程序。
只允许显示警告。
让我们考虑一下您的示例的概括:
List<String> strList = someMethod();
Date d = (Date) strList;
这些是Date d = (Date) strList;
不是编译错误的主要原因。
直觉原因是编译器(通常)不知道该方法调用return对象的精确类型.有可能除了是实现 List
的 class 之外, 也是 Date
的子 class。
技术原因是Java语言规范"allows"缩小引用转换 对应于此类型转换。根据 JLS 5.1.6.1:
"A narrowing reference conversion exists from reference type S
to reference type T
if all of the following are true:"
...
5) "S
is an interface type, T
is a class type, and T
does not name a final
class."
...
另外一个地方JLS也说了可能会在运行时抛出异常...
请注意,JLS 5.1.6.1 的确定仅 涉及变量的声明类型,而不是实际的运行时类型。在一般情况下,编译器不知道也不能知道实际的运行时类型。
那么,为什么 Java 编译器不能计算出转换不工作?
在我的示例中,someMethod
调用可以 return 具有多种类型的对象。即使编译器能够分析方法体并确定可以 returned 的精确类型集,也没有什么可以阻止有人将其更改为 return 不同的类型......在编译调用它的代码。这就是JLS 5.1.6.1说到做到的根本原因。
在您的示例中,智能编译器可以发现转换永远不会成功。并且允许发出编译时警告来指出问题。
那么为什么不允许智能编译器说这是一个错误呢?
因为 JLS 说这是一个有效的程序。时期。任何将其称为 error 的编译器都不符合 Java。
此外,任何拒绝 JLS 和 other 编译器认为有效的 Java 程序的编译器都会妨碍 Java 源代码.
5.5.1. Reference Type Casting:
Given a compile-time reference type S
(source) and a compile-time
reference type T
(target), a casting conversion exists from S
to
T
if no compile-time errors occur due to the following rules.
[...]
If S
is an interface type:
[...]
If T
is a class or interface type that is not final, then if there exists a supertype X
of T
, and a supertype Y
of
S
, such that both X
and Y
are provably distinct parameterized
types, and that the erasures of X
and Y
are the same, a
compile-time error occurs.
Otherwise, the cast is always legal at compile time (because even if T
does not implement S
, a subclass of T
might).
List<String>
是 S
而 Date
在你的情况下是 T
。
如果我尝试将 String
转换为 java.util.Date
,Java 编译器会捕获错误。那么,为什么编译器不将以下内容标记为错误?
List<String> strList = new ArrayList<>();
Date d = (Date) strList;
当然,JVM 在运行时会抛出 ClassCastException
,但编译器不会标记它。
行为与 javac 1.8.0_212 和 11.0.2.
相同演员在技术上是可行的。 javac 无法轻易证明您的情况并非如此,并且 JLS 实际上将其定义为有效的 Java 程序,因此标记错误是不正确的。
这是因为List
是一个接口。因此,您可以将 Date
的子 class 实际实现 List
伪装成 List
在这里 - 然后将其转换为 Date
就完全可以了。例如:
public class SneakyListDate extends Date implements List<Foo> {
...
}
然后:
List<Foo> list = new SneakyListDate();
Date date = (Date) list; // This one is valid, compiles and runs just fine
检测这种情况可能并不总是可能的,因为如果实例来自某个方法,则需要运行时信息。即使这样,编译器也需要付出更多的努力。由于 class-tree 根本无法匹配,编译器只会阻止绝对不可能的转换。如所见,这里不是这种情况。
请注意,JLS 要求您的代码是有效的 Java 程序。在 5.1.6.1. Allowed Narrowing Reference Conversion 它说:
A narrowing reference conversion exists from reference type
S
to reference typeT
if all of the following are true:
- [...]
- One of the following cases applies:
- [...]
S
is an interface type,T
is a class type, andT
does not name afinal
class.
所以即使编译器能够发现你的案例实际上是不可能的,它也不允许标记错误,因为 JLS 将其定义为有效 Java 程序。
只允许显示警告。
让我们考虑一下您的示例的概括:
List<String> strList = someMethod();
Date d = (Date) strList;
这些是Date d = (Date) strList;
不是编译错误的主要原因。
直觉原因是编译器(通常)不知道该方法调用return对象的精确类型.有可能除了是实现
List
的 class 之外, 也是Date
的子 class。技术原因是Java语言规范"allows"缩小引用转换 对应于此类型转换。根据 JLS 5.1.6.1:
"A narrowing reference conversion exists from reference type
S
to reference typeT
if all of the following are true:"...
5) "
S
is an interface type,T
is a class type, andT
does not name afinal
class."...
另外一个地方JLS也说了可能会在运行时抛出异常...
请注意,JLS 5.1.6.1 的确定仅 涉及变量的声明类型,而不是实际的运行时类型。在一般情况下,编译器不知道也不能知道实际的运行时类型。
那么,为什么 Java 编译器不能计算出转换不工作?
在我的示例中,
someMethod
调用可以 return 具有多种类型的对象。即使编译器能够分析方法体并确定可以 returned 的精确类型集,也没有什么可以阻止有人将其更改为 return 不同的类型......在编译调用它的代码。这就是JLS 5.1.6.1说到做到的根本原因。在您的示例中,智能编译器可以发现转换永远不会成功。并且允许发出编译时警告来指出问题。
那么为什么不允许智能编译器说这是一个错误呢?
因为 JLS 说这是一个有效的程序。时期。任何将其称为 error 的编译器都不符合 Java。
此外,任何拒绝 JLS 和 other 编译器认为有效的 Java 程序的编译器都会妨碍 Java 源代码.
5.5.1. Reference Type Casting:
Given a compile-time reference type
S
(source) and a compile-time reference typeT
(target), a casting conversion exists fromS
toT
if no compile-time errors occur due to the following rules.[...]
If
S
is an interface type:
[...]
If
T
is a class or interface type that is not final, then if there exists a supertypeX
ofT
, and a supertypeY
ofS
, such that bothX
andY
are provably distinct parameterized types, and that the erasures ofX
andY
are the same, a compile-time error occurs.Otherwise, the cast is always legal at compile time (because even if
T
does not implementS
, a subclass ofT
might).
List<String>
是 S
而 Date
在你的情况下是 T
。