public class Test { // No throws clause here public static void main(String[] args) { doThrow(new SQLException()); } static void doThrow(Exception e) { Test.
doThrow0(e); } @SuppressWarnings("unchecked") static
void doThrow0(Exception e) throws E { throw (E) e; } }
class Test { Object x() { return "abc"; } String x() { return "123"; } }
ขวา. ภาษา Java ไม่อนุญาตให้มีการแทนที่สองวิธีอย่างเท่าเทียมกันภายในคลาสเดียวกันในเวลาเดียวกัน โดยไม่คำนึงถึงความแตกต่างในการส่งหรือส่งคืนประเภท แต่รอสักครู่ ตรวจสอบเอกสารประกอบอีกครั้งสำหรับ Class.getMethod(String, Class…) มันบอกว่า:
โปรดทราบว่าเป็นไปได้ที่จะมีวิธีการที่สอดคล้องกันมากกว่าหนึ่งวิธีในคลาส เพราะแม้ว่าภาษา Java จะห้ามไม่ให้มีหลายวิธีที่มีลายเซ็นเดียวกัน แต่มีประเภทการส่งคืนต่างกัน แต่ Java Virtual Machine ไม่อนุญาต ความยืดหยุ่นในเครื่องเสมือนนี้สามารถใช้เพื่อปรับใช้คุณลักษณะภาษาต่างๆ ตัวอย่างเช่น การส่งคืนความแปรปรวนร่วมสามารถทำได้ด้วยวิธีบริดจ์ วิธีบริดจ์และวิธีการแทนที่จะมีลายเซ็นเดียวกัน แต่มีประเภทการส่งคืนต่างกัน ว้าวนั่นสมเหตุสมผลแล้ว จริงๆ แล้วมีหลายสิ่งเกิดขึ้นมากมายเมื่อคุณเขียนสิ่งต่อไปนี้: ดูที่รหัสไบต์ที่สร้างขึ้น: ดังนั้น t จึงเป็นวัตถุในรหัสไบต์ เรื่องนี้เป็นที่เข้าใจกันดี จริง ๆ แล้วเมธอดสะพานสังเคราะห์ถูกสร้างขึ้นโดยคอมไพลเลอร์เนื่องจากสามารถคาดหวังชนิดการส่งคืนของ Parent.x() ในบางส่วนของการโทร การเพิ่มยาชื่อสามัญโดยไม่มีวิธีบริดจ์ดังกล่าวจะไม่สามารถทำได้อีกต่อไปในการแทนค่าไบนารี่ ดังนั้นการเปลี่ยน JVM เพื่อให้ฟีเจอร์ดังกล่าวสร้างความเจ็บปวดน้อยลง (ซึ่งช่วยให้วิธี covariant เอาชนะเป็นผลข้างเคียงได้ ... ) ฉลาดใช่ไหม?
3. ทั้งหมดต่อไปนี้เป็นอาร์เรย์สองมิติ นี่เป็นเรื่องจริง แม้ว่าเครื่องวิเคราะห์ทางจิตของคุณจะไม่สามารถเข้าใจประเภทการส่งคืนจากวิธีที่อธิบายไว้ข้างต้นได้ในทันที แต่ก็เหมือนกันหมด! เช่นเดียวกับโค้ดชิ้นถัดไป คุณคิดว่ามันบ้าเหรอ? โอกาสในการเขียนมากมายยังทำให้จินตนาการระเบิดอีกด้วย!
พิมพ์คำอธิบายประกอบ
อุปกรณ์ที่มีความลึกลับเป็นรองจากพลังของมันเท่านั้น หรืออีกนัยหนึ่ง: เมื่อฉันตัดสินใจครั้งสุดท้ายก่อนวันหยุด 4 สัปดาห์ ฉันอนุญาตให้คุณใช้มันในแบบที่คุณต้องการ
4. คุณจะไม่ได้รับ Conditional ดังนั้น คุณคิดว่าคุณรู้ทุกอย่างเกี่ยวกับ Conditional แล้วเมื่อคุณเริ่มใช้มันใช่หรือไม่? ให้ฉันทำให้คุณผิดหวัง - คุณคิดผิด พวกคุณส่วนใหญ่จะคิดว่าสองตัวอย่างต่อไปนี้เทียบเท่ากัน: เทียบเท่ากับสิ่งนี้? เลขที่ ลองใช้การทดสอบด่วน โปรแกรมจะแสดงผลดังนี้: ใช่! ตัวดำเนินการแบบมีเงื่อนไขจะดำเนินการพิมพ์แบบหล่อหากจำเป็น เพราะไม่อย่างนั้นคุณคงคาดหวังว่าโปรแกรมจะส่ง NullPointerException ใช่หรือไม่
5. คุณจะไม่ได้รับตัวดำเนินการมอบหมายแบบผสม ความฉลาดเพียงพอหรือไม่? ลองดูตัวอย่างโค้ดสองรายการต่อไปนี้:
abstract class Parent
{ abstract T x(); } class Child extends Parent
{ @Override String x() { return "abc"; } }
// Method descriptor #15 ()Ljava/lang/String; // Stack: 1, Locals: 1 java.lang.String x(); 0 ldc
[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]
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[][] = {{}}; }
@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 [] = {{}}; }
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
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);
i += j; i = i + j;
โดยสัญชาตญาณพวกเขาควรจะเท่าเทียมกันใช่ไหม? แต่คุณรู้อะไรไหม - พวกมันแตกต่างออกไป ข้อกำหนด JLS ระบุว่า:
นิพจน์ผสมประเภท E1 op = E2 เทียบเท่ากับ E1 = (T) ((E1) op (E2)) โดยที่ T คือประเภทของ E1 ยกเว้นว่า E1 จะได้รับการประเมินเพียงครั้งเดียว ตัวอย่างที่ดีคือการใช้ *= หรือ /= :
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
แต่สิ่งนี้เป็นไปได้อย่างไร? ตกลง คำตอบอยู่ที่การแทนที่แคช Integer ของ JDK ผ่านการสะท้อนกลับ จากนั้นใช้ auto-boxing และ auto-unboxing อย่าทำสิ่งนี้โดยไม่ได้รับอนุญาตจากผู้ใหญ่! หรืออีกนัยหนึ่ง:
7. GOTO หนึ่งในรายการโปรดของฉัน Java มี GOTO! เขียนสิ่งนี้:
int goto = 1;
แล้วคุณจะได้สิ่งนี้ นั่นเป็นเพราะว่า goto เป็นคำสงวนที่ไม่ได้ใช้ เผื่อไว้... แต่นั่นไม่ใช่ส่วนที่น่าตื่นเต้น สิ่งที่ยอดเยี่ยมคือคุณสามารถรวม goto จับคู่กับตัวแบ่ง ดำเนินการต่อ และทำเครื่องหมายบล็อกได้: การกระโดดไปข้างหน้า ในโค้ดไบต์: การกระโดดไปข้างหลัง ในโค้ดไบต์:
8. Java มีนามแฝงประเภท ในภาษาอื่น ๆ (เช่น Ceylon) เราสามารถกำหนดประเภทได้ นามแฝงง่ายมาก: คลาส People ที่นี่ถูกสร้างขึ้นในลักษณะที่สามารถแลกเปลี่ยนกับชุดได้
Test.java:44: error:
expected int goto = 1; ^
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 ..
interface People => Set
;
People? p1 = null; Set
? p2 = p1; People? p3 = p2;
class Test {
void x(I i, L l) { System.out.println( i.intValue() + ", " + l.longValue() ); } }
new Test().x(1, 2L);
// A helper type. You could also just use List interface Type
{} class C implements Type
> {} class D
implements Type
แล้ว C และ D หมายถึงอะไร? ในแง่หนึ่งเป็นการเรียกซ้ำ คล้ายกับการเรียกซ้ำใน java.lang.Enum ลองพิจารณา: เมื่อพิจารณาจากข้อกำหนดข้างต้น การใช้งานจริงของ enum เป็นเพียงการใช้วากยสัมพันธ์เท่านั้น: ด้วยเหตุนี้ เราจึงกลับมาที่ประเภททั้งสองของเรากัน รหัสต่อไปนี้จะคอมไพล์หรือไม่ คำถามที่ยาก...และแก้ไขไม่ได้จริงหรือ? C เป็นประเภทย่อยของ Type หรือไม่? ลองรวบรวมสิ่งนี้ใน Eclipse หรือ Idea ของคุณ แล้วพวกเขาจะบอกคุณว่าพวกเขาคิดอย่างไร ล้างมันลงในท่อระบายน้ำ... ความสัมพันธ์บางประเภทใน Java ไม่สามารถตัดสินใจได้! 10. Type Intersection Java มีคุณสมบัติที่น่าสนใจมากที่เรียกว่า type intersection คุณสามารถประกาศประเภท (ทั่วไป) ที่เป็นจุดตัดของสองประเภทได้ ตัวอย่างเช่น: พารามิเตอร์ประเภทกำหนดเอง T ที่คุณเชื่อมโยงกับอินสแตนซ์ของคลาสการทดสอบจะต้องมีทั้งอินเทอร์เฟซแบบอนุกรมและแบบโคลนได้ ตัวอย่างเช่น ไม่สามารถจำกัดสตริงได้ แต่วันที่สามารถ: คุณลักษณะนี้มีประโยชน์หลายอย่างใน Java8 ซึ่งคุณสามารถส่งประเภทต่างๆ ได้ สิ่งนี้ช่วยได้อย่างไร? แทบจะไม่มีอะไรเลย แต่ถ้าคุณต้องการแปลงนิพจน์แลมบ์ดาให้เป็นประเภทที่คุณต้องการ ไม่มีทางอื่นอีกแล้ว สมมติว่าคุณมีข้อ จำกัด บ้า ๆ ในวิธีการของคุณ: คุณต้องการ Runnable ซึ่งในเวลาเดียวกันก็สามารถทำให้เป็นอนุกรมได้เฉพาะในกรณีที่คุณต้องการดำเนินการในที่อื่นและส่งผลลัพธ์ผ่านเครือข่าย แลมบ์ดาและการทำให้เป็นอนุกรมเพิ่มความประชดเล็กน้อย คุณสามารถทำให้นิพจน์แลมบ์ดาของคุณเป็นอนุกรมได้ หากประเภทเป้าหมายและอาร์กิวเมนต์ของนิพจน์นั้นทำให้เป็นอนุกรมได้ แม้ว่าสิ่งนี้จะเป็นจริง แต่ก็ไม่ได้เปิดใช้งานอินเทอร์เฟซแบบอนุกรมโดยอัตโนมัติ คุณต้องพาพวกเขามาประเภทนี้ด้วยตัวเอง แต่เมื่อคุณส่งไปที่ Serializable เท่านั้น: จากนั้น lambda จะไม่สามารถ Runnable ได้อีกต่อไป ดังนั้นให้ส่งเป็นทั้งสองประเภท: และโดยสรุป: public abstract class Enum
// This enum MyEnum {} // Is really just sugar for this class MyEnum extends Enum
class Test { Type c = new C(); Type> d = new D
Step 0) C Step 1) Type
>> > Step 4) D
class Test
// Doesn't compile Test
execute((Serializable) (() -> {}));
execute((Runnable & Serializable) (() -> {}));
GO TO FULL VERSION