Mục lục
Có rất nhiều thứ đã thay đổi trong Java từ khi bắt đầu vào năm 1995 cho đến ngày nay. Java 8 là một bản phát hành mang tính cách mạng, đã đưa Java trở lại vị trí một trong các ngôn ngữ lập trình tốt nhất hiện nay.
Như lịch công bố trên website Java thì đến tháng 9/2023, Oracle sẽ công bố phiên bản Java 21 LTS. Vậy thì trong bài viết này, mình sẽ điểm lại Java đã thay đổi những gì kể từ phiên bản Java 8 LTS đến Java 17 LTS nhé.
Java 8
Lambda Expressions và Stream API
Với việc phát triển thêm tính năng Lambda và Stream API thì Java 8 đã có cuộc cách mạng khi những lập trình viên có thể dễ dàng hơn rất nhiều trong quá trình làm việc.
Thế giới trước khi có biểu thức Lambda?
Mình ví dụ bài toán:
Mình có một đại lý xe hơi. Để bỏ các thủ tục giấy tờ, mình muốn tạo một phần mềm tìm thấy tất cả những chiếc xe hiện có chạy dưới 50.000km.
Như cách thông thường, chúng ta sẽ làm bằng cách này:
Để thực hiện điều này, chúng ta sẽ tạo một hàm static với tham số là danh sách ô tô và sẽ trả về một danh sách ô tô đã được lọc theo điều kiện cụ thể.
Sử dụng Stream và Lambda Expression
Vẫn theo ví dụ trên, khách hàng chúng tôi muốn tìm tất cả các xe có cùng tiêu chí.
Đây là giải pháp khi dùng Stream và biểu thức Lambda:
Chúng ta cần chuyển danh sách xe thành một luồng bằng cách gọi phương thức stream()
. Bên trong phương thức filter()
chúng ta đang thiết lập điều kiện của mình. Và điều kiện ở đây là chỉ giữ lại những chiếc xe có số kilometers < 50.000km. Và cuối cùng đóng gói lại vào một danh sách.
Nếu bạn muốn tìm hiểu thêm về biểu thức lambda, hãy đọc thêm bài này nhé!
Method Reference
Trước khi có phương thức tham chiếu
Vẫn cái ví dụ cửa hàng đại lý ô tô, giờ mình muốn in ra tất cả những chiếc xe trong cửa hàng. Khi đó thì mình sẽ sử dụng phương thức tham chiếu.
Một phương thức tham chiếu cho phép chúng ta gọi các hàm trong các lớp bằng một loại cú pháp đặc biệt ::
Có 4 loại phương thức tham chiếu:
- Tham chiếu đến một hàm static
- Tham chiếu đến một phương thức thể hiện trên một đối tượng
- Tham chiếu đến một phương thức thể hiện trên một loại
- Tham chiếu đến một hàm khởi tạo
Khi không dùng phương thức tham chiếu thì mình sẽ làm bằng cách:
Chúng ta đang sử dụng biểu thức lambda để gọi hàm toString()
trên mỗi ô tô.
Sử dụng phương thức tham chiếu
Đây là cách sử dụng phương thức tham chiếu trong tình huống tương tự:
Chúng ta vẫn sử dụng biểu thức lambda, nhưng bây giờ gọi hàm toString() theo phương thức tham chiếu. Nhìn rất ngắn gọn và dễ đọc.
Tìm hiểu thêm phương thức tham chiếu tại đây.
Default Methods
Hãy tưởng tượng chúng ta có một hàm log(String message)
và hàm này sẽ in những thông báo khi được gọi.
Sau đó chúng ta lại muốn thêm thời gian vào hàm log để dễ dàng tìm kiếm. Tất nhiên là chúng ta không muốn khách hàng gặp lỗi trong lần chuyển đổi này. Chúng ta sẽ thực hiện việc này bằng cách triển khai phương thức mặc định trong một interface.
Triển khai phương thức mặc định là tính năng cho phép chúng ta tạo dự phòng một implementation của một phương thức trong interface.
Các trường hợp sử dụng
Đây là chương trình ban đầu chúng ta sử dụng:
Chúng ta tạo một interface với chỉ một phương thức và implementing chúng trong classLoggingImplementation
Thêm mới phương thức
Chúng ta sẽ thêm một phương thức mới trong interface. Phương thức nhận tham số thứ hai là ngày tháng hiện tại.
Chúng ta thêm mới phương thức vào trong interface nhưng không implement vào trong tất cả các class. Như vậy code sẽ thông báo lỗi:
Class 'LoggingImplementation' must either be declared abstract
or implement abstract method 'log(String, Date)' in 'Logging'`.
Sử dụng phương thức mặc định
Sau khi thêm phương thức vào trong interface, compiler sẽ trả về ngoài lê. Chúng ta sẽ triển khai phương thức mặc định cho phương thức mới.
Thêm từ khóa default
cho phép chúng ta thêm phương thức bên trong interface. Bây giờ, class LoggingImplementation không bị lỗi trình biên dịch mặc dù chúng ta không triển khai phương thức mới này bên trong nó.
Nếu bạn còn thắc mắc về Default method, tìm hiểu thêm tại đây nhé
Type Annotations
Type annotations là một tính năng nữa được giới thiệu trong Java 8. Mặc dù trước đây java đã có annotations, nhưng bây giờ chúng ta có thể sử dụng ở bất cứ nơi nào. Điều này có nghĩa chúng ta có thể sử dụng chúng trên:
- Khai báo biến cục bộ
- Gọi hàm constructor
- Type casting
- Generics
- Mệnh để clause….
Khai báo biến cục bộ
Khai báo kiểu này sẽ đảm bảo biến cục bộ không thể có giá trị null:
Chúng ta sử dụng annotation trên biến cục bộ ở đây. Bộ xử lý có thể đọc annotations @NotNull
và đưa ra ngoại lệ khi chuỗi rỗng.
Gọi hàm constructor
Chúng ta muốn đảm bảo rằng không thể tạo một chuỗi rỗng ArrayList
Đây là annotation được khai báo trên hàm tạo, điều này đảm bảo danh sách mảng không được trống.
Generics type
Một trong những yêu cầu là mỗi email phải ở dạng @.com. Nếu dùng annotation, chúng ta có thể làm điều đó một cách dễ dàng:
Đây là một biến chứa danh sách địa chỉ email. Chúng ta sử dụng annotation @Email để đảm bảo từng phần tử trong List emails đúng định dạng.
Repeating Annotations
Trong thực tế, một số lỗi nghiêm trọng khi gặp exception thì cần phải báo luôn cho chủ sở hữu và nhóm quản trị viên bảo mật của công ty.
Việc sử dụng repeating annotations cho phép chúng ta đặt nhiều annotations trên cùng một class.
Tạo một repeating annotation
Với ví dụ trên, chúng ta tạo một annotation có tên là @Notify
Chúng ta tạo @Notify
như một annotation thông thường, tuy nhiên ta sẽ thêm @Repeatable
vào annotation đó. Ngoài ra chúng ta phải tạo một vùng chứa mảng đối tượng Notify. Bộ xử lý giờ đây có quyền truy cập vào tất cả Repeating Notify
thông qua vùng chưa @Notifications
Sử dụng Repeating Annotations
Chúng ta có thể thêm repeating annotation nhiều lần ở cùng một class
Chúng ta có một custom exception class mà chúng ta sẽ throw ra những chỗ exception quan trọng. Khi service của chúng ta nhận exception này thì sẽ gửi cảnh báo đến hai email mà chúng ta đã định nghĩa ở trên.
Java 9
Java 9 giới thiệu một số tính năng mới
- Java Module System
- Try-with-resources
- Diamond Syntax with Inner Anonymous Classes
- Private Interface Methods
Java Module System
Một module là một nhóm các package, dependencies, và các tài nguyên của chúng. Nó cung cấp một tập hợp các chức năng rộng hơn so với package.
Khi tạo một module mới, chúng ta cần cung cấp một số thuộc tính:
- Name
- Dependencies
- Public Packages – by default, all packages are module private
- Services Offered
- Services Consumed
- Reflection Permissions
Tạo một dự án nhiều module trong Intellij
Đầu tiên, chúng ta tạo một ứng dụng in ra “Hello World” đơn giản. Nhưng sẽ chia ra module thứ nhất in từ “Hello “, module thứ hai in từ “World”
Chúng ta có hai Module: Hello module và World module. Bên trong mỗi module, chúng ta tạo thêm một file module-info.java.
Tệp này xác định các module trong java, bên trong chúng ta sẽ định nghĩa những module nào cần import và export.
Định nghĩa module thứ nhất
Như đã nói ở trên, thì module Hello
để in ra chứ “Hello “. Và module World
in ra chữ “World!”. Vậy điều đầu tiên chúng ta cần làm là export module World trong file module-info.java:
Chúng ta sử dụng keyword module
với tên module để định nghĩa module để tham chiếu đến module hiện tại.
Từ khoá exports
để xuất ra những package trong module.
Một số từ khoá khác có thể sử dụng như là:
- requires
- requires transitive
- exports to
- uses
- provides with
Các bạn có thể tìm hiểu thêm tại đây.
Sau đó chúng ta tạo một hàm để in ra chữ “World!”
Định nghĩa module thứ hai
Sau khi tạo và export được module World thì chúng ta tạo module Hello:
Chúng ta xác định các module phụ thuộc bằng từ khoá requires
. Theo mặc định, những module nào không được exports
thì là những module private, không được tham chiếu đến các module khác.
Và để gọi module World
thì chúng ta sẽ import chúng và gọi hàm print()
đã định nghĩa ở module World
.
Try-with-resources
Try-with-resources là một tính năng cho phép chúng ta khai báo các tài nguyên có thể tự động đóng trong một khối try-catch. Khai báo trong một khối try-catch sẽ yêu cầu JVM giải phóng chúng sau khi thực thi xong.
Kết thúc tài nguyên theo cách thủ công
Chúng ta muốn đọc văn bản bằng BufferedReader. BufferedReader là một closable resource,
vì vậy chúng ta cần đóng đúng cách sau khi sử dụng. Vậy thì trước Java 7 chúng ta làm như sau:
Đóng resource với try-with-resources
Try-catch-resource được giới thiệu đầu tiên từ bản Java 7. Vậy nó có những thay đổi gì, hãy xem nhé:
Cú pháp trên cung cấp rất nhiều tính năng và quản lý tự động các tài nguyên, tuy nhiên, chúng ta vẫn cần khai báo một biến cục bộ để làm việc với, Java 9 tinh chỉnh thêm cho tính năng này để tránh sự rườm rà.
Diamond Syntax with Inner Anonymous Classes
Chúng ta biết, Java SE 7 đã giới thiệu một tính năng mới: Diamond Operator để tránh mã dư thừa và độ dài, để cải thiện khả năng đọc. Tuy nhiên trong Java SE 8, Oracle Corp (Nhà phát triển thư viện Java) đã phát hiện ra rằng một số hạn chế trong việc sử dụng toán tử Diamond với Anonymous Inner Class. Họ đã khắc phục các vấn đề đó và sẽ phát hành như một phần của Java 9.
Private Interface Methods
Trong Java 8, chúng ta có thể cung cấp phương thức thực hiện trong các Interfaces bằng cách sử dụng các phương thức Default và Static. Tuy nhiên, chúng ta không thể tạo các phương thức Private trong Interfaces.
Để tránh mã dư thừa và khả năng sử dụng lại nhiều hơn, từ Java 9, chúng ta cũng có thể viết các phương thức private static và private trong một giao diện sử dụng từ khoá ‘private’.
(Còn nữa ~~)