Trước khi đi vào bài viết, gửi tới các bạn lời chúc sức khỏe cho một năm mới 2024 thật cháy với ngọn lửa học tập, công việc vừa ý, túi tiền nặng ký nhé ♥️😘
Trải qua hàng loạt các phiên bản, Java Development Kit (JDK) từ JDK 8 đến JDK 21 đã chứng kiến sự tiến bộ đáng kể trong ngôn ngữ lập trình Java và môi trường phát triển của nó. Từ những cải tiến nhỏ đến những thay đổi đáng kể, JDK đã không ngừng đáp ứng những thách thức ngày càng phức tạp của ngành công nghiệp công nghệ. Hãy cùng tìm hiểu về những tính năng quan trọng được giới thiệu qua các phiên bản từ JDK 8 đến JDK 21, và nhìn lại hành trình phát triển đầy tích cực của ngôn ngữ Java.
Bài học sẽ cực kỳ hữu ích cho những người mới bắt đầu cũng như các lập trình viên lâu năm đang làm việc trên phiên bản java cũ như Java 8 và Java 11 và muốn tự cập nhật những gì đang xảy ra trong thế giới Java.
JAVA 8
1. Lambda Expressions (Biểu thức Lambda)
Cho phép lập trình hàm (functional programming) bằng cách cho phép sử dụng các hàm vô danh (anonymous functions).
Cung cấp cú pháp ngắn gọn để tạo các đoạn mã xử lý sự kiện hoặc triển khai functional interface chỉ chứa một phương thức duy nhất
2. Functional Interfaces
Là interface chỉ chứa một phương thức trừu tượng duy nhất được gọi là "abstract method".
Tính năng chính của Functional Interface là khả năng sử dụng biểu thức lambda để triển khai phương thức của nó.
Chú thích @FunctionalInterface để đánh dấu những giao diện như vậy.
3. Stream API
Giới thiệu một trừu tượng mới gọi là Stream để xử lý chuỗi các phần tử.
Hỗ trợ các thao tác hàm trên streams như filter, map, reduce, v.v.
Tham khảo bài viết: 15 bài tập thực hành giúp bạn thành thạo Java Stream API của mình để hiểu rõ hơn nhé.
4. Method References (Phương thức tham chiếu)
Cung cấp một cú pháp rút gọn cho biểu thức Lambda.
Cho phép tham chiếu đến phương thức hoặc constructor bằng toán tử ::.
5. Optional
Một đối tượng chứa giá trị có thể null hoặc không null.
Hỗ trợ xử lý kiểm tra null hiệu quả hơn và tránh NullPointerExceptions.
Tham khảo 2 bài bài viết của mình để hiểu rõ hơn nhé:
- Optional trong Java - cách sử dụng và khi nào nên sử dụng
- Optional trong Java - làm chủ 15 phương thức trong 20s
6. New Date and Time API (API Ngày và Giờ Mới)
Gói java.time giới thiệu một API linh hoạt hơn để xử lý ngày và giờ.
Giải quyết các vấn đề với các lớp java.util.Date và java.util.Calendar cũ.
7. Default Methods (Phương Thức Mặc Định):
Default Methods được giới thiệu để thêm tính năng mở rộng cho các interface mà không làm phá vỡ các lớp implement từ interface này.
Một interface có thể chứa phương thức mặc định, là phương thức có thể được triển khai bởi các lớp triển khai của interface đó. Các lớp triển khai có thể sử dụng phương thức mặc định này mà không cần phải triển khai lại nó.
8. Nashorn JavaScript Engine (Bộ Duyệt JavaScript Nashorn)
Thay thế bộ duyệt JavaScript cũ Rhino.
Cung cấp hiệu suất tốt hơn và tương thích tốt hơn với tiêu chuẩn JavaScript hiện đại.
9. Parallel Streams (Luồng Song Song)
Cho phép xử lý song song của streams bằng cách sử dụng phương thức parallel().
Tăng hiệu suất trên hệ thống đa lõi cho một số loại thao tác cụ thể.
10. Collectors
Giới thiệu một bộ các phương thức tiện ích trong lớp Collectors cho các thao tác phổ biến, như toList(), toSet(), joining(), v.v.
11. Functional Interfaces trong gói java.util.function
Functional interface mới như Predicate, Function, Consumer và Supplier để hỗ trợ biểu thức Lambda.
JAVA 9
1. Cải tiến Process API
Java 9 giới thiệu cải tiến cho API Process, mang lại sự kiểm soát tốt hơn đối với các tiến trình native. Lớp mới ProcessHandle cho phép developer tương tác với các tiến trình và lấy ra các thông tin về chúng.
2. Collection Factory Methods
Java 9 đã thêm vào các giao diện bộ sưu tập (List, Set, Map, v.v.) các phương thức tạo đối tượng tĩnh mới, giúp tạo ra các phiên bản không thay đổi của các collection này trở nên thuận tiện hơn.
- List<String> colors: Được tạo bằng cách sử dụng phương thức tạo mới List.of(). Danh sách này chứa các màu "Red", "Green", và "Blue".
Lưu ý rằng các đối tượng được tạo bằng cách sử dụng Collection Factory Methods là không thay đổi (immutable), điều này nghĩa là sau khi được khởi tạo, bạn không thể thay đổi cấu trúc của chúng (thêm, loại bỏ phần tử). Điều này mang lại tính an toàn và dễ kiểm soát hơn trong quá trình lập trình.
3. Cải tiến Stream API
Stream API được cải thiện với nhiều phương thức mới như takeWhile, dropWhile, và ofNullable, giúp tăng tính linh hoạt và chức năng khi làm việc với luồng (streams).
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream; public class StreamAPIImprovementsExample { public static void main(String[] args) { // Ví dụ 1: takeWhile List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> lessThanFive = numbers.stream() .takeWhile(n -> n < 5) .collect(Collectors.toList()); System.out.println("Số nhỏ hơn 5: " + lessThanFive); // Ví dụ 2: dropWhile List<Integer> greaterThanThree = numbers.stream() .dropWhile(n -> n <= 3) .collect(Collectors.toList()); System.out.println("Số lớn hơn 3: " + greaterThanThree); // Ví dụ 3: ofNullable // Ví dụ 3: ofNullable String value1 = "Xin chào"; String value2 = null; // Ví dụ với giá trị không rỗng Stream.ofNullable(value1) .ifPresentOrElse(v -> System.out.println("Ví dụ ofNullable - Giá trị không rỗng: " + v), () -> System.out.println("Ví dụ ofNullable - Giá trị rỗng")); // Ví dụ với giá trị rỗng Stream.ofNullable(value2) .ifPresentOrElse(v -> System.out.println("Ví dụ ofNullable - Giá trị không rỗng: " + v), () -> System.out.println("Ví dụ ofNullable - Giá trị rỗng")); // Ví dụ của luồng an toàn với giá trị null List<String> names = Arrays.asList("Alice", "Bob", null, "Charlie", null, "David"); List<String> nonNullNames = names.stream() .flatMap(name -> StreamAPIImprovementsExample.nullSafeStream(name)) .collect(Collectors.toList()); System.out.println("Các tên không rỗng: " + nonNullNames); } // Phương thức hỗ trợ để tạo một luồng từ một giá trị có thể là null private static <T> java.util.stream.Stream<T> nullSafeStream(T value) { return value == null ? java.util.stream.Stream.empty() : java.util.stream.Stream.of(value); }
}
Trong ví dụ này:
- takeWhile: được sử dụng để lấy các phần tử từ stream cho đến khi một điều kiện nhất định được đáp ứng (trong trường hợp này, là số nhỏ hơn 5).
- dropWhile: được sử dụng để bỏ các phần tử từ stream trong khi một điều kiện nhất định được đáp ứng (trong trường hợp này, là số nhỏ hơn hoặc bằng 3).
- ofNullable: được sử dụng để tạo một stream từ một giá trị có thể là null, loại bỏ giá trị null (trong trường hợp này, loại bỏ tên null từ danh sách).
4. Private Methods trong Interfaces
Interface trong Java 9 có thể chứa phương thức riêng tư, cho lập trình viên triển đóng gói chức năng chung bên trong một giao diện mà không cần phải tiết lộ nó cho các lớp bên ngoài.
5. HTTP/2 Client:
Java 9 giới thiệu một thư viện HTTP client mới nhẹ hỗ trợ HTTP/2 và WebSocket. Thư viện này được thiết kế để hiệu quả và linh hoạt hơn so với API HttpURLConnection cũ.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse; public class HttpClientExample { public static void main(String[] args) throws Exception { // Tạo đối tượng HttpClient HttpClient httpClient = HttpClient.newHttpClient(); // Tạo đối tượng HttpRequest sử dụng Builder HttpRequest httpRequest = HttpRequest.newBuilder() .uri(new URI("https://www.example.com")) .GET() .build(); // Gửi HTTP request và nhận HTTP response HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); // In thông tin về HTTP response System.out.println("Response Code: " + response.statusCode()); System.out.println("Response Body: " + response.body()); }
}
Đoạn mã này đang sử dụng API HttpClient trong Java để gửi một HTTP GET request đến "https://www.example.com" và hiển thị mã trạng thái và nội dung của phản hồi.
Java 10
1. Local-Variable Type Inference (var)
Java 10 giới thiệu khả năng sử dụng từ khóa var để đặt kiểu dữ liệu cho biến cục bộ. Điều này cho phép bạn khai báo biến cục bộ mà không cần chỉ định rõ kiểu dữ liệu, để trình biên dịch tự động suy luận dựa trên giá trị được gán.
import java.net.http.HttpClient;
import java.io.BufferedReader;
import java.util.List; public class LocalVarInference { /** * Cho phép: chỉ khi sử dụng làm biến cục bộ * Không cho phép: ở bất kỳ nơi nào khác (trường của lớp, tham số của phương thức, vv.) * Sử dụng 'var' một cách có trách nhiệm! * * Sử dụng: * - khi rõ ràng về kiểu dữ liệu (string, int) * - để rút gọn kiểu dữ liệu quá dài và phức tạp * * Không sử dụng: * - khi giá trị trả về không rõ ràng (var data = service.getData();) */ public static void main(String[] args) { // Cho phép, nhưng mang lại ít lợi ích var b = "b"; var c = 5; // int var d = 5.0; // double var httpClient = HttpClient.newHttpClient(); // Suy luận phức tạp hơn :) var list = List.of(1, 2.0, "3"); // Lợi ích trở nên rõ ràng hơn với các kiểu dữ liệu có tên dài var reader = new BufferedReader(null); // so với BufferedReader reader2 = new BufferedReader(null); }
}
2. Optional API — giới thiệu phương thức mới
- Phương thức mới: orElseThrow()
- Cách sử dụng và ví dụ mình viết trong bài này nhé : Optional trong Java - làm chủ 15 phương thức trong 20s
JAVA 11
1.HTTP client
Java 11 đã giới thiệu một HTTP Client tích hợp trong gói java.net.http. Dưới đây là một ví dụ đơn giản về cách sử dụng HttpClient trong Java 11:
2. Các phương thức về File mới
Java 11 đã giới thiệu một số phương thức mới trong gói java.nio.file, cung cấp các chức năng bổ sung cho việc làm việc với tệp và thư mục. Một số phương thức đáng chú ý bao gồm:
-
Files.readString(Path path) : Phương thức này được sử dụng để đọc nội dung của một tệp tin thành một chuỗi.
-
Files.writeString(Path path, CharSequence content, OpenOption... options) : Phương thức này được sử dụng để ghi nội dung một chuỗi vào tệp tin.
-
Files.readAllLines(Path path): Phương thức này được sử dụng để đọc tất cả các dòng từ tệp tin và trả về một danh sách (List<String>) chứa các dòng đó.
-
Files.write(Path path, Iterable<? extends CharSequence> lines, OpenOption... options): Phương thức này được sử dụng để ghi một dãy các dòng (được đại diện bởi một Iterable) vào tệp tin
-
Files.newBufferedReader(Path path): Phương thức này được sử dụng để tạo một đối tượng BufferedReader để đọc từ một tệp tin.
-
Files.newBufferedWriter(Path path, OpenOption... options): Phương thức này được sử dụng để tạo một đối tượng BufferedWriter để ghi vào một tệp tin.
- files.mismatch(Path path1, Path path2): Phương thức Files.mismatch(Path path1, Path path2) được sử dụng để so sánh nội dung của hai tệp tin và trả về vị trí của byte đầu tiên không giống nhau. Nếu hai tệp tin giống nhau, phương thức sẽ trả về -1.
JAVA 12
1. Compact Number Formatting (Định dạng số ngắn gọn)
Java 12 đã giới thiệu một tính năng mới có tên là “Compact Number Formatting” nằm trong JEP 357. Cải tiến này cung cấp cho chúng ta một cách ngắn gọn hơn để định dạng các số lớn theo cách dành riêng cho từng vùng miền.
2. String::indent (JEP 326)
String::indent là một phương thức giới thiệu trong Java 12 để thêm hoặc loại bỏ các dòng trống ở đầu chuỗi và cuối chuỗi (trailing và leading whitespaces). Nó trả về một bản sao của chuỗi với các thay đổi được thực hiện.
Đầu ra:
4. Cải tiến thêm java.util.stream.Collectors (JEP 325)
Lớp Collectors trong Java 12 đã giới thiệu các collectors như teeing, cho phép kết hợp hai collectors thành một collector duy nhất
JAVA 13
Mình thấy thì phiên bản Java này không có nhiều điều thú vị, chỉ là một vài sự update nhỏ nên chúng mình sẽ cùng sang phiên bản java tiếp theo nhé.
JAVA 14
1. “Switch Expressions” (SE) thay cho “Switch Statements” (SS)
Switch Expressions (SE) được giới thiệu dưới dạng tính năng xem trước trong Java 12 và được hoàn thiện trong Java 13. Cung cấp một cách mới và nhanh chóng hơn để viết các câu lệnh switch
Điều này giúp cải thiện độ đọc và bảo trì của mã, cũng như hỗ trợ tính năng mở rộng của ngôn ngữ.
- Đây là cách viết cũ “Switch Statements” (SS)
- Cách viết mới với “Switch Expressions” (SE)
2. "YIELD" Statement
Trong Java, từ khóa yield được giới thiệu trong phiên bản 13 để hỗ trợ tính năng "Switch Expressions" (SE). yield được sử dụng trong một biểu thức switch để trả về giá trị từ một khối mã của một trường hợp trong switch expression.
JAVA 15
1. Text-block
Text block giúp đơn giản hóa việc tạo và duy trì các chuỗi đa dòng trong code Java.
- Không sử dụng Text Block:
- Sử dụng Text Block:
Việc sử dụng text-block sẽ giúp chúng ta:
- Duy trì Cấu Trúc: Text block giữ nguyên cấu trúc của chuỗi, cho phép bạn biểu diễn các chuỗi nhiều dòng một cách tự nhiên hơn, cải thiện khả năng đọc code.
- Kiểm soát khoảng trắng: Các khoảng trắng ở đầu và cuối trên mỗi dòng được loại bỏ, mang lại khả năng kiểm soát thụt lề tốt hơn.
- Chuỗi thoát (escape string): Chuỗi thoát vẫn hợp lệ trong các khối văn bản, cho phép bao gồm các ký tự đặc biệt.
JAVA 16
1. Pattern Matching for instanceof
Là một tính năng trong Java giúp làm cho mã nguồn đơn giản hóa quá trình kiểm tra kiểu của đối tượng và thực hiện chuyển đổi ngay trong cùng một câu lệnh. Điều này giúp giảm bớt mã nguồn lặp lại và làm cho mã trở nên dễ đọc hơn.
Trước khi có pattern matching for instanceof, chúng ta thường phải sử dụng instanceof kết hợp với ép kiểu (cast) và sau đó kiểm tra lại kiểu để sử dụng đối tượng theo kiểu mong muốn.
Trong ví dụ trên, dòng if (obj instanceof String str) sử dụng pattern matching cho instanceof để kiểm tra kiểu của obj và đồng thời thực hiện chuyển đổi thành biến str kiểu String. Nó thực hiện cả hai công việc (kiểm tra và chuyển đổi) trong một lần, giúp làm cho mã nguồn trở nên ngắn gọn và dễ đọc hơn.
2. Record
Trong Java, record là một loại mới của lớp đặc biệt được giới thiệu với bản tiêu chuẩn trong Java 16. record được thiết kế để đơn giản hóa việc tạo các lớp không thay đổi (Immutable class) chỉ chứa dữ liệu. Nó tự động tạo các phương thức getter, equals, hashCode và toString dựa trên các trường dữ liệu của nó.
Dưới đây là cú pháp để khai báo một record trong Java:
Trong ví dụ này, Person là một record có hai trường dữ liệu là name và age. Mỗi trường dữ liệu trong record được xác định bởi một tham số của constructor, và các phương thức getter được tạo tự động.
Dưới đây là một số điểm chính về record trong Java:
-
Dữ liệu Bất Biến (Immutable Data): Các trường dữ liệu của record là không thay đổi (immutable), nghĩa là sau khi tạo ra một record, các giá trị của các trường không thể thay đổi.
-
Tự Động Tạo Phương Thức:
- Phương thức getter cho mỗi trường dữ liệu.
- Phương thức equals() để so sánh hai record dựa trên giá trị của các trường dữ liệu.
- Phương thức hashCode() để tạo mã băm dựa trên các trường dữ liệu.
- Phương thức toString() để tạo chuỗi biểu diễn record.
-
Không Thể Kế Thừa:
- record không thể mở rộng từ một lớp khác hoặc implement các interface khác.
- Một record không thể là một lớp cha hay một interface.
-
Constructor Tự Động:
- record tự động tạo một constructor có tham số để khởi tạo các trường dữ liệu.
Dưới đây là một ví dụ về cách sử dụng record trong Java:
3. Date Time Formatter API
Trong Java 16, DateTimeFormatter hỗ trợ day period thông qua biểu tượng "B". Biểu tượng "B" được sử dụng để hiển thị hoặc phân tích các yếu tố như "AM" (Ante Meridiem) và "PM" (Post Meridiem) trong định dạng thời gian.
- Trong ví dụ này, ba định dạng thời gian khác nhau được tạo bằng cách sử dụng biểu tượng "B" với các kiểu khác nhau. Cụ thể, ví dụ thứ nhất sử dụng kiểu thông thường với "a" (AM/PM), ví dụ thứ hai sử dụng kiểu "B" đơn giản (narrow), và ví dụ thứ ba sử dụng kiểu "BBBB" để hiển thị day period một cách chi tiết và rộng lớn.
So Sánh với SimpleDateFormat:
- DateTimeFormatter là một lựa chọn tốt hơn so với SimpleDateFormat vì nó an toàn đối với đa luồng và không đổi.
- SimpleDateFormat không an toàn cho việc đa luồng và có thể gây ra vấn đề đồng bộ hóa.
4. Changes in Stream API
Java 16 mang đến một số thay đổi đáng chú ý cho Stream API, làm cho nó trở nên mạnh mẽ và thuận tiện hơn để sử dụng. Dưới đây là những điểm nổi bật chính:
- Phương thức Stream.toList(): Phương thức mới này cung cấp một cách ngắn gọn để thu thập các phần tử của một stream vào một List. Trước đây, bạn phải sử dụng collect(Collectors.toList()), thì bây giờ có thể là không cần thiết nữa.
- Phương thức Stream.mapMulti(): Phương thức này cho phép bạn ánh xạ mỗi phần tử của một stream thành không hoặc nhiều phần tử, tạo ra một stream mới từ các phần tử kết quả. Nó hữu ích cho việc phân chia hoặc làm phẳng các cấu trúc dữ liệu phức tạp.
Trong ví dụ này, chúng ta tạo một Stream của các số nguyên từ 1 đến 3 và sau đó sử dụng mapMulti để nhân mỗi số với các số từ 1 đến số nguyên đó. Kết quả là một stream chứa các giá trị tương ứng với các kết quả nhân.
Khi bạn chạy chương trình, bạn sẽ nhận được đầu ra sau:
-
Những thay đổi nhỏ khác:
- String streams hiện đã hỗ trợ trực tiếp các phương thức limit và skip, loại bỏ các thao tác trung gian.
- Phương thức peek có thể được sử dụng với các stream song song, cho phép các tác động phụ mà không ảnh hưởng đến tính song song.
JAVA 17
1. Sealed classes(Subclassing)
Lớp Sealed là một tính năng mới được giới thiệu trong Java 17 (JEP 409) mang lại sự kiểm soát mạnh mẽ hơn đối với các việc kế thừa. Về cơ bản, chúng cho phép bạn hạn chế lớp hoặc interface của mình chỉ có thể được extend hoặc implement bởi các lớp cụ thể. Những lợi ích mà nó mang lại bao gồm:
-
Tăng cường An Toàn Kiểu: Bằng cách chỉ định các lớp con được phép, bạn sẽ ngăn chặn sự mở rộng không mong muốn có thể làm hỏng mã nguồn của bạn hoặc tạo ra lỗ hổng bảo mật.
-
Thiết Kế Thư Viện: Bạn có thể tạo ra các hệ sinh thái đóng gói trong thư viện của mình, đảm bảo người dùng chỉ làm việc với các lớp mở rộng được phê duyệt và không tạo ra các triển khai không tương thích.
-
Dễ Dàng Bảo Trì Mã Nguồn: Việc biết chính xác tập hợp các lớp con có thể xuất hiện giảm bớt sự phức tạp khi tư duy về code của bạn và làm cho nó dễ hiểu và dễ bảo trì hơn.
Trong ví dụ trên:
-
Lớp Shape được đánh dấu là sealed, và permits chỉ định rằng nó chỉ cho phép các lớp Circle, Rectangle, và Triangle làm lớp con của nó.
-
Các lớp Circle, Rectangle, và Triangle đều được đánh dấu là final, tức là chúng không thể có thêm lớp con.
-
Kết quả là, chỉ có các lớp được liệt kê trong permits có thể kế thừa trực tiếp từ Shape. Các lớp khác không thể mở rộng Shape, đảm bảo kiểm soát chặt chẽ hơn về quyền truy cập vào kế thừa và giảm nguy cơ của lỗi trong quá trình phát triển và bảo trì mã nguồn.
JAVA 18
1. UTF-8 by Default
Java 18 biến UTF-8 thành mã hóa ký tự mặc định cho nền tảng, phù hợp với các tiêu chuẩn hiện đại và đơn giản hóa việc xử lý ký tự
Một ví dụ đơn giản như này, mình có hàm viết văn bản “Happy Coding!” bằng tiếng Nhật và chạy chức năng trong Unix (Linux hoặc MacOS):
Lúc này, nếu bạn chạy chức năng đọc bên dưới trong Windows, bạn sẽ gặp vấn đề không thể đọc được văn bản đã viết ở trên:
Lý do là vì Unix lưu trữ tệp ở định dạng UTF-8 trong khi Windows đọc nó ở định dạng Windows-1252. Trong phiên bản Java 18 mới này, vấn đề này đã được giải quyết vì UTF-8 hiện được đặt mặc định.
2. Simple Web Server
Trong Java 18, nó cung cấp công cụ dòng lệnh để khởi động máy chủ web. Mặc dù công cụ này có thể không được khuyến nghị trong môi trường production nhưng nó rất hữu ích cho việc thử nghiệm.
Để khởi động máy chủ web chúng ta có thể sử dụng lệnh “jwebserver”. Theo mặc định, nó khởi chạy máy chủ trên localhost:8000.
Bạn có thể sử dụng -b để chỉ định địa chỉ IP mà máy chủ sẽ lắng nghe. Với -p, bạn có thể thay đổi cổng và -d thư mục mà máy chủ sẽ phục vụ. Còn -o, để định cấu hình đầu ra log. Ví dụ:
Ngoài ra, bạn có thể nhận được danh sách các options kèm theo lời giải thích với câu lệnh:
3. Triển khai lại Core Reflection bằng Method Handles
Mặc dù thay đổi này sẽ hiếm khi được sử dụng, nhưng bạn nên biết về sự thay đổi này nếu bạn làm việc với Java Reflection.
Java reflection là khả năng của một chương trình Java để kiểm tra, khám phá và thậm chí thay đổi cấu trúc của nó trong quá trình chạy. Reflection cung cấp các công cụ mạnh mẽ nhưng cũng có thể tạo ra mã khó bảo trì và hiệu suất không tốt nếu sử dụng không đúng cách.
Ở Java 18, quyết định đã được đưa ra để triển khai lại mã của các lớp reflection như java.lang.reflect.Method, Field, và Co****nstructor trên cơ sở của java.lang.invoke Method Handles. Method Handles là một phần của Java's Invokedynamic API, và chúng mang lại hiệu suất tốt hơn và khả năng tùy chỉnh cao hơn so với Core Reflection.
Ví dụ:
- Core Reflection
- MethodHandles
Ví dụ được đưa ra là để đọc trường id private của một đối tượng String thông qua reflection. Trong trường hợp Core Reflection, bạn cần sử dụng getDeclaredField và setAccessible, trong khi trong trường hợp MethodHandles, bạn có thể sử dụng VarHandle để đạt được cùng một mục đích. Sự thay đổi này được đưa ra với hy vọng giảm công sức phát triển cho cả java.lang.reflect và java.lang.invoke APIs, giúp tối ưu hóa mã và tăng cường hiệu suất.
4. Ngừng sử dụng Finalization trong quản lý tài nguyên
Finalization là một phần của quản lý bộ nhớ tự động của Java, có nhiệm vụ dọn dẹp tài nguyên không còn sử dụng nữa, chẳng hạn như đóng tệp hoặc đóng kết nối cơ sở dữ liệu.
Tuy nhiên, việc sử dụng Finalization có những hạn chế và vấn đề, làm giảm hiệu suất và gây khó khăn trong việc dự đoán chính xác khi tài nguyên sẽ được giải phóng. Thậm chí, có thể xảy ra tình trạng Finalization không được gọi hoặc bị gọi quá muộn, dẫn đến lãng phí tài nguyên.
Đề xuất ngừng sử dụng Finalization là để mở đường cho các lựa chọn thay thế an toàn hơn và đáng tin cậy hơn. Các phương tiện như try-with-resources trong Java (kể từ Java 7) và sự sử dụng AutoCloseable interface là những ví dụ về các cách thức an toàn hơn để quản lý tài nguyên. Sự thay thế này giúp tăng hiệu suất, giảm khả năng xảy ra lỗi và làm cho mã nguồn dễ đọc và bảo trì hơn.
JAVA 19
Ở phiên bản java này JDK cũng đã đưa ra một số tính năng nổi bật, nhưng chúng mới chỉ đang thử nghiệm hoặc đang phát triển để thu thập ý kiến từ cộng đồng và các trải nghiệm thực tế trước khi chúng trở thành một phần chính thức của JDK. Nên mình sẽ chưa đề cập tới ở đây.
Tham khảo thông tin về các tính năng được giới thiệu trong Java 19
Java 20
Cũng tương tự như Java 19, thì Java 20 cũng như vậy.
Tham khảo thông tin các tính năng được giới thiệu trong Java 20
JAVA 21
1. Virtual Threads
Một trong những tính năng quan trọng nhất của Java 21 là Virtual Threads
Tính năng này giới thiệu các luồng nhẹ (lightweight threads) chạy trên các luồng của hệ điều hành (system threads), nhằm đơn giản hóa việc lập trình đồng thời và cải thiện hiệu suất cho một số khối lượng công việc nhất định.
Hiểu một cách đơn giản nó sẽ là như này, trong mô hình truyền thống của Java, mỗi luồng được tạo tương đương với một luồng hệ điều hành. Điều này có nghĩa là khi bạn tạo ra một luồng Java, JVM sẽ tạo một luồng hệ điều hành tương ứng và khi bạn tạo ra 100 luồng thì cũng sẽ có 100 luồng hệ điều hành tương ứng được tạo - tỉ lệ 1:1. Khi mở rộng ứng dụng, xu hướng là chúng ta sẽ cần càng nhiều threads hơn, dẫn đến memory tăng theo. Việc này tạo ra áp lực nên CPU. Thêm nữa, các developer ít kinh nghiệm thường mắc lỗi trong việc quản lý threads dẫn đến việc sử dụng chúng không hiệu quả.
Với Vitural Threads câu chuyện nay đã khác, threads Java & threads Os sẽ theo tỉ lệ: n:1. Tức là ta có thể chạy rất nhiều đoạn code bất động bộ với số lượng threads Os ít hơn, đôi khi chỉ cần 1. Thời gian khởi tạo & tài nguyên cần thiết của Vitural Thread ít hơn so với Threads Os. Vitural Threads sẽ được quản lý bởi JDK. Nhìn chung, tính năng này rất hữu ích và được cộng đồng đón nhận tích cực
Virtual Threads là một biến thể của java.lang.Thread nhưng nhẹ và linh hoạt hơn nhiều và là một phần của Project Loom không được hệ điều hành quản lý hoặc lên lịch. Thay vào đó, JVM sẽ chịu trách nhiệm làm điều này. Như các bạn đã biết, bất kỳ công việc thực tế nào cũng phải được chạy trong một luồng nền tảng (Platform Thread), nhưng JVM đang sử dụng khái niệm Carrier Thread , chính là các luồng nền tảng, để “mang” bất kỳ luồng ảo nào khi thực thi.
Đây là ví dụ đơn giản giới thiệu các luồng ảo trong Java 21:
- Create Virtual Threads:
- Chờ luồng hoàn thành
- Output:
Điều này sẽ xen kẽ đầu ra từ cả hai luồng ảo, thể hiện việc thực thi đồng thời mà không cần sử dụng toàn bộ luồng hệ điều hành (os thread). Bạn có thể thấy đầu ra giống như vậy
Lợi ích mà Vitural Threads mang lại:
-
Nhẹ hơn:
- So với các luồng hệ điều hành, virtual threads có chi phí tạo và chuyển đổi ngữ cảnh đáng kể thấp hơn.
-
Tăng hiệu suất đồng thời:
- Có thể quản lý một số lượng lớn hơn các virtual threads một cách hiệu quả trong một số lượng giới hạn của các luồng hệ điều hành, giúp tận dụng tốt hơn các tài nguyên cho một số công việc cụ thể.
-
Lập trình đồng thời (concurrency ) được đơn giản hóa:
- Virtual threads loại bỏ nhu cầu quản lý và đồng bộ hóa luồng phức tạp, giúp làm đơn giản hóa lập trình đồng thời cho các nhà phát triển.
Kết bài
Nhìn chung, hành trình phát triển của JDK từ JDK 8 đến JDK 21 đã là một chặng đường dài đầy thử thách và sáng tạo. Lập trình viên Java ngày càng có những công cụ và tính năng mạnh mẽ để xây dựng ứng dụng hiện đại và linh hoạt. Tính đến JDK 21, Java không chỉ là một ngôn ngữ lập trình mạnh mẽ mà còn là một hệ sinh thái đa dạng và đáng tin cậy. Chắc chắn, những cập nhật tiếp theo của JDK sẽ tiếp tục đưa Java đến những đỉnh cao mới, giúp người phát triển không ngừng nâng cao khả năng sáng tạo và hiệu suất trong công việc của họ.