那麼,您從一開始就一直在使用Java?還記得那些被稱為「 Oak」的日子,OO仍然是熱門話題,C ++人士認為Java沒有機會,Applet還是一件事嗎?
我敢打賭,您至少不了解以下一半內容。
1.沒有被檢查的異常
那就對了!JVM不知道任何這樣的事情,只有Java語言知道。
您是否想要證明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;
}
}
2.您可以使方法重載僅在返回type上有所不同
那不會編譯,對嗎?
class Test {
Object x() { return "abc"; }
String x() { return "123"; }
}
對。Java語言不允許兩個方法 在同一類中「覆蓋等效」,無論它們的throws子句或returntype可能如何不同。
哇,是的,這很有意義。實際上,這幾乎就是您編寫以下內容時發生的情況:
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 "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()可能期望籤名的返回typeObject在某些調用位置。如果沒有此類橋接方法,則無法以二進位兼容的方式添加泛型。因此,更改JVM以使其具有此功能的痛苦就較小(這也使協變量重載成為副作用……)聰明吧?
您是否熟悉語言細節和內部知識?
3.所有這些都是二維數組!
class Test {
int[][] a() { return new int[0][]; }
int[] b() [] { return new int[0][]; }
int c() [][] { return new int[0][]; }
}
對,是真的。即使您的心理分析器可能無法立即理解上述方法的返回type,它們也是相同的!與以下代碼段相似:
class Test {
int[][] a = {{}};
int[] b[] = {{}};
int c[][] = {{}};
}
你覺得這很瘋狂嗎?
@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);
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.您也沒有得到複合賦值運算符
夠古怪嗎?讓我們考慮以下兩段代碼:
憑直覺,它們應該等效,對嗎?但猜猜怎麼了。他們不是!JLS指定:
形式為E1 op = E2的複合賦值表達式等效於E1 =(T)(((E1)op(E2))),其中T是E1的type,只是E1僅被評估一次。
這是如此的美麗,我想引用彼得Lawrey的回答這個堆棧溢出問題:
這種轉換的一個很好的例子是使用* =或/ =
字節b = 10;
b * = 5.7;
System.out.println(b); //列印57
要麼
字節b = 100;
b / = 2.5;
System.out.println(b); //列印40
要麼
char ch ='0';
ch * = 1.1;
System.out.println(ch); //列印'4'
要麼
char ch ='A';
ch * = 1.5;
System.out.println(ch); //列印'a'
現在,這有多麼難以置信?我將在我的應用程式中將字符轉換/乘法。因為,你知道...
6.隨機整數
現在,這更像是一個難題。尚未閱讀解決方案。看看您是否可以自己找到這個。當我運行以下程序時:
…然後「有時」,我得到以下輸出:
92
221
45
48
236
183
39
193
33
84
7.轉到
這是我的最愛之一。Java有GOTO!輸入...
int goto = 1;
這將導致:
Test.java:44:錯誤:預期<identifier>
int goto = 1;
^
這是因為goto是一個未使用的關鍵字,以防萬一...
但這不是令人興奮的部分。令人興奮的是,你可以真正實現與藤break,continue並標記塊:
向前跳
label: {
// do stuff
if (check) break label;
// do more stuff
}
在字節碼中:
2 iload_1 [檢查]
3 ifeq 6 //向前跳
6 ..
向後跳
label: do {
// do stuff
if (check) continue label;
// do more stuff
break label;
} while(true);
在字節碼中:
2 iload_1 [檢查]
3 ifeq 9
6 goto 2 //向後跳
9 ..
8. Java具有type別名
在其他語言中(例如Ceylon),我們可以非常輕鬆地定義type別名:
interface People => Set<Person>;
People這樣構造的type可以與Set<Person>以下項互換使用:
People? p1 = null;
Set<Person>? p2 = p1;
People? p3 = p2;
在Java中,我們不能在頂級定義type別名。但是我們可以在類或方法的範圍內這樣做。假設我們對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()
);
}
}
在上述程序中,Integer「別名」 I用於Test類的範圍,而Long「別名」 L用於x()方法的範圍。然後我們可以像上面這樣調用上面的方法:
new Test().x(1, 2L);
當然,這種技術不應被認真對待。在這種情況下,Integer和Long都是最終type,這意味著typeI和L是有效的別名(幾乎,賦值兼容僅是一種方式)。如果我們使用了非最終type(例如Object),那麼我們真的會使用普通的泛型。
這些愚蠢的把戲足夠了。現在換個真正了不起的東西!
9.一些type關係是不確定的!
好吧,現在這將變得非常時髦,因此可以喝杯咖啡併集中精力。請考慮以下兩種type:
// 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>>>> {}
現在,做typeC和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> { ... }
考慮到這一點,讓我們回到兩種type。以下內容可以編譯嗎?
class Test {
Type<? super C> c = new C();
Type<? super D<Byte>> d = new D<Byte>();
}
C是Type <的子type嗎?superC>?
步驟0)C <?: Type <?superC>
步驟1)Type <Type <?super C >> <?:type(繼承)
步驟2)C(檢查通配符?superC)
步 。。。(永遠循環)
接著:
D是Type <的子type嗎?superD <Byte >>?
步驟0)D <Byte> <?: Type <?superC <Byte >>
步驟1)Type <Type <?superD <D <Byte >>>> <?:type<?superD <Byte >>
步驟2)D <Byte> <?: Type <?superD <D <Byte >>>
步驟3)Type <type <?superC <C >>> <?:type<?superC <C >>
步驟4)D <D <Byte >> <?: Type <?superD <D <Byte >>>
步 。。。(永遠擴展)
嘗試在Eclipse中編譯以上代碼,它將崩潰!
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) {}
您Runnable還Serializable希望這樣做,以防萬一您想在其他地方執行它並通過電線發送它。Lambda和序列化有點古怪。
Lambda可以序列化:
如果lambda表達式的目標類型和捕獲的參數可序列化,則可以對其進行序列化
但是,即使這是真的,他們也不會自動實現Serializable標記器接口。要強制他們使用這種類型,必須強制轉換。但是當你只投給Serializable……
execute((Serializable) (() -> {}));
…然後,lambda將不再可運行。
嗯...
所以…
將其強制轉換為兩種類型:
execute((Runnable & Serializable) (() -> {}));
最後,開發這麼多年我也總結了一套學習Java的資料與面試題,如果你在技術上面想提升自己的話,可以關注我,私信發送領取資料或者在評論區留下自己的聯繫方式,有時間記得幫我點下轉發讓跟多的人看到哦。