JavaRush /จาวาบล็อก /Random-TH /10 เรื่องที่คุณไม่รู้เกี่ยวกับจาวา
minuteman
ระดับ

10 เรื่องที่คุณไม่รู้เกี่ยวกับจาวา

เผยแพร่ในกลุ่ม
คุณเพิ่งเริ่มทำงานกับ Java แล้วหรือยัง? จำวันที่มันถูกเรียกว่า "Oak" เมื่อการวางแนววัตถุยังคงเป็นประเด็นร้อน เมื่อคน C++ คิดว่า Java ไม่มีโอกาส และเมื่อไม่มีใครเคยได้ยินเกี่ยวกับแอปเพล็ตเลย ฉันคิดว่าคุณไม่รู้เรื่องต่อไปนี้ด้วยซ้ำ มาเริ่มต้นสัปดาห์ด้วยเซอร์ไพรส์เจ๋งๆ เกี่ยวกับการทำงานภายในของ Java กันดีกว่า 10 เรื่องที่คุณไม่รู้เกี่ยวกับ Java - 11. ไม่มีสิ่งที่เรียกว่าข้อยกเว้นที่ถูกตรวจสอบ ถูกตัอง! JVM ไม่มีความคิดเกี่ยวกับสิ่งนี้ มีเพียงภาษา Java เท่านั้นที่รู้ วันนี้ทุกคนเห็นพ้องกันว่าการตรวจสอบข้อยกเว้นนั้นเป็นข้อผิดพลาด ดังที่ Bruce Eckel กล่าวในการพูดคุยครั้งสุดท้ายที่ GeeCON ในปราก ไม่มีภาษาอื่นใดเนื่องจาก Java ใช้ข้อยกเว้นที่ถูกตรวจสอบ แม้แต่ Java 8 ก็ไม่ครอบคลุมภาษาเหล่านั้นใน Streams API ใหม่อีกต่อไป (ซึ่งอาจสร้างความรำคาญเล็กน้อยเมื่อ lambdas ของคุณใช้ IO หรือ JDBC ). คุณต้องการพิสูจน์ว่า JVM ไม่ทราบเรื่องดังกล่าวหรือไม่? ลองใช้โค้ดต่อไปนี้: ไม่เพียงแต่คอมไพล์นี้เท่านั้น แต่ยังส่ง SQLException ด้วย คุณไม่จำเป็นต้องใช้ @SneakyThrows ของ Lombok สำหรับสิ่งนี้ด้วยซ้ำ 2. คุณสามารถมีวิธีโอเวอร์โหลดที่แตกต่างกันเพียงประเภทการคืนสินค้าเท่านั้น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 [] = {{}}; } 10 เรื่องที่คุณไม่รู้เกี่ยวกับ Java - 2 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 อย่าทำสิ่งนี้โดยไม่ได้รับอนุญาตจากผู้ใหญ่! หรืออีกนัยหนึ่ง: 10 เรื่องที่คุณไม่รู้เกี่ยวกับ Java - 3 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 ; : ใน Java เราไม่สามารถกำหนดนามแฝงที่ระดับบนสุดได้ แต่เราสามารถทำได้ตามความต้องการของคลาสหรือเมธอด สมมติว่าเราไม่พอใจกับชื่อเช่น Integer, Long เป็นต้น และเราต้องการชื่อที่สั้นกว่านี้: I และ L ง่าย: ในตัวอย่างด้านบน จำนวนเต็มจะถูกแปลงเป็น I เพื่อให้มองเห็นคลาสการทดสอบได้ ในขณะที่แบบยาวจะถูกแปลงเป็น L สำหรับความต้องการของเมธอด x() ตอนนี้เราสามารถเรียกวิธีนี้ได้ดังนี้: แน่นอนว่าเทคนิคนี้ไม่ควรจริงจัง ในกรณีนี้ จำนวนเต็มและแบบยาวเป็นประเภทสุดท้าย ซึ่งหมายความว่า I และ L เป็นการแปลงที่มีประสิทธิภาพ (โดยส่วนใหญ่แล้วการแปลงจะไปเพียงทางเดียวเท่านั้น) หากเราตัดสินใจที่จะใช้ประเภทที่ไม่ใช่ขั้นสุดท้าย (เช่น Object) เราก็สามารถใช้ยาชื่อสามัญทั่วไปได้ เราเล่นไปนิดหน่อยก็พอแล้ว เรามาดูสิ่งที่น่าสนใจกันดีกว่า 9. ความสัมพันธ์บางประเภทไม่สามารถตัดสินใจได้! เอาล่ะ เรื่องนี้จะน่าสนใจมากจริงๆ เลยหยิบกาแฟเข้มข้นสักแก้วแล้วมาดูสองประเภทต่อไปนี้: 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 0) D > Step 1) Type >>> > Step 2) D >> Step 3) List >> > Step 4) D > >> Step . . . (expand forever) class Test { } // Doesn't compile Test s = null; // Compiles Test d = null; void execute(T t) {} execute((Serializable) (() -> {}));execute((Runnable & Serializable) (() -> {}));

Java นั้นทรงพลังพอ ๆ กับความลึกลับ

ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION