关于Java的十件事
那么,您從一開始就一直在使用Java? 還記得曾經被稱為“ Oak”的日子,OO仍然是熱門話題,C ++人士認為Java沒有機會,Applet還是一件事嗎?
我敢打賭,您至少不了解以下一半內容。 讓我們從本周開始,對Java的內部運作產生一些驚喜。
1.沒有被檢查的異常
那就對了! JVM不知道任何這樣的事情,只有Java語言知道。
今天,每個人都同意檢查異常是一個錯誤。 正如Bruce Eckel在布拉格GeeCON閉幕致辭中所說的那樣,在Java參與使用受檢查的異常之后,沒有其他語言可以使用,甚至Java 8也不再將它們包含在新的Streams API中( 實際上可能有點麻煩,當您的lambda使用IO或JDBC時 )。
您是否想要證明JVM不知道這樣的事情? 嘗試以下代碼:
public class Test {// No throws clause herepublic 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子句或return類型可能如何不同。
但是請稍等。 Class.getMethod(String, Class...)出Class.getMethod(String, Class...)的Javadoc。 內容為:
請注意,一個類中可能有多個匹配方法,因為Java語言禁止一個類聲明具有相同簽名但返回類型不同的多個方法,而Java虛擬機卻沒有。 虛擬機中這種增加的靈活性可用于實現各種語言功能。 例如,協變收益可以通過過渡方法來實現。 bridge方法和被重寫的方法將具有相同的簽名,但返回類型不同。
哇,是的,這很有意義。 實際上,這幾乎就是您編寫以下內容時發生的情況:
abstract class Parent<T> {abstract T x(); }class Child extends Parent<String> {@OverrideString x() { return "abc"; } }在Child檢查生成的字節碼:
// Method descriptor #15 ()Ljava/lang/String;// Stack: 1, Locals: 1java.lang.String x();0 ldc <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.您沒有條件表達式
因此,您認為使用條件表達式時就知道這一切嗎? 我告訴你,你沒有。 你們大多數人都認為以下兩個片段是等效的:
Object o1 = true ? new Integer(1) : new Double(2.0);…一樣嗎?
Object o2;if (true)o2 = new Integer(1); elseo2 = 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這怎么可能呢??
。
。
。
。
。
。 擾流板...未來的解決方案...
。
。
。
。
。
好的,解決方案就在這里 ,它與通過反射覆蓋JDK的Integer緩存有關,然后使用自動裝箱和自動拆箱。 不要在家做! 換句話說,讓我們再這樣考慮一下
當我這樣做時,我的四周假期前的最后一次提交
7.轉到
這是我的最愛之一。 Java有GOTO! 輸入...
int goto = 1;這將導致:
Test.java:44: error: <identifier> expectedint goto = 1;^這是因為goto是未使用的關鍵字 ,以防萬一……
但這不是令人興奮的部分。 令人興奮的部分是,您實際上可以使用break , continue和帶標簽的塊來實現goto:
向前跳
label: {// do stuffif (check) break label;// do more stuff }在字節碼中:
2 iload_1 [check] 3 ifeq 6 // Jumping forward 6 ..向后跳
label: do {// do stuffif (check) continue label;// do more stuffbreak label; } while(true);在字節碼中:
2 iload_1 [check]3 ifeq 96 goto 2 // Jumping backward9 ..8. Java具有類型別名
在其他語言中( 例如Ceylon ),我們可以非常輕松地定義類型別名:
interface People => Set<Person>;這樣構造的People類型可以與Set<Person>互換使用:
People? p1 = null; Set<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 ,而對于x()方法的范圍, Long被“別名”到L 然后我們可以像上面這樣調用上面的方法:
new Test().x(1, 2L);當然,這種技術不應被認真對待。 在這種情況下, Integer和Long都是最終類型,這意味著類型I和L 實際上是別名(幾乎,賦值兼容只是一種方式)。 如果我們使用了非最終類型(例如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 <的子類型嗎? 超級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 <的子類型嗎? 超級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) List<List<? super C<C>>> <?: List<? super C<C>> Step 4) D<D<Byte>> <?: Type<? super D<D<Byte>>> Step . . . (expand forever)嘗試在Eclipse中編譯以上內容,它將崩潰! ( 不用擔心。我已經提交了一個錯誤 )
讓它沉入……
Java中的某些類型關系是不確定的 !
如果您對有關此奇特的Java怪癖的更多細節感興趣,請閱讀Ross Tate的論文“在Java的類型系統中使用通配符” (與Alan Leung和Sorin Lerner合著),或者閱讀我們自己的有關將子類型多態與泛型多態相關聯的想法。
10.類型交點
Java具有一個非常獨特的功能,稱為類型交集。 您可以聲明一個(通用)類型,它實際上是兩種類型的交集。 例如:
class Test<T extends Serializable & Cloneable> { }泛型類型參數T ,你綁定的類的實例Test必須實現 Serializable和Cloneable 。 例如, String不是可能的綁定,但Date是:
// 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表達式的目標類型和捕獲的參數可序列化,則可以對其進行序列化
但是,即使這是真的,他們也不會自動實現Serializable標記接口。 要強制他們使用這種類型,必須強制轉換。 但是,當您僅轉換為Serializable ...
execute((Serializable) (() -> {}));…然后,lambda將不再可運行。
嗯...
所以…
將其強制轉換為兩種類型:
execute((Runnable & Serializable) (() -> {}));結論
我通常只說這是關于SQL的問題,但現在該是結束這篇文章的時候了:
Java是僅憑其強大功能就無法解開謎底的設備。
翻譯自: https://www.javacodegeeks.com/2014/11/10-things-you-didnt-know-about-java.html
總結
以上是生活随笔為你收集整理的关于Java的十件事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Arquillian测试安全的EJB
- 下一篇: 掌上大学天翼电脑版(掌上大学天翼校园)