<sub id="rrh1b"><menuitem id="rrh1b"></menuitem></sub>

    <th id="rrh1b"><address id="rrh1b"><dfn id="rrh1b"></dfn></address></th><th id="rrh1b"></th>

    <th id="rrh1b"></th>

    <sub id="rrh1b"></sub>

        <sub id="rrh1b"><meter id="rrh1b"></meter></sub>
        <address id="rrh1b"><menuitem id="rrh1b"><font id="rrh1b"></font></menuitem></address>

          首页»Java WEB»关于 Java 你不知道的 10 件事

          关于 Java 你不知道的 10 件事

          来源:oschina 发布时间:2017-05-06 阅读次数:

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

            你是从很早开始就一直使用 Java 吗?那你还记得它的过去吗?那时,Java 还叫 Oak,OO 还是一个热门话题,C++ 的 folk 者认为 Java 是不可能火起来,Java 开发的小应用程序 Applets 还受到关注。

            我敢打?#27169;?#19979;面我要介绍的这些事,有一半你都不知道。下面让我们来深入探索 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 语言不允许两个方法在同一个类中“等效重载”,而忽?#20113;?#35832;如throws自居或返回类型等的潜在的差异。

            查看 Class.getMethod(String, Class...) 的 Javadoc。 其?#20852;得?#22914;下:

          请注意,类中可能有多个匹配方法,因为 Java 语言禁止在一个类声明具有相同签名但返回类型不同的多个方法,但 Java 虚拟机并不是如此。虚拟机中增加的灵活性可以用于实?#25351;?#31181;语言特征。例如,可以用桥接方法实?#20013;?#21464;参返回; 桥接方法和被重写的方法将具有相同的签名但拥有不同的返回类型。

            哇哦,有道理。实际上下面的代码暗藏着很多事情:

          abstract class Parent<T> {
              abstract T x();
          }
          
          class Child extends Parent<String> {
              @Override
              String x() { return "abc"; }
          }

            来看?#27425;?nbsp;Child 生成的字节码:

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

            其实在字节码中 T 真的只是 Object。这很好理解。

            合成的桥方法实际是由编译器生成的,因为 Parent.x() 签名中的返回类型在实?#23454;?#29992;的时候正好是 Object。在没有这种桥方法的情况下引入泛型将无法在二进制?#24405;?#23481;。因此,改变 JVM 来允许这个特?#36816;?#24102;来的痛苦会更小(副作用是允许协变凌驾于一切之上) 很聪明,不是吗?

            你看过语言内部的细节吗?不妨看看,在这里会发?#25351;?#22810;很有意思的东西

           3. 所有这些都?#23884;?#32500;数组!

          class Test {
              int[][] a()  { return new int[0][]; }
              int[] b() [] { return new int[0][]; }
              int c() [][] { return new int[0][]; }
          }

            是的,这是真的。即使你的大脑解析器不能立刻理解上面方法的返回类型,但其实他?#23884;?#26159;一样的!类似的还有下面这些代码片段:

          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周的假期之前。

            对你来说,上面的内容在你的实际使用中?#19994;?#20102;吧。

           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

            由此可见,三?#21051;?#20214;运算符会在有需要的情况下,对操作数进行类型提升。注意,是只在有需要时才进行;否则,代码可能会抛出 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. 你还没搞懂复合赋值运算符

            很奇怪吗?来看看下面这?#21483;写?#30721;:

          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. 随机整数

            现在有一个更难的谜题。不要去?#21019;?#26696;,看看你能不能自己?#19994;?#31572;案。如果运行下面的程序:

          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 缓存,然后使用自动装箱和拆箱。不要在家干这种事情!或者,我们应?#27809;?#31181;方式进?#20889;?#31867;操作。

            ?#20197;?周前做最后一次提交的时候

           7. GOTO

            这是我的最爱之一。Java也有GOTO!输入下试试……

          int goto = 1;

            将输出:

          Test.java:44: error: <identifier> expected
          int goto = 1;
          ^

            这是因为goto是一个未使用的关键字, 仅仅是为了以防万一……

            但这不是最令人兴奋的部分。令人兴奋的部分是你可以使用 break、continue 和标记块来实现 goto 功能:

            向前跳:

          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  ..

           8. Java 有类型别名

            其它语言 (比如 Ceylon) 中,我们很容易为类型定义别名:

          interface People => Set<Person>;

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

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

            Java 中我们不能在顶层作用域定义类型别名,但是我们可以在类或方法作用域中干这个事情。假如我们不?#19981;?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);

            这种技术?#27604;?#19981;太会受重视。这种情况下,Integer 和 Long 都是 final 类型,也就是说,I 和 L 是事实上的别名(基本?#32454;?#20540;兼容性只需要考虑一种可能性)。如果我们使用非 final 类型 (比如 Object),那就是一般的泛型。

            这些把戏已经玩够了。现在来看看真正了不起的东西!

           9. 某些类型的关系并不确定!

            好了,这会很引人注目,先来杯咖啡提提神。思?#23478;?#19979;下面两个类型:

          // A helper type. You could also just use List
          interface Type<T> {}
          
          class C implements Type<Type <? super C>> {}
          class D<P> implements Type<Type <? super D<D<P>>>> {}

            现在告诉我,类型 C 和 D 到?#36164;?#20160;么?

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

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

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

          // This
          enum MyEnum {}
          
          // Is really just sugar for this
          class 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 中试着编译一下,它会?#35272;#?(不用担?#27169;?#25105;提交了 BUG 报告)

            让这个事情沉下去…

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

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

           10. 类型交集

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

          class Test<T extends Serializable & Cloneable> {
          }

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

          // Doesn't compile
          Test<String> s = null;
          
          // Compiles
          Test<Date> d = null;

            这个特性已经在 Java 8 中使用。这很有用吗?#32771;?#20046;没用,但是如果你希望某个 Lambda 表达式是这种类型,还真没别的办法。假设你的方法有这种疯狂的类型?#38469;?/p>

          <T extends Runnable & Serializable> void execute(T t) {}

            你想通过执行它得到一个可以序列化 (Serializable) 的 Runnable 对象。Lambda 和序列化也有点奇怪。

            Lambda 可以序列经

          如果 Lambda 的目标类型和参数类型都可以序列化,那么你可以序列化这个 Lambda

            但是即使是这样,他?#23884;?#19981;能自动实现 Serializable 标记接口。你必须强制转换类型。但是当你只扔给 Serializable 时...

          execute((Serializable) (() -> {}));

            ... 那么 lambda 将不再是 Runnable 的。

            因此要把它转换为两种类型:

          execute((Runnable & Serializable) (() -> {}));

           结论

            一句话总结这篇文章就是:

          Java 恰好是一种看起来神秘的语言,其实不然。

            本文地址:https://www.oschina.net/translate/10-things-you-didnt-know-about-java

            原文地址:https://www.sitepoint.com/10-things-you-didnt-know-about-java/

          QQ群:WEB开发者官方群(515171538),验证消息:10000
          微信群:加小编微信 849023636 邀请您加入,验证消息:10000
          提示:更多精彩内容关注微信公众号:全栈开发者中?#27169;╢sder-com)
          网友评论(共0条评论) 正在载入评论......
          理智评论文明上网,拒绝恶意谩骂 发表评论 / 共0条评论
          登录会员中心
          彩运

            <sub id="rrh1b"><menuitem id="rrh1b"></menuitem></sub>

            <th id="rrh1b"><address id="rrh1b"><dfn id="rrh1b"></dfn></address></th><th id="rrh1b"></th>

            <th id="rrh1b"></th>

            <sub id="rrh1b"></sub>

                <sub id="rrh1b"><meter id="rrh1b"></meter></sub>
                <address id="rrh1b"><menuitem id="rrh1b"><font id="rrh1b"></font></menuitem></address>

                    <sub id="rrh1b"><menuitem id="rrh1b"></menuitem></sub>

                    <th id="rrh1b"><address id="rrh1b"><dfn id="rrh1b"></dfn></address></th><th id="rrh1b"></th>

                    <th id="rrh1b"></th>

                    <sub id="rrh1b"></sub>

                        <sub id="rrh1b"><meter id="rrh1b"></meter></sub>
                        <address id="rrh1b"><menuitem id="rrh1b"><font id="rrh1b"></font></menuitem></address>

                          养鸡赚钱吗 彩票合买会不会陪钱 大乐透周六走势图行 河南快3最新走势图 双色球杀尾数大师软件 电子游戏产业 澳门21点游戏规则 网球比分结果 湖南快乐十分动物彩票 广东时时彩开奖信息 内蒙古快三一定牛彩票预测 香港两码中特马 云南快乐十分开奖今天前三组 河北快三购买 内蒙古时时彩一定牛