JavaRush /Java 博客 /Random-ZH /关于 Java 你不知道的 10 件事
minuteman
第 32 级

关于 Java 你不知道的 10 件事

已在 Random-ZH 群组中发布
那么,您最近开始使用 Java 了吗?还记得它被称为“Oak”的日子吗?那时,面向对象仍然是一个热门话题,那时,C++ 人们认为 Java 没有机会,甚至没有人听说过 Applet。我可以假设你连以下事情的一半都不知道。让我们以一些有关 Java 内部工作原理的酷炫惊喜开始新的一周。关于 Java 你不知道的 10 件事 - 11. 不存在受检查异常这样的东西。 这是正确的!JVM 不知道这样的事情,只有 Java 语言知道。今天,每个人都同意检查异常是一个错误。正如 Bruce Eckel 在布拉格 GeeCON 的最后一次演讲中所说,自 Java 以来,没有其他语言使用检查异常,甚至 Java 8 也不再在新的 Streams API 中涵盖它们(当您的 lambda 使用 IO 或 JDBC 时,这可能会带来一些不便)。你想要证明 JVM 不知道这样的事情吗?尝试以下代码: 这不仅会编译,还会抛出 SQLException,您甚至不需要为此使用 Lombok 的 @SneakyThrows。 2. 您可以拥有仅返回类型不同的重载方法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 [] = {{}}; } 关于 Java 你不知道的 10 件事 - 2 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 缓存,然后使用自动装箱和自动拆箱。未经成人许可,请勿这样做!或者换句话说: 关于 Java 你不知道的 10 件事 - 3 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 ; : 在Java中,我们不能简单地在顶层定义别名。但我们可以根据类或方法的需要来这样做。假设我们对 Integer、Long 等名称不满意。我们想要更短的名称:I 和 L。 简单: 在上面的示例中,为了 Test 类的可见性,Integer 被转换为 I,而 Long 被转换为 L 以满足 x() 方法的需要。 现在我们可以这样调用这个方法: 当然,这个技术不应该被认真对待。 在这种情况下,Integer 和 Long 是最终类型,这意味着 I 和 L 是有效的转换(几乎,转换只进行单向)。 如果我们决定使用非最终类型(例如 Object),那么我们可以使用常规泛型。 我们玩了一点就足够了。 让我们继续讨论一些真正有趣的事情。 9. 有些类型关系是不可判定的! 好吧,现在这会变得非常有趣,所以拿起一杯浓缩咖啡,让我们看看以下两种类型: 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 0) D > Step 1) Type >>> > Step 2) D >> Step 3) List >> > Step 4) D > >> Step . . . (expand forever) class Test { } // Doesn't compile Test s = null; // Compiles Test d = null; void execute(T t) {} execute((Serializable) (() -> {}));execute((Runnable & Serializable) (() -> {}));

Java 既强大又神秘。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION