<dl id="3wz6h"></dl><li id="3wz6h"></li>

      1. <dl id="3wz6h"></dl>

      2. <dl id="3wz6h"><ins id="3wz6h"></ins></dl>

            <dl id="3wz6h"></dl>

            <dl id="3wz6h"><ins id="3wz6h"></ins></dl>
            1. 
              
              <output id="3wz6h"><ins id="3wz6h"><nobr id="3wz6h"></nobr></ins></output>

              <li id="3wz6h"><ins id="3wz6h"></ins></li>
              
              

            2. <output id="3wz6h"><ins id="3wz6h"><nobr id="3wz6h"></nobr></ins></output>
              首頁»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 還受到關注。

                我敢打賭,下面我要介紹的這些事,有一半你都不知道。下面讓我們來深入探索 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: 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() 簽名中的返回類型在實際調用的時候正好是 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 緩存,然后使用自動裝箱和拆箱。不要在家干這種事情!或者,我們應該換種方式進行此類操作。

                我在4周前做最后一次提交的時候

               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 中我們不能在頂層作用域定義類型別名,但是我們可以在類或方法作用域中干這個事情。假如我們不喜歡 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 List
              interface 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 實際上只是單純的語法糖:

              // 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 中試著編譯一下,它會崩潰! (不用擔心,我提交了 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 compile
              Test<String> s = null;
              
              // Compiles
              Test<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) (() -> {}));

               結論

                一句話總結這篇文章就是:

              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
              提示:更多精彩內容關注微信公眾號:全棧開發者中心(fsder-com)
              網友評論(共0條評論) 正在載入評論......
              理智評論文明上網,拒絕惡意謾罵 發表評論 / 共0條評論
              登錄會員中心
              云南十一选往期