public class Test { // No throws clause here public static void main(String[] args) { doThrow(new SQLException()); } static void doThrow(Exception e) { Test.
doThrow0(e); } @SuppressWarnings("unchecked") static
void doThrow0(Exception e) throws E { throw (E) e; } }
class Test { Object x() { return "abc"; } String x() { return "123"; } }
正确的。Java 语言不允许在同一个类中同时等效地重写两个方法,无论它们的抛出或返回类型有何不同。但等一下。再次检查 Class.getMethod(String, Class...) 的文档。它说:
请注意,一个类中可能有多个相应的方法,因为虽然Java语言禁止多个具有相同签名但返回类型不同的方法,但Java虚拟机却不这样做。虚拟机的这种灵活性可用于实现各种语言功能。例如,协变返回可以通过桥接方法来完成;桥接方法和重写方法将具有相同的签名,但返回类型不同。 哇,这很有道理。当您编写以下内容时,实际上发生了很多事情: 查看生成的字节码: 所以 t 实际上是字节码中的一个对象。这是很好理解的。合成桥方法实际上是由编译器生成的,因为在调用的某些部分可以预期 Parent.x() 的返回类型。在二进制表示中将不再可能添加没有此类桥接方法的泛型。因此,更改 JVM 以允许这样的功能产生更少的痛苦(这也允许协变方法重写作为副作用......)聪明吧?
3. 以下均为二维数组。 这实际上是真的。即使您的心理分析器无法立即理解上述方法的返回类型,它们都是相同的!就像下一段代码一样。 你认为这很疯狂吗?写作的机会之多也简直爆炸想象力!
键入注释。
这种装置的神秘性仅次于它的力量。 或者换句话说:当我在 4 周假期之前做出最后一次承诺时。 我允许您以任何您喜欢的方式使用它。
4. 你不会得到条件语句 那么,当你开始使用条件语句时,你认为你已经了解了条件语句的所有内容吗?让我让你失望吧——你错了。大多数人都会认为下面两个例子是等价的: 等价于这个? 不。让我们快速测试一下 程序将输出以下内容: 是的!如果需要,条件运算符将执行类型转换。因为否则你会期望程序抛出 NullPointerException?
5. 您也不会得到复合赋值运算符。 足智多谋就够了吗?我们来看下面两个代码片段:
abstract class Parent
{ abstract T x(); } class Child extends Parent
{ @Override String x() { return "abc"; } }
// Method descriptor #15 ()Ljava/lang/String; // Stack: 1, Locals: 1 java.lang.String x(); 0 ldc
[16] 2 areturn Line numbers: [pc: 0, line: 7] Local variable table: [pc: 0, pc: 3] local: this index: 0 type: Child // Method descriptor #18 ()Ljava/lang/Object; // Stack: 1, Locals: 1 bridge synthetic java.lang.Object x(); 0 aload_0 [this] 1 invokevirtual Child.x() : java.lang.String [19] 4 areturn Line numbers: [pc: 0, line: 1]
class Test { int[][] a() { return new int[0][]; } int[] b() [] { return new int[0][]; } int c() [][] { return new int[0][]; } }
class Test { int[][] a = {{}}; int[] b[] = {{}}; int c[][] = {{}}; }
@Target(ElementType.TYPE_USE) @interface Crazy {} class Test { @Crazy int[][] a1 = {{}}; int @Crazy [][] a2 = {{}}; int[] @Crazy [] a3 = {{}}; @Crazy int[] b1[] = {{}}; int @Crazy [] b2[] = {{}}; int[] b3 @Crazy [] = {{}}; @Crazy int c1[][] = {{}}; int c2 @Crazy [][] = {{}}; int c3[] @Crazy [] = {{}}; }
Object o1 = true ? new Integer(1) : new Double(2.0);
Object o2; if (true) o2 = new Integer(1); else o2 = new Double(2.0);
System.out.println(o1); System.out.println(o2);
1.0 1
Integer i = new Integer(1); if (i.equals(1)) i = null; Double d = new Double(2.0); Object o = true ? i : d; // NullPointerException! System.out.println(o);
i += j; i = i + j;
直观上来说,它们应该是相等的,对吧?但你知道吗——它们是不同的。JLS 规范规定:
E1 op = E2 类型的复合表达式等效于 E1 = (T) ((E1) op (E2)),其中 T 是 E1 的类型,但 E1 仅计算一次。 一个很好的例子是使用 *= 或 /= :
byte b = 10; b *= 5.7; System.out.println(b); // prints 57
or:
byte b = 100; b /= 2.5; System.out.println(b); // prints 40
or:
char ch = '0'; ch *= 1.1; System.out.println(ch); // prints '4'
or:
char ch = 'A'; ch *= 1.5; System.out.println(ch); // prints 'a'
那么,这仍然是一个有用的工具吗?
6. 随机整数 现在来做一个更困难的任务。不要阅读解决方案。看看你自己是否能找到答案。当我运行以下程序时:
for (int i = 0; i < 10; i++) { System.out.println((Integer) i); }
有时我会得到以下输出:
92 221 45 48 236 183 39 193 33 84
但这怎么可能呢?好吧,答案在于通过反射覆盖 JDK 的 Integer 缓存,然后使用自动装箱和自动拆箱。未经成人许可,请勿这样做!或者换句话说:
7. GOTO 我的最爱之一。Java 有 GOTO!写这个:
int goto = 1;
你会得到这个: 那是因为 goto 是一个未使用的保留字,以防万一......但这不是令人兴奋的部分。最酷的是,你可以包含 goto 与break、continue和标记块配对: 在字节码中向前跳转: 在字节码中 向后跳转 :
8. Java有类型别名 在其他语言(如Ceylon)中,我们可以定义类型别名非常简单: 这里的 People 类的构建方式可以与 Set 互换
Test.java:44: error:
expected int goto = 1; ^
label: { // do stuff if (check) break label; // do more stuff }
2 iload_1 [check] 3 ifeq 6 // Jumping forward 6 ..
label: do { // do stuff if (check) continue label; // do more stuff break label; } while(true);
2 iload_1 [check] 3 ifeq 9 6 goto 2 // Jumping backward 9 ..
interface People => Set
;
People? p1 = null; Set
? p2 = p1; People? p3 = p2;
class Test {
void x(I i, L l) { System.out.println( i.intValue() + ", " + l.longValue() ); } }
new Test().x(1, 2L);
// A helper type. You could also just use List interface Type
{} class C implements Type
> {} class D
implements Type
那么C和D到底是什么意思呢?从某种意义上说,它们是递归的,类似于 java.lang.Enum 中的递归。考虑一下: 考虑到上面的规范,枚举的实际实现只是语法糖: 考虑到这一点,让我们回到我们的两种类型。下面的代码能编译通过吗? 一个难题……而且实际上还没有得到解决? C 是 Type 的子类型吗? 尝试在您的 Eclipse 或 Idea 中编译它,他们会告诉您他们的想法。把它冲进下水道…… Java中的一些类型关系是不可判定的!10. 类型交集 Java 有一个非常有趣的特性,称为类型交集。您可以声明一个实际上是两种类型的交集的(通用)类型。例如: 与 Test 类的实例关联的自定义类型参数 T 必须同时包含 Serialized 和 Cloneable 接口。例如,String 不能受到限制,但 Date 可以: 此功能在 Java8 中有多种用途,您可以在其中强制转换类型。这有什么帮助?几乎什么都没有,但如果你想将 lambda 表达式转换为你需要的类型,那么没有其他方法。假设您的方法有一个疯狂的限制: 只有当您想在另一个地方执行它并通过网络发送结果时,您才需要 Runnable ,同时它也是可序列化的。Lambda 和序列化增添了一点讽刺意味。 如果 lambda 表达式的目标类型和参数是可序列化的,则可以序列化它。 但即使这是真的,它们也不会自动启用 Serialized 接口。你必须自己把它们变成这种类型。但是,当您仅强制转换为 Serialized: 时,lambda 将不再是 Runnable,因此将它们强制转换为两种类型: 总而言之: public abstract class Enum
// This enum MyEnum {} // Is really just sugar for this class MyEnum extends Enum
class Test { Type c = new C(); Type> d = new D
Step 0) C Step 1) Type
>> > Step 4) D
class Test
// Doesn't compile Test
execute((Serializable) (() -> {}));
execute((Runnable & Serializable) (() -> {}));
GO TO FULL VERSION