关于Java你不知道的10件事

转载自 关于Java你不知道的10件事

作为 Java 书呆子,比起实用技能,我们会对介绍 Java 和 JVM 的概念细节更感兴趣。因此我想推荐 Lukas Eder 在 jooq.org 发表的原创作品给大家。

你是从很早开始就一直使用 Java 吗?那你还记得它的过去吗?那时,Java 还叫 Oak,OO 还是一个热门话题,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 Parent<T> {abstract T x();
}class Child extends Parent<String> {@Override    String x() { return "abc"; }
}

来看看为 Child 生成的字节码:

// Method descriptor #15 ()Ljava/lang/String;// Stack: 1, Locals: 1java.lang.String x();0  ldc </String><String "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 => Set<Person>;

这里产生了 People 类型,使用它就跟使用 Set<Person> 一样:

People?      p1 = null;Set</Person><Person>? p2 = p1;People?      p3 = p2;

Java 中我们不能在顶层作用域定义类型别名,但是我们可以在类或方法作用域中干这个事情。假如我们不喜欢 Integer、Long 等等名称,而是想用更简短的 I 和 L,很简单:

class Test<I 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 Type<T> {}
class C implements Type<Type <? super C>> {}class D<P> implements 
Type<Type <? super D<D<P>>>> {}

现在告诉我,类型 C 和 D 到底是什么?

它们存在递归,是一种类似 java.lang.Enum (但有略微不同)的递归方式。看看:

public abstract class Enum<E extends Enum<E>> { ... }

在上面的描述中,enum 实际上只是单纯的语法糖:

// Thisenum MyEnum {}// Is really just sugar for thisclass MyEnum extends Enum<MyEnum> { ... }

认识到这一点之后我们回过头来看看前面提到的两个类型,下面的代码会编译成什么样?

class Test {Type< ? super C> c = new C();Type< ? super D<Byte>> d = new D<Byte>();}

非常难回答的问题,不过 Ross Tate 已经回答了。这个问题的答案是不可判定的:

C 是 Type<? super C> 的子类?

Step 0) C <?: Type<? super C>Step 1) Type<Type<? super C>> <?: Type (inheritance)Step 2) C 聽(checking wildcard ? super C)Step . . . (cycle forever)

然后:

D 是 Type<? super D<Byte>> 的子类?

Step 0) D<Byte> <?: Type<? super C<Byte>>Step 1) Type<Type<? super D<D<Byte>>>> <?: Type<? super D<Byte>>Step 2) D<Byte> <?: Type<? super D<D<Byte>>>Step 3) Type<Type<? super C<C>>> <?: Type<? super C<C>>Step 4) D<D<Byte>> <?: Type<? super D<D<Byte>>>Step . . . (expand forever)

在 Eclipse 中试着编译一下,它会崩溃! (不用担心,我提交了 BUG 报告)

让这个事情沉下去…

Java 中某些类型的关系是不明确的!

如果你对 Java 这个用法感到奇怪之余也感兴趣,就去看看 Ross Tate 写的 “在 Java 的类型系统中使用通配符” (与 Alan Leung 和 Sorin Lerner 合著),我们也在讨论泛型多态中的相关子类多态性。

10类型交集


Java 有一个非常奇怪的特性叫类型交集。你可以申明某个(泛型)类型,而它实际上是两个类型的交集,比如:

class Test<T extends Serializable & Cloneable> {}

绑定到 Test 类型实例的泛型类型参数 T 必须实现 Serializable 和 Cloneable。比如,String 就不符合要求,但 Dete 满足:

// Doesn't compileTest<String> s = null;// CompilesTest<Date> 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) (() -> {}));


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/330851.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

模型评估与选择 ( Bias(偏差),Error(误差),和Variance(方差) )

转自&#xff1a; https://github.com/familyld/Machine_Learning/blob/master/02model_evaluation_and_model_selection.md 机器学习中的Bias(偏差)&#xff0c;Error(误差)&#xff0c;和Variance(方差)有什么区别和联系&#xff1f; 参见 https://www.zhihu.com/question…

单列集合Set的实现类HashSet

