JavaRush /Blog Java /Random-VI /10 điều bạn chưa biết về Java
minuteman
Mức độ

10 điều bạn chưa biết về Java

Xuất bản trong nhóm
Vậy gần đây bạn có bắt đầu làm việc với Java không? Bạn có nhớ những ngày nó được gọi là "Oak", khi hướng đối tượng vẫn còn là một chủ đề nóng, khi người ta nghĩ C++ không có cơ hội và thậm chí chưa có ai nghe nói đến applet? Tôi có thể cho rằng bạn thậm chí không biết một nửa những điều sau đây. Hãy bắt đầu tuần mới với một số điều ngạc nhiên thú vị về hoạt động bên trong của Java. 10 điều bạn chưa biết về Java - 11. Không có ngoại lệ được kiểm tra. Đúng rồi! JVM không biết gì về điều đó, chỉ có ngôn ngữ Java mới biết. Ngày nay mọi người đều đồng ý rằng các trường hợp ngoại lệ được kiểm tra là một sai lầm. Như Bruce Eckel đã nói trong bài phát biểu cuối cùng của mình tại GeeCON ở Praha, không có ngôn ngữ nào khác kể từ khi Java sử dụng ngoại lệ được kiểm tra, ngay cả Java 8 cũng không còn đưa chúng vào API Luồng mới nữa (điều này có thể hơi phiền toái khi lambdas của bạn sử dụng IO hoặc JDBC ). Bạn có muốn bằng chứng cho thấy JVM không biết điều đó không? Hãy thử đoạn mã sau: Đoạn mã này không chỉ biên dịch mà còn ném ra một ngoại lệ SQLException, bạn thậm chí không cần sử dụng @SneakyThrows của Lombok cho việc này. 2. Bạn có thể có các phương thức nạp chồng chỉ khác nhau về kiểu trả về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; } } Điều này sẽ không được biên dịch, phải không? class Test { Object x() { return "abc"; } String x() { return "123"; } } Phải. Ngôn ngữ Java không cho phép hai phương thức được ghi đè tương đương trong cùng một lớp tại cùng một thời điểm, bất kể sự khác biệt của chúng về kiểu ném hoặc kiểu trả về. Nhưng đợi một chút. Kiểm tra lại tài liệu về Class.getMethod(String, Class…). Nó nói: Lưu ý rằng có thể có nhiều hơn một phương thức tương ứng trong một lớp, bởi vì trong khi ngôn ngữ Java cấm nhiều phương thức có cùng chữ ký nhưng có kiểu trả về khác nhau thì Máy ảo Java lại không cấm. Tính linh hoạt này trong máy ảo có thể được sử dụng để triển khai các tính năng ngôn ngữ khác nhau. Ví dụ, kết quả trả về hiệp biến có thể được thực hiện bằng các phương thức cầu nối; Phương thức bridge và phương thức ghi đè sẽ có cùng chữ ký nhưng có kiểu trả về khác nhau. Ồ, điều đó có ý nghĩa. Thực sự có khá nhiều điều xảy ra khi bạn viết như sau: Nhìn vào mã byte được tạo: Vậy t thực sự là một đối tượng trong mã byte. Điều này được hiểu rõ. Phương thức cầu nối tổng hợp thực sự được tạo bởi trình biên dịch vì kiểu trả về của Parent.x() có thể được mong đợi ở một số phần nhất định của lệnh gọi. Việc thêm các generic mà không có các phương thức cầu nối như vậy sẽ không thể thực hiện được trong biểu diễn nhị phân. Vì vậy, việc thay đổi JVM để cho phép một tính năng như vậy tạo ra ít rắc rối hơn (điều này cũng cho phép ghi đè phương thức hiệp biến như một tác dụng phụ...) Thông minh phải không? 3. Tất cả những điều sau đây đều là mảng hai chiều. Điều này thực sự đúng. Ngay cả khi máy phân tích tinh thần của bạn không thể hiểu ngay kiểu trả về từ các phương pháp được mô tả ở trên, thì chúng đều giống nhau! Giống như đoạn mã tiếp theo. Bạn có nghĩ điều này thật điên rồ không? Số lượng cơ hội để viết cũng chỉ đơn giản là làm bùng nổ trí tưởng tượng! Gõ chú thích. Một thiết bị có bí ẩn chỉ đứng sau sức mạnh của nó. Hay nói cách khác: Khi tôi thực hiện cam kết cuối cùng ngay trước kỳ nghỉ 4 tuần của mình. Tôi cho phép bạn sử dụng nó theo bất kỳ cách nào bạn muốn. 4. Bạn sẽ không gặp phải câu điều kiện Vì vậy, bạn nghĩ rằng mình đã biết mọi thứ về câu điều kiện khi bắt đầu sử dụng chúng? Hãy để tôi làm bạn thất vọng - bạn đã sai rồi. Đa số các bạn sẽ cho rằng 2 ví dụ sau là tương đương: tương đương với cái này? KHÔNG. Hãy sử dụng một bài kiểm tra nhanh Chương trình sẽ xuất ra kết quả như sau: Có! Toán tử có điều kiện sẽ thực hiện ép kiểu nếu cần. Bởi vì nếu không bạn sẽ mong đợi chương trình ném ra một ngoại lệ NullPointerException? 5. Bạn cũng sẽ không nhận được toán tử gán ghép. Liệu sự tháo vát có đủ không? Chúng ta hãy xem hai đoạn mã sau: 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 điều bạn chưa biết về 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; Theo trực giác, chúng phải bằng nhau phải không? Nhưng bạn biết không - chúng khác nhau. Đặc tả JLS cho biết: Biểu thức ghép của loại E1 op = E2 tương đương với E1 = (T) ((E1) op (E2)), trong đó T là loại E1, ngoại trừ E1 chỉ được đánh giá một lần. Một ví dụ điển hình là sử dụng *= hoặc /= : byte b = 10; b *= 5.7; System.out.println(b); // prints 57 hoặc: byte b = 100; b /= 2.5; System.out.println(b); // prints 40 hoặc: char ch = '0'; ch *= 1.1; System.out.println(ch); // prints '4' hoặc: char ch = 'A'; ch *= 1.5; System.out.println(ch); // prints 'a' Vậy đây có còn là một công cụ hữu ích không? 6. Số nguyên ngẫu nhiên Bây giờ là một nhiệm vụ khó khăn hơn. Đừng đọc giải pháp. Hãy xem liệu bạn có thể tự mình tìm được câu trả lời hay không. Khi tôi chạy chương trình sau: for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } Đôi khi tôi nhận được kết quả như sau: 92 221 45 48 236 183 39 193 33 84 Nhưng làm sao điều này có thể thực hiện được? Được rồi, câu trả lời nằm ở việc ghi đè bộ đệm Integer của JDK thông qua phản chiếu, sau đó sử dụng tính năng tự động đóng hộp và tự động bỏ hộp. Đừng làm điều này mà không có sự cho phép của người lớn! Hay nói cách khác: 10 điều bạn chưa biết về Java - 3 7. GOTO Một trong những sở thích của tôi. Java có GOTO! Viết điều này: int goto = 1; và bạn nhận được điều này: Đó là vì goto là một từ dành riêng không được sử dụng, chỉ để đề phòng... Nhưng đó không phải là phần thú vị. Điều thú vị là bạn có thể bao gồm goto được ghép nối với các khối ngắt, tiếp tục và được đánh dấu: Nhảy về phía trước Trong mã byte: Nhảy lùi Trong mã byte: 8. Java có bí danh loại Trong các ngôn ngữ khác (như Ceylon), chúng ta có thể xác định loại bí danh rất dễ dàng: Lớp People ở đây được xây dựng theo cách có thể thay thế nó bằng Set 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 ; : Trong Java, chúng ta không thể chỉ định nghĩa bí danh ở cấp cao nhất. Nhưng chúng ta có thể làm điều này theo nhu cầu của một lớp hoặc phương thức. Giả sử rằng chúng ta không hài lòng với những cái tên như Integer, Long, v.v. và chúng tôi muốn các tên ngắn hơn: I và L. Easy: Trong ví dụ trên, Integer được chuyển thành I để hiển thị cho lớp Test, trong khi Long được chuyển thành L để đáp ứng nhu cầu của phương thức x(). Bây giờ chúng ta có thể gọi phương pháp này như sau: Tất nhiên, không nên coi trọng kỹ thuật này. Trong trường hợp này, Integer và Long là loại cuối cùng, có nghĩa là I và L là các chuyển đổi hiệu quả (hầu như, chuyển đổi chỉ diễn ra một chiều). Nếu chúng ta quyết định sử dụng các kiểu không phải cuối cùng (ví dụ như Object), thì chúng ta có thể sử dụng các kiểu generic thông thường. Chúng tôi chơi một chút và thế là đủ. Hãy chuyển sang một cái gì đó thực sự thú vị. 9. Một số mối quan hệ kiểu không thể quyết định được! Được rồi, bây giờ điều này sẽ thực sự thú vị, vì vậy hãy lấy một tách cà phê đậm đặc và xem xét hai loại sau: 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 >>> {} Vậy C và D có ý nghĩa gì? Theo một nghĩa nào đó, chúng có tính đệ quy, tương tự như đệ quy trong java.lang.Enum. Hãy xem xét: Với các thông số kỹ thuật ở trên, việc triển khai thực tế của enum chỉ là cú pháp: Với ý nghĩ đó, hãy quay lại hai loại của chúng ta. Đoạn mã sau có được biên dịch không? Một câu hỏi khó... và nó vẫn chưa thực sự được giải quyết? C có phải là kiểu con của Type không ? Hãy thử biên dịch phần này trong Eclipse hoặc Idea của bạn và họ sẽ cho bạn biết họ nghĩ gì. Xả nó xuống cống... Một số mối quan hệ kiểu trong Java không thể quyết định được! 10. Kiểu giao nhau Java có một tính năng rất thú vị gọi là kiểu giao nhau. Bạn có thể khai báo một loại (chung) thực sự là giao điểm của hai loại. Ví dụ: Tham số loại tùy chỉnh T mà bạn liên kết với các phiên bản của lớp Kiểm tra phải bao gồm cả giao diện Có thể tuần tự hóa và Có thể sao chép. Ví dụ: Chuỗi không thể bị hạn chế, nhưng Ngày có thể: Tính năng này có nhiều cách sử dụng trong Java8, nơi bạn có thể truyền kiểu. Điều này giúp ích như thế nào? Hầu như không có gì, nhưng nếu bạn muốn chuyển biểu thức lambda của mình thành loại bạn cần thì không có cách nào khác. Giả sử bạn có một hạn chế điên rồ như vậy trong phương pháp của mình: Bạn muốn Runnable đồng thời chỉ có thể Serializable nếu bạn muốn thực thi nó ở một nơi khác và gửi kết quả qua mạng. Lambda và tuần tự hóa thêm một chút mỉa mai. Bạn có thể tuần tự hóa biểu thức lambda của mình nếu loại mục tiêu và đối số của nó có thể tuần tự hóa. Nhưng ngay cả khi điều này đúng, chúng cũng không tự động kích hoạt giao diện Serializable. Bạn phải tự mình mang chúng đến loại này. Nhưng khi bạn chỉ chuyển sang Serializable: thì lambda sẽ không còn Runnable nữa, vì vậy hãy chuyển chúng sang cả hai loại: Và kết luận: 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 mạnh mẽ nhưng cũng đầy bí ẩn.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION