请考虑以下示例:
class Quirky {
public static void main(String[] args) {
int x = 1;
int y = 3;
System.out.println(x == (x = y)); // false
x = 1; // reset
System.out.println((x = y) == x); // true
}
}
Run Code Online (Sandbox Code Playgroud)
我不确定Java语言规范中是否有一个项目要求加载变量的先前值以与右侧(x = y)进行比较,右侧()按括号隐含的顺序首先计算.
为什么第一个表达式评估false,但第二个表达式评估为true?我本来期望(x = y)先评估,然后它会x与自己(3)进行比较并返回true.
这个问题与Java表达式中子表达式的评估顺序不同,这x绝对不是这里的"子表达式".需要加载它以进行比较而不是"评估".这个问题是特定于Java的,而且这个表达式x == (x = y)不同于通常为棘手的面试问题精心设计的不切实际的结构,而是来自一个真实的项目.它应该是比较和替换成语的单行替代品
int oldX = x;
x = y;
return …Run Code Online (Sandbox Code Playgroud) 到目前为止,我认为有效的 final和final或多或少是等效的,如果在实际行为中不相同,JLS 会将它们相似地对待。然后我发现了这个人为的场景:
final int a = 97;
System.out.println(true ? a : 'c'); // outputs a
// versus
int a = 97;
System.out.println(true ? a : 'c'); // outputs 97
Run Code Online (Sandbox Code Playgroud)
显然,JLS 在这里两者之间产生了重要区别,我不知道为什么。
我阅读了其他主题,例如
但他们没有详细说明。毕竟,在更广泛的层面上,它们似乎几乎是等价的。但深入挖掘,它们显然不同。
是什么导致了这种行为,谁能提供一些解释这一点的 JLS 定义?
编辑:我发现了另一个相关的场景:
final String a = "a";
System.out.println(a + "b" == "ab"); // outputs true
// versus
String a = "a";
System.out.println(a + "b" == "ab"); // outputs false
Run Code Online (Sandbox Code Playgroud)
所以字符串实习在这里的行为也不同(我不想在实际代码中使用这个片段,只是对不同的行为感到好奇)。
这里提出了一些问题,为什么你不能在接口中定义静态方法,但它们都没有解决基本的不一致性:为什么你可以在接口中定义静态字段和静态内部类型,而不是静态方法?
静态内部类型可能不是一个公平的比较,因为这只是产生一个新类的语法糖,但为什么是字段而不是方法?
接口中的静态方法的一个参数是它破坏了JVM使用的虚拟表解析策略,但是不应该同样适用于静态字段,即编译器可以内联它吗?
一致性是我想要的,Java应该支持接口中没有任何形式的静态,或者它应该是一致的并允许它们.
尝试x通过未初始化的局部变量访问静态字段时,出现Foo foo; foo.x编译错误Variable 'foo' might not have been initialized。
class Foo{
public static int x = 1;
public static void main(String[] args) {
Foo foo;
System.out.println(foo.x); // Error: Variable 'foo' might not have been initialized
}
}
Run Code Online (Sandbox Code Playgroud)
它可能看起来像这样的错误是有道理的,但直到我们意识到,访问static成员编译器不实际使用值的变量,但只有它的类型。
例如,我可以foo使用value 进行初始化,null这将使我们能够x毫无问题地进行访问:
Foo foo = null;
System.out.println(foo.x); //compiles and while running prints 1!!!
Run Code Online (Sandbox Code Playgroud)
之所以如此,x是因为编译器意识到这是静态的,并foo.x像对待其编写时一样对待Foo.x(至少这是我到目前为止所认为的)。 …
给出以下函数调用C:
fooFunc( barFunc(), bazFunc() );
Run Code Online (Sandbox Code Playgroud)
执行的顺序barFunc和BazFunc未指定,因此barFunc()可以在in 之前bazFunc()或bazFunc()之前调用.barFunc()C
并Java指定函数参数表达式执行顺序或喜欢的C是不确定?
请考虑以下示例代码
class MyClass {
public String var = "base";
public void printVar() {
System.out.println(var);
}
}
class MyDerivedClass extends MyClass {
public String var = "derived";
public void printVar() {
System.out.println(var);
}
}
public class Binding {
public static void main(String[] args) {
MyClass base = new MyClass();
MyClass derived = new MyDerivedClass();
System.out.println(base.var);
System.out.println(derived.var);
base.printVar();
derived.printVar();
}
}
Run Code Online (Sandbox Code Playgroud)
它给出了以下输出
base
base
base
derived
Run Code Online (Sandbox Code Playgroud)
在运行时解析方法调用,并按预期调用正确的重写方法.
相反,变量访问在编译时解析,我后来才知道.我期待输出为
base
derived
base
derived
Run Code Online (Sandbox Code Playgroud)
因为在派生类中,var阴影的重新定义是基类中的阴影.
为什么变量的绑定发生在编译时而不是在运行时?这只是出于性能原因吗?
请考虑以下事项:
public Class<List<String>> getObjectType() {
// what can I return here?
}
Run Code Online (Sandbox Code Playgroud)
我可以从这个满足泛型和编译的方法返回什么类文字表达式?List.class不会编译,也不会编译List.<String>class.
如果你想知道"为什么",我正在写一个Spring的实现FactoryBean<List<String>>,这需要我实现Class<List<String>> getObjectType().但是,这不是一个Spring问题.
编辑: SpringSource的权力听到了我的哀悼,因此Spring 3.0.1将返回类型getObjectType()改为Class<?>,这样可以很好地避免问题.
我有3个班:
public class Alpha {
public Number number;
}
public class Beta extends Alpha {
public String number;
}
public class Gama extends Beta {
public int number;
}
Run Code Online (Sandbox Code Playgroud)
为什么以下代码编译?而且,为什么测试通过没有任何运行时错误?
@Test
public void test() {
final Beta a = new Gama();
a.number = "its a string";
((Alpha) a).number = 13;
((Gama) a).number = 42;
assertEquals("its a string", a.number);
assertEquals(13, ((Alpha) a).number);
assertEquals(42, ((Gama) a).number);
}
Run Code Online (Sandbox Code Playgroud) 好的,所以方法重载是一个糟糕的事情.既然已经解决了这个问题,我们假设我实际上想要重载这样的方法:
static void run(Consumer<Integer> consumer) {
System.out.println("consumer");
}
static void run(Function<Integer, Integer> function) {
System.out.println("function");
}
Run Code Online (Sandbox Code Playgroud)
在Java 7中,我可以使用非模糊的匿名类作为参数轻松地调用它们:
run(new Consumer<Integer>() {
public void accept(Integer integer) {}
});
run(new Function<Integer, Integer>() {
public Integer apply(Integer o) { return 1; }
});
Run Code Online (Sandbox Code Playgroud)
现在在Java 8中,我当然想用lambda表达式调用这些方法,我可以!
// Consumer
run((Integer i) -> {});
// Function
run((Integer i) -> 1);
Run Code Online (Sandbox Code Playgroud)
既然编译器应该能够推断出来Integer,为什么我不离开Integer呢?
// Consumer
run(i -> {});
// Function
run(i -> 1);
Run Code Online (Sandbox Code Playgroud)
但这不编译.编译器(javac,jdk1.8.0_05)不喜欢这样:
Test.java:63: error: reference to run is …Run Code Online (Sandbox Code Playgroud) 如果在不同的目录中将两个具有相同的不区分大小写的公共Java类写入,那么这两个类在运行时都不可用.(我在Windows,Mac和Linux上使用多个版本的HotSpot JVM进行了测试.如果有其他JVM可以同时使用,我也不会感到惊讶.)例如,如果我创建一个名为class的类,a并且A如下所示:
// lowercase/src/testcase/a.java
package testcase;
public class a {
public static String myCase() {
return "lower";
}
}
// uppercase/src/testcase/A.java
package testcase;
public class A {
public static String myCase() {
return "upper";
}
}
Run Code Online (Sandbox Code Playgroud)
如果尝试我这样调用myCase这两个类:
System.out.println(A.myCase());
System.out.println(a.myCase());
Run Code Online (Sandbox Code Playgroud)
类型检查成功,但是当我运行由上面的代码生成的类文件时,我得到:
线程"main"中的异常java.lang.NoClassDefFoundError:testcase/A(错误名称:testcase/a)
在Java中,名称通常区分大小写.某些文件系统(例如Windows)不区分大小写,因此上述行为发生时我并不感到惊讶,但似乎错了.不幸的是,Java规范对于哪些类是可见的是奇怪的非常规.在Java语言规范(JLS)的Java SE 7版(第6.6.1节,第166页)说:
如果一个类或接口类型被声明为public,那么任何代码都可以访问它,只要声明它的编译单元(第7.3节)是可观察的.
在7.3节中,JLS以极其模糊的术语定义了编译单元的可观察性:
预定义包java及其子包lang和io的所有编译单元始终是可观察的.对于所有其他包,主机系统确定哪些编译单元是可观察的.
在Java虚拟机规范同样是模糊的(第5.3.1节):
以下步骤用于加载,从而使用引导类加载器创建由[二进制名称] N表示的非阵列类或接口C [...]否则,Java虚拟机将参数N传递给方法的调用引导类加载器以依赖于平台的方式搜索C的声称表示.
所有这些都导致了四个问题,按重要性降序排列:
a和A同步?编写自定义类加载器会起作用吗?java ×10
jls ×10
equality ×1
final ×1
generics ×1
inheritance ×1
interface ×1
java-8 ×1
jvm ×1
lambda ×1
overloading ×1
static ×1
subclassing ×1