请考虑以下代码:
public interface A {
public A another();
}
public interface B {
public B another();
}
public interface AB extends A,B {
public AB another();
}
Run Code Online (Sandbox Code Playgroud)
这会导致编译错误AB:
B型和A型不兼容; 两者都定义了另一个(),但具有不相关的返回类型
我已经看到了这个问题,并按照接受的答案中的不兼容性示例 - 即
public interface C {
public void doSomething();
}
public interface D {
public boolean doSomething();
}
public interface CD extends C,D {
}
Run Code Online (Sandbox Code Playgroud)
但是,在这种情况下,返回类型实际上是不兼容的 - 返回类型不能同时为void和布尔值.然而,在上面的示例中,another()返回类型AB是a A和a B,因此可以实现两个扩展接口.
此外,看过JLS(8.4.8,8.4.8.3,8.4.8.4)后,我不太明白为什么我的上面的例子非法.任何人都可以向我解释这个吗?
其次,是否有任何解决方案/解决方法,此除重复的合同要求A或B在AB?
以下是JLS第8.4.8.2节的简要示例.
class Super {
static String greeting() { return "Goodnight"; }
String name() { return "Richard"; }
}
class Sub extends Super {
static String greeting() { return "Hello"; }
String name() { return "Dick"; }
}
class Test {
public static void main(String[] args) {
Super s = new Sub();
System.out.println(s.greeting() + ", " + s.name());
}
}
Run Code Online (Sandbox Code Playgroud)
根据该例子的讨论,跑步的输出main()将是"晚安,迪克".这是因为静态方法是根据调用它们的变量/表达式的静态类型调用的.
这是我的问题:任何即使是中等流量敏感的编译器都可以确定s在调用时存储的任何对象的类型必须始终如此Sub,因此如果允许编译器使用该信息,即使调用静态方法也可能有一些动态绑定的感觉.为什么不允许这样做?Java是否有明确的目标,即每个编译器生成的行为完全相同的字节码还是有其他原因?
所以,我正在尝试编写一个方法来回答我之前的一个问题:如何判断一个任意的java.lang.Method是否会覆盖另一个?为此,我正在阅读JLS,并且在一个案例中似乎缺少一些部分.
想象一下,您有以下课程:
public class A<T> {
public void foo(T param){};
}
public class B extends A<String> {
public void foo(String param){};
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,很明显是B.foo覆盖A.foo,但我不明白这种情况如何符合规范.
关于方法覆盖,JLS§8.4.8.1规定:
在类C中声明的实例方法m1将覆盖在类A中声明的另一个实例方法m2,如果以下所有条件都为真:
C是A的子类.
m1的签名是m2签名的子签名(§8.4.2).
或者:
- m2在与C相同的包中是公共的,受保护的或声明的,具有默认访问权限
- m1覆盖方法m3(m3与m1不同,m3与m2不同),使得m3覆盖m2.
显然,在我们的案例中,第1点和第3点是满意的.让我们在JLS中看一下子签名意味着什么.该JLS§8.4.2说:
如果两个方法具有相同的名称和参数类型,则它们具有相同的签名.
如果满足以下所有条件,则两个方法或构造函数声明M和N具有相同的参数类型:
它们具有相同数量的形式参数(可能为零)
它们具有相同数量的类型参数(可能为零)
设A1,...,An为M的类型参数,让B1,...,Bn为N的类型参数.将N的类型中每次出现的Bi重命名为Ai后,相应类型变量的界限为同样,M和N的形式参数类型是相同的.
在我们的例子中,第1点显然是正确的(都有1个参数).
第2点有点混乱(这就是我不确定规范究竟意味着什么):两种方法都没有声明它们自己的类型参数,而是A.foo使用T哪种类型变量来对类进行参数化.
所以我的第一个问题是:在这个上下文中,是否在类计数中声明类型变量?
好的,现在让我们假设T不计算,因此第2点是假的(我不知道在这种情况下我怎么能应用第3点).我们的两种方法没有相同的签名,但这并不妨碍B.foo成为其子签名A.foo.
在JLS§8.4.2中稍微进一步说:
方法m1的签名是方法m2的签名的子签名,如果:
m2与m1具有相同的签名,或
m1的签名与m2签名的擦除(§4.6)相同.
我们已经确定第1点是错误的.
根据JLS§4.6的方法的擦除签名是a signature consisting of the same name …
鉴于这一小部分代码:
import java.util.Arrays;
public class Sample {
private final int test;
private Sample(int test) {
this.test = test;
}
public static void main(String[] args) {
System.out.println(Arrays.toString(Hello.class.getDeclaredConstructors()));
}
public static class Hello {
private final int i;
private Hello(int i) {
this.i = i;
}
public int getI() {
return i;
}
public static class Builder {
private int i;
private Builder() {
}
public static Builder builder() {
return new Builder();
}
public void add(int i) {
this.i = i; …Run Code Online (Sandbox Code Playgroud) Quoth JLS#8.1.3:
内部类可能不会声明静态初始化器(第8.7节)......
这表现如下:
class A {
class B {
static { // Compile-time Error: Cannot define static initializer in inner type A.B
System.out.println("Class is initializing...");
}
}
}
Run Code Online (Sandbox Code Playgroud)
既然Java的内部(非静态)类是由类加载器加载的,就像其他类一样,为什么我们不能为它们安装静态初始化器?
这种限制背后的原因是什么?
虽然JLS规范中似乎非常精确地描述了Java语法,但是有一些具体案例我无法应用于给定的定义.
例如,采用ClassInstanceCreationExpressionJLS8第15.9章中的规则,非限定new表达式应采用以下形式:
new [TypeArguments] {Annotation} Identifier [TypeArgumentsOrDiamond] ( [ArgumentList] ) [ClassBody]
Run Code Online (Sandbox Code Playgroud)
Identifier是一个标准的Java标识符(基本上是Java字母/数字,没有点).
该定义如何应用于静态嵌套类instanciation等有效表达式:
new C1.C2();
Run Code Online (Sandbox Code Playgroud)
或包合格的类instanciation:
new java.lang.String("foo");
Run Code Online (Sandbox Code Playgroud)
鉴于点不能成为一部分Identifier?
请注意,对于非限定new表达式,此定义从JLS7更改为JLS8,其中JLS7 表示:
new [TypeArguments] TypeDeclSpecifier [TypeArgumentsOrDiamond]( [ArgumentList] ) [ClassBody]
Run Code Online (Sandbox Code Playgroud)
TypeDeclSpecifier 被定义为:
TypeDeclSpecifier:
TypeName
ClassOrInterfaceType . Identifier
Run Code Online (Sandbox Code Playgroud)
允许new静态嵌套类和包限定类的非限定表达式.
考虑这个程序:
public class xx<T> {
<T> Iterable<T> createIterable(Class<T> cls) {
return null;
}
Iterable<? extends Number> createNumberIterable(boolean floatingPoint) {
return this.createIterable(floatingPoint ? Integer.class : Float.class);
}
}
Run Code Online (Sandbox Code Playgroud)
在Java 7下,它编译:
$ java -version
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
$ javac xx.java
$
Run Code Online (Sandbox Code Playgroud)
在Java 8下它没有:
$ java -version
java version "1.8.0_40"
Java(TM) SE Runtime Environment (build 1.8.0_40-b25)
Java HotSpot(TM) 64-Bit Server VM (build 25.40-b25, mixed mode)
$ javac xx.java …Run Code Online (Sandbox Code Playgroud) 我对这个程序的输出感到有些神秘:
public class xx {
public static void main(String[] args) throws Exception {
Number x = false ? new Long(123) : new Integer(456);
System.out.println(x + " isa " + x.getClass().getName());
}
}
Run Code Online (Sandbox Code Playgroud)
这是它输出的内容:
456 isa java.lang.Long
Run Code Online (Sandbox Code Playgroud)
它出现在编译器"促进"类型的对象Integer来Long,就像它通常会促进原始值.我从来没有听说过对象推广,这种行为似乎非常令人惊讶.
我的问题:根据JLS,这是非常正确的行为吗?如果是这样,我希望尽可能看到参考.
或者这是一种autoboxing-gone-wild编译器错误?
我正在使用:
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
Run Code Online (Sandbox Code Playgroud) Java语言规范是否要求将Java编译为Java字节代码?
据我所知,事实并非如此:
编译时通常包括将程序转换为与机器无关的字节代码[表示.
[...]
Java编程语言通常被编译为Java虚拟机规范Java SE 9 Edition中定义的字节码指令集和二进制格式.
(强调我的)
我在规范中找不到任何其他提到的"字节码"或"字节码".
这是否意味着所有字节码操作在技术上都不被JLS定义的"Java语言"所涵盖,并且在技术上依赖于实现细节?
java ×10
jls ×10
bytecode ×1
constructor ×1
generics ×1
grammar ×1
interface ×1
java-module ×1
javac ×1
overriding ×1
reflection ×1