网站开发前端与后端,wordpress部分文字管理员可见,主网站下建立子目录站,企业网站seo诊断工具转载自 关于Java你不知道的10件事 作为 Java 书呆子#xff0c;比起实用技能#xff0c;我们会对介绍 Java 和 JVM 的概念细节更感兴趣。因此我想推荐 Lukas Eder 在 jooq.org 发表的原创作品给大家。 你是从很早开始就一直使用 Java 吗#xff1f;那你还记得它的过去吗比起实用技能我们会对介绍 Java 和 JVM 的概念细节更感兴趣。因此我想推荐 Lukas Eder 在 jooq.org 发表的原创作品给大家。 你是从很早开始就一直使用 Java 吗那你还记得它的过去吗那时Java 还叫 OakOO 还是一个热门话题C 的 folk 者认为 Java 是不可能火起来Java 开发的小应用程序 Applets 还受到关注。 我敢打赌下面我要介绍的这些事有一半你都不知道。下面让我们来深入探索 Java 的神秘之处。 1没有检查异常这种事情 没错JVM 不会知道这些事情只有 Java 语句知道。 如今大家都认为检查异常是个错误。正如 Bruce Eckel 在布拉格 GeeCON 闭幕时所说Java 之后再没别的语言检查异常甚至 Java 8 在新的 Stream API 中也不再干这个事情(如果你的 Lambda 使用 IO 和 JDBC这其实还是有点痛苦)。 如何证实 JVM 并不清楚检查异常一事试试下面的代码
public class Test {// No throws clause here public static void main(String[] args) {doThrow(new SQLException());}static void doThrow(Exception e) {Test.RuntimeException doThrow0(e);}SuppressWarnings(unchecked)static E extends Exception void doThrow0(Exception e) throws E {throw (E) e;}} 这不仅可以编译通过它还可以抛出 SQLException。你甚至不需要 Lombok 的 SneakyThrows 就能办到。 这篇文章可以看到更详细的相关内容或者在 Stack Overflow 上看。 2你可以定义仅在返回值有差异的重载函数 这样的代码无法编译对不
class Test {Object x() { return abc; }String x() { return 123; }} 对。 Java 语言不允许两个方法在同一个类中“等效重载”而忽略其诸如throws自居或返回类型等的潜在的差异。 查看 Class.getMethod(String, Class…) 的 Javadoc。 其中说明如下 请注意类中可能有多个匹配方法因为 Java 语言禁止在一个类声明具有相同签名但返回类型不同的多个方法但 Java 虚拟机并不是如此。虚拟机中增加的灵活性可以用于实现各种语言特征。例如可以用桥接方法实现协变参返回; 桥接方法和被重写的方法将具有相同的签名但拥有不同的返回类型。 哇哦有道理。实际上下面的代码暗藏着很多事情
abstract class ParentT {abstract T x();
}class Child extends ParentString {Override String x() { return abc; }
} 来看看为 Child 生成的字节码
// Method descriptor #15 ()Ljava/lang/String;// Stack: 1, Locals: 1java.lang.String x();0 ldc /StringString abc [16]2 areturnLine 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: 1bridge synthetic java.lang.Object x();0 aload_0 [this]1 invokevirtual Child.x() : java.lang.String [19]4 areturnLine numbers:[pc: 0, line: 1]其实在字节码中 T 真的只是 Object。这很好理解。 合成的桥方法实际是由编译器生成的因为 Parent.x() 签名中的返回类型在实际调用的时候正好是 Object。在没有这种桥方法的情况下引入泛型将无法在二进制下兼容。因此改变 JVM 来允许这个特性所带来的痛苦会更小(副作用是允许协变凌驾于一切之上) 很聪明不是吗 你看过语言内部的细节吗不妨看看在这里会发现更多很有意思的东西。 3所有这些都是二维数组
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[][] {{}};}你认为这很疯狂想象在上面使用 JSR-308 / Java 8 类型注解 。语法的可能性指数激增
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 [] {{}};}类型注解。看起来很神秘其实并不难理解。 或者换句话说: 当我做最近一次提交的时候是在我4周的假期之前。 4条件表达式的特殊情况 可能大多数人会认为
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 由此可见三目条件运算符会在有需要的情况下对操作数进行类型提升。注意是只在有需要时才进行否则代码可能会抛出 NullPointerException 空引用异常
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); 5你还没搞懂复合赋值运算符 很奇怪吗来看看下面这两行代码
i j;
i i j;直观看来它们等价是吗但可其实它们并不等价JLS 解释如下 E1 op E2 形式的复合赋值表达式等价于 E1 (T)((E1) op (E2))这里 T 是 E1 的类型E1 只计算一次。 非常好我想引用 Peter Lawrey Stack Overflow 上的对这个问题的回答 使用 * 或 / 来进行计算的例子
byte b 10;
b * 5.7;
System.out.println(b); // prints 57或者
byte b 100;
b / 2.5;
System.out.println(b); // prints 40或者
char ch 0;
ch * 1.1;
System.out.println(ch); // prints 4 或者
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 这怎么可能 . spoiler… 继续解答… 好了答案在这里 (https://blog.jooq.org/2013/10/17/add-some-entropy-to-your-jvm/)这必须通过反射重写 JDK 的 Integer 缓存然后使用自动装箱和拆箱。不要在家干这种事情或者我们应该换种方式进行此类操作。 7GOTO 这是我的最爱之一。Java也有GOTO输入下试试……
int goto 1; 将输出
Test.java:44: error: identifier expectedint goto 1;
^这是因为goto是一个未使用的关键字, 仅仅是为了以防万一…… 但这不是最令人兴奋的部分。令人兴奋的部分是你可以使用 break、continue 和标记块来实现 goto 功能 向前跳
label: {// do stuff if (check) break label;// do more stuff
} 在字节码中格式如下
2 iload_1 [check]3 ifeq 6 // Jumping forward6 .. 向后跳
label: do {// do stuff if (check) continue label;// do more stuff break label;} while(true);在字节码中格式如下:
2 iload_1 [check]3 ifeq 96 goto 2 // Jumping backward9 .. 8Java 有类型别名 其它语言 (比如 Ceylon) 中我们很容易为类型定义别名
interface People SetPerson; 这里产生了 People 类型使用它就跟使用 SetPerson 一样
People? p1 null;Set/PersonPerson? p2 p1;People? p3 p2; Java 中我们不能在顶层作用域定义类型别名但是我们可以在类或方法作用域中干这个事情。假如我们不喜欢 Integer、Long 等等名称而是想用更简短的 I 和 L很简单
class TestI extends Integer {L extends Long void x(I i, L l) {System.out.println(i.intValue() , l.longValue());}
}在上面的程序中Test 类作用域内 Integer 被赋予 I 这样的 “别名”类似地Long 在 x() 方法中被赋予 L 这样的 “别名”。之后我们可以这样调用方法
new Test().x(1, 2L); 这种技术当然不太会受重视。这种情况下Integer 和 Long 都是 final 类型也就是说I 和 L 是事实上的别名(基本上赋值兼容性只需要考虑一种可能性)。如果我们使用非 final 类型 (比如 Object)那就是一般的泛型。 9某些类型的关系并不确定 好了这会很引人注目先来杯咖啡提提神。思考一下下面两个类型
// A helper type. You could also just use Listinterface TypeT {}
class C implements TypeType ? super C {}class DP implements
TypeType ? super DDP {} 现在告诉我类型 C 和 D 到底是什么 它们存在递归是一种类似 java.lang.Enum (但有略微不同)的递归方式。看看
public abstract class EnumE extends EnumE { ... } 在上面的描述中enum 实际上只是单纯的语法糖
// Thisenum MyEnum {}// Is really just sugar for thisclass MyEnum extends EnumMyEnum { ... }认识到这一点之后我们回过头来看看前面提到的两个类型下面的代码会编译成什么样
class Test {Type ? super C c new C();Type ? super DByte d new DByte();}非常难回答的问题不过 Ross Tate 已经回答了。这个问题的答案是不可判定的 C 是 Type? super C 的子类?
Step 0) C ?: Type? super CStep 1) TypeType? super C ?: Type (inheritance)Step 2) C 聽(checking wildcard ? super C)Step . . . (cycle forever)然后 D 是 Type? super DByte 的子类?
Step 0) DByte ?: Type? super CByteStep 1) TypeType? super DDByte ?: Type? super DByteStep 2) DByte ?: Type? super DDByteStep 3) TypeType? super CC ?: Type? super CCStep 4) DDByte ?: Type? super DDByteStep . . . (expand forever)在 Eclipse 中试着编译一下它会崩溃 (不用担心我提交了 BUG 报告) 让这个事情沉下去… Java 中某些类型的关系是不明确的 如果你对 Java 这个用法感到奇怪之余也感兴趣就去看看 Ross Tate 写的 “在 Java 的类型系统中使用通配符” (与 Alan Leung 和 Sorin Lerner 合著)我们也在讨论泛型多态中的相关子类多态性。 10类型交集 Java 有一个非常奇怪的特性叫类型交集。你可以申明某个(泛型)类型而它实际上是两个类型的交集比如
class TestT extends Serializable Cloneable {} 绑定到 Test 类型实例的泛型类型参数 T 必须实现 Serializable 和 Cloneable。比如String 就不符合要求但 Dete 满足
// Doesnt compileTestString s null;// CompilesTestDate d null; 这个特性已经在 Java 8 中使用。这很有用吗几乎没用但是如果你希望某个 Lambda 表达式是这种类型还真没别的办法。假设你的方法有这种疯狂的类型约束
T extends Runnable Serializable void execute(T t) {} 你想通过执行它得到一个可以序列化 (Serializable) 的 Runnable 对象。Lambda 和序列化也有点奇怪。 Lambda 可以序列经 如果 Lambda 的目标类型和参数类型都可以序列化那么你可以序列化这个 Lambda 但是即使是这样他们都不能自动实现 Serializable 标记接口。你必须强制转换类型。但是当你只扔给 Serializable 时…...
execute((Serializable) (() - {})); … 那么 lambda 将不再是 Runnable 的。 因此要把它转换为两种类型
execute((Runnable Serializable) (() - {}));