Set接口 [Collection】的子类 HashSet 特点【无序&#xff0c;不可重复,不能排序】 默认比较地址值【地址相同的值相同】&#xff0c;重写后可比较内容【内容相同的值相同】 1.比较地址值【默认】 public class Demo1 {public static void main(String[] args) {HashSet<…

双列集合Map的实现类

Map接口【和Collection接口并列】 Map接口 成员方法【实现于Map接口&#xff0c;TreeMap也可实现&#xff0c;这里以HashMap为例】 //HashMap实现类 :无序[HashSet底存原理] 哈希表 public class Demo1 {public static void main(String[] args) {HashMap<String, Intege…

机器学习指标大汇总

转自&#xff1a; http://www.36dsj.com/archives/42271 作者&#xff1a;无影随想 在使用机器学习算法的过程中&#xff0c;针对不同场景需要不同的评价指标&#xff0c;在这里对常用的指标进行一个简单的汇总。 一、分类 1. 精确率与召回率 精确率与召回率多用于二分类问题。…

到底什么是分布式系统

转载自 到底什么是分布式系统分布式系统背景 说分布式系统必须要说集中式系统&#xff0c;集中式系统中整个项目就是一个独立的应用&#xff0c;整个应用也就是整个项目&#xff0c;所有的东西都在一个应用里面。 如下图所示如一个网站就是一个应用&#xff0c;最后是多个增加多…

Map集合相关应用

1.键盘录入一个字符串&#xff0c;求该字符串中每一个字符出现的次数。 要求&#xff1a;按照字母顺序打印 如: 录入的字符串为"apple"&#xff0c;打印 a(1) e(1) l(1) p(2) public class Demo4 {public static void main(String[] args) {//键盘录入Scanner sc n…

机器学习算法常用指标总结

转自&#xff1a; http://www.cnblogs.com/maybe2030/p/5375175.html#_label2 阅读目录 1. TPR、FPR&TNR 2. 精确率Precision、召回率Recall和F1值 3. 综合评价指标F-measure 4. ROC曲线和AUC 5. 参考内容 考虑一个二分问题&#xff0c;即将实例分成正类&#xff08;positi…

SLA服务可用性4个9是什么意思?怎么达到?

转载自 SLA服务可用性4个9是什么意思&#xff1f;怎么达到&#xff1f;SLA&#xff1a;服务等级协议&#xff08;简称&#xff1a;SLA&#xff0c;全称&#xff1a;service level agreement&#xff09;。是在一定开销下为保障服务的性能和可用性&#xff0c;服务提供商与用户间…

ROC和AUC介绍以及如何计算AUC

转自&#xff1a; http://alexkong.net/2013/06/introduction-to-auc-and-roc/ ROC&#xff08;Receiver Operating Characteristic&#xff09;曲线和AUC常被用来评价一个二值分类器&#xff08;binary classifier&#xff09;的优劣&#xff0c;对两者的简单介绍见这里。这篇…

为什么Netty这么火?与Mina相比有什么优势?

转载自 为什么Netty这么火&#xff1f;与Mina相比有什么优势&#xff1f;Netty是什么&#xff1f;为什么这么火&#xff1f; Netty是目前最流行的由JBOSS提供的一个Java开源框架NIO框架&#xff0c;Netty提供异步的、事件驱动的网络应用程序框架和工具&#xff0c;用以快速开发…

一张图告诉你为什么是服务网关

转载自 一张图告诉你为什么是服务网关&#xff0c;文末有现金抽奖。网关服务是单一访问点&#xff0c;并充当多项服务的代理。服务网关启用了跨所有服务的路由转发、过滤和公共处理等。在微服务实践中远不止这点功能&#xff0c;它可以做到统一接入、流量管控、安全防护、业务隔…

offer复习日志

&#xff08;1&#xff09;复习mysql&#xff0c;只需要 2.5 个小时&#xff1b;

File类对文件的操作应用

1.在不存在的文件夹下创建文件 //在当前模块下aaa文件下ddd下eee中创建一个e.txt文件 public class Demo2 {public static void main(String[] args) throws IOException {File file new File("day11_myFile\\aaa\\ddd\\eee");//createNewFile()建立文件需要文件夹…

一张图告诉你什么是系统架构师

转载自 一张图告诉你什么是系统架构师这张图从架构师的综合能力、岗位认识、岗位职责等方面&#xff0c;清楚的画出了作为一个架构的基本准则。人人都想成为架构师&#xff0c;可作为架构你达到了上面的要求了吗&#xff1f; 系统架构师是个神奇的岗位。为什么这么说&#xff0…

机器学习和统计里面的auc怎么理解?

转自&#xff1a; https://www.zhihu.com/question/39840928 作者&#xff1a;小小丘 &#xff08;该作者对 auc的意义 讲得非常棒, 感谢付出&#xff09; 链接&#xff1a;https://www.zhihu.com/question/39840928/answer/84906286 来源&#xff1a;知乎 著作权归作者所有。…

IO流总述

IO流分类 1)按照流向分类&#xff1a; 输入流&#xff1a;读取数据用的 输出流&#xff1a;写数据用的 2)按照读写的数据分类(体系结构) <1>字节流[读写任何类型的文件(任何文件底层都是字节数据)] 字节输入流&#xff1a;IntputStream(抽象类) –FileInuptStream: 读取…

分布式ID生成器的解决方案总结

转载自 分布式ID生成器的解决方案总结在互联网的业务系统中&#xff0c;涉及到各种各样的ID&#xff0c;如在支付系统中就会有支付ID、退款ID等。那一般生成ID都有哪些解决方案呢&#xff1f;特别是在复杂的分布式系统业务场景中&#xff0c;我们应该采用哪种适合自己的解决方案…

IO流相关应用

1.1.字节流复制文件 //运用字节输入输出流将a.txt文件复制到b.txt中 public class Demo1 {public static void main(String[] args) throws IOException {//创建输入流对象FileInputStream fis new FileInputStream("day11_myIO\\a.txt");//可以使用BufferedInputS…

回归模型和分类模型的区别

转自&#xff1a; https://www.zhihu.com/question/21329754 分类和回归的区别在于输出变量的类型。 定量输出称为回归&#xff0c;或者说是连续变量预测&#xff1b; 定性输出称为分类&#xff0c;或者说是离散变量预测。 举个例子&#xff1a; 预测明天的气温是多少度&…

java计算混淆矩阵(分类指标:查准率P,查全率R,P和R的调和均值F1,正确率A)

【0】README 本文使用 java 计算混淆矩阵&#xff0c;并利用 混淆矩阵值计算 分类指标&#xff1b;通用分类指标有&#xff1a; 查准率&#xff0c;查全率&#xff0c;查准率和查全率的调和均值F1值&#xff0c;正确率&#xff0c; AOC&#xff0c; AUC等&#xff1b;本文计算…