Các tính năng bổ sung và deprecated của Java
- 24-10-2023
- Toanngo92
- 0 Comments
Các tính năng đã bị loại bỏ hoặc bị đánh dấu là không còn được khuyến nghị sử dụng trong các phiên bản Java gần đây từ Java 9 đến Java 15. Một số trong số chúng bao gồm:
- Loại bỏ các hàm khởi tạo (constructors) của các lớp tương ứng với kiểu nguyên thủy (primitive types). Các lớp như Boolean, Byte, Short, Character, Integer, Long, Float và Double được coi là các lớp “box” (vì chúng có thể sử dụng autoboxing). Các hàm khởi tạo của các lớp này đã bị đánh dấu là đã lỗi thời (deprecated).
- Loại bỏ máy chạy script Nashorn và công cụ jjs. Nashorn là máy chạy script JavaScript được giới thiệu trong Java 8. Nó đã có thời gian sử dụng hợp lệ từ Java 8 đến Java 10, nhưng đã bị đánh dấu là đã lỗi thời trong Java 11. Mục tiêu chính của Nashorn là cung cấp một máy chạy JavaScript nhẹ và hiệu suất cao trong Java với JVM native.
jjs
là công cụ dòng lệnh được sử dụng cùng với Nashorn. - Các tùy chọn thu gom rác nội bộ. Một số tùy chọn thu gom rác đã bị loại bỏ (như Concurrent Mark Sweep (CMS) Garbage Collector).
- Loại bỏ var như một tên lớp hợp lệ. Từ Java 10 trở đi, bạn không thể sử dụng var như một tên lớp.
- Loại bỏ dấu gạch dưới (_) như một định danh hợp lệ. Từ Java 9 trở đi, bạn không thể sử dụng dấu gạch dưới (_) như một định danh hợp lệ.
Lớp Math trong Java chứa nhiều phương thức tích hợp để thực hiện các phép tính/sự tính toán số học cơ bản như phép mũ, logarith, căn bậc hai và các phương pháp lượng giác cơ bản. Nó nằm trong gói java.lang. Các phép tính toán toán học phức tạp hơn cũng có thể được thực hiện bằng lớp Math
. Ví dụ, việc tìm giá trị tối thiểu hoặc tối đa của các giá trị đã cho, làm tròn giá trị, tính căn bậc hai, và nhiều phép tính toán khác là hoàn toàn khả thi.
Mục lục
Các Phương thức Toán Cơ Bản
Math.abs()
Phương thức này tạo ra giá trị tuyệt đối của đầu vào đã cho trong phép tính. Math.abs()
chỉ trả về giá trị dương, ngay cả khi đầu vào đã cho chứa giá trị âm. Nó loại bỏ các giá trị âm và trả về giá trị dương. Đoạn mã 1 thể hiện một chương trình hoàn chỉnh sử dụng phương thức Math.abs()
.
Ví dụ:
public class MathAbsExample {
public static void main(String[] args) {
int negativeNumber = -10;
double negativeDouble = -15.75;
// Calculate the absolute value of an integer
int absInt = Math.abs(negativeNumber);
System.out.println("Absolute value of " + negativeNumber + " is " + absInt);
// Calculate the absolute value of a double
double absDouble = Math.abs(negativeDouble);
System.out.println("Absolute value of " + negativeDouble + " is " + absDouble);
}
}
Phương thức Math.abs()
có thể được nạp chồng (overloaded) bằng bốn cách sau:
int
double
long
float
Cụ thể, nó có thể được sử dụng với các kiểu dữ liệu int
, double
, long
, và float
khác nhau để tính giá trị tuyệt đối của chúng.
Math.ceil()
Phương thức Math.ceil()
làm tròn một giá trị số thực gần giá trị số nguyên. Giá trị sau khi được làm tròn sẽ được trả về dưới dạng một số thực (double). Đoạn mã dưới hiển thị cách sử dụng Math.ceil()
:
public class BasicMathDemo {
public static void main(String[] args) {
double objCeil = Math.ceil(6.454);
System.out.println("Result as " + objCeil);
}
}
Sau khi thực thi đoạn mã này, biến objCeil
sẽ chứa giá trị 7.0. Phương thức Math.ceil()
đã làm tròn giá trị 6.454 lên thành 7.0.
Math.floor()
Phương thức Math.floor()
làm tròn một giá trị số thực xuống (làm tròn xuống dưới) đến giá trị số nguyên gần nhất. Giá trị sau khi được làm tròn sẽ được hiển thị dưới dạng số thực (double).
public class MathFloorExample {
public static void main(String[] args) {
double number = 9.75;
double result = Math.floor(number);
System.out.println("Original Number: " + number);
System.out.println("Floor Value: " + result);
}
}
Ngoài ra, còn một số phương thức khác như:
- Math.floorDiv()
- Math.min()
- Math.max()
- Math.round()
- Math.random()
Phương thức toán mũ và logarit (Exponential and Logarithmic Math Methods)
Lớp Math
cũng chứa một bộ phương thức để thực hiện các phép tính mũ và logarithmic (logarit) tính toán. Các phương thức này được giải thích như sau:
Math.exp()
Phương thức này tạo ra giá trị e
(số Euler) mũ với giá trị được đưa ra như một tham số.
Ví dụ:
public class MathExpExample {
public static void main(String[] args) {
double x = 2.0;
// Calculate the exponential value of x
double result = Math.exp(x);
System.out.println("e^" + x + " is approximately " + result);
}
}
Math.log()
Trong Java, phương thức Math.log()
được sử dụng để tính logarithm tự nhiên (hay logarithm cơ số e) của một số. Nó tính logarithm cơ số e (cơ số tự nhiên) của một số và trả về kết quả dưới dạng một số thực.
Cú pháp của phương thức Math.log()
như sau:
double Math.log(double a);
Ở đây, a
là số mà bạn muốn tính logarithm tự nhiên của nó.
Dưới đây là một ví dụ minh họa về cách sử dụng Math.log()
:
public class MathLogExample {
public static void main(String[] args) {
double x = 10.0;
// Tính logarithm tự nhiên của x (logarithm cơ số e)
double result = Math.log(x);
System.out.println("log(" + x + ") = " + result);
}
}
Trong ví dụ này, chúng ta sử dụng phương thức Math.log()
để tính logarithm tự nhiên của số x
, trong trường hợp này x
được đặt là 10.0
. Kết quả sẽ là giá trị logarithm cơ số e của 10.0
.
Kết quả của đoạn mã này sẽ là:
log(10.0) = 2.302585092994046
Nói cách khác, logarithm tự nhiên của 10 (logarithm cơ số e) là khoảng 2.302585092994046.
Math.log10()
Phương thức Math.log10()
trong Java được sử dụng để tính logarithm cơ số 10 của một số. Nó tính logarithm cơ số 10 của một số và trả về kết quả dưới dạng một số thực.
Cú pháp của phương thức Math.log10()
như sau:
double Math.log10(double a);
Ở đây, a
là số mà bạn muốn tính logarithm cơ số 10 của nó.
Dưới đây là một ví dụ minh họa về cách sử dụng Math.log10()
:
public class MathLog10Example {
public static void main(String[] args) {
double x = 100.0;
// Tính logarithm cơ số 10 của x
double result = Math.log10(x);
System.out.println("log10(" + x + ") = " + result);
}
}
Trong ví dụ này, chúng ta sử dụng phương thức Math.log10()
để tính logarithm cơ số 10 của số x
, trong trường hợp này x
được đặt là 100.0
. Kết quả sẽ là giá trị logarithm cơ số 10 của 100.0
.
Kết quả của đoạn mã này sẽ là:
log10(100.0) = 2.0
Nói cách khác, logarithm cơ số 10 của 100 là 2.0.
Math.pow()
Phương thức Math.pow()
trong Java được sử dụng để tính luỹ thừa (exponentiation) của một số theo một số mũ khác. Nó tính giá trị của một số cơ bản (base
) mũ exponent
và trả về kết quả dưới dạng một số thực. Phương thức này có hai tham số, base
là số cơ bản và exponent
là số mũ.
Cú pháp của phương thức Math.pow()
như sau:
double Math.pow(double base, double exponent);
Dưới đây là một ví dụ minh họa về cách sử dụng Math.pow()
:
public class MathPowExample {
public static void main(String[] args) {
double base = 2.0;
double exponent = 3.0;
// Tính 2^3 (2 mũ 3)
double result = Math.pow(base, exponent);
System.out.println(base + "^" + exponent + " = " + result);
}
}
Trong ví dụ này, chúng ta sử dụng phương thức Math.pow()
để tính giá trị của 2 mũ 3 (2^3). Kết quả sẽ là giá trị của phép tính này.
Kết quả của đoạn mã này sẽ là:
2.0^3.0 = 8.0
Nói cách khác, 2 mũ 3 bằng 8.0. Phương thức Math.pow()
cho phép tính toán luỹ thừa của một số bất kỳ theo một số mũ bất kỳ.
Math.sqrt()
Phương thức Math.sqrt()
trong Java được sử dụng để tính căn bậc hai (square root) của một số. Nó tính căn bậc hai của một số dương và trả về kết quả dưới dạng một số thực. Phương thức này có một tham số là số mà bạn muốn tính căn bậc hai.
Cú pháp của phương thức Math.sqrt()
như sau:
double Math.sqrt(double a);
Dưới đây là một ví dụ minh họa về cách sử dụng Math.sqrt()
:
public class MathSqrtExample {
public static void main(String[] args) {
double number = 16.0;
// Tính căn bậc hai của 16
double result = Math.sqrt(number);
System.out.println("Căn bậc hai của " + number + " là " + result);
}
}
Trong ví dụ này, chúng ta sử dụng phương thức Math.sqrt()
để tính căn bậc hai của số number
, trong trường hợp này number
được đặt là 16.0
. Kết quả sẽ là giá trị căn bậc hai của 16.0
.
Kết quả của đoạn mã này sẽ là:
Căn bậc hai của 16.0 là 4.0
Nói cách khác, căn bậc hai của 16 là 4.0
Các phương thức toán lượng giác (Trigonometric Math Methods)
Lớp Math
cũng bao gồm một tập hợp các phương thức lượng giác có thể tính các giá trị được sử dụng trong lượng giác học, chẳng hạn như sin, cosin, tan và nhiều phương thức khác. Math.PI
là một hằng số kiểu double chứa giá trị gần với số Pi (π). Math.PI
thường được sử dụng trong các phép tính lượng giác.
Danh sách các phương thức:
- Math.sin()
- Math.cos()
- Math.asin()
- Math.acos()
- Math.atan()
- Math.sinh()
- Math.cosh()
- Math.tanh()
- Math.toDegrees()
- Math.toRadians()
Các phép toán số chính xác
Một trong những cải tiến mới trong Java 8 là lớp Math
bây giờ bao gồm một tập hợp các phương thức sẽ ném ra một ngoại lệ ArithmeticException
khi giá trị kiểu số nguyên (hoặc số long) vượt quá giới hạn tối đa. Ví dụ, phép nhân giữa 100000 và 100000 vượt quá giới hạn tối đa và sẽ dẫn đến ngoại lệ này được hiển thị. Những phép tính như vậy được gọi là “exact numeric operations” (phép tính số học chính xác).
Các phép tính số học chính xác (exact numeric operations) có thể được thực hiện bằng các phương thức sau:
addExact
: Phép cộng chính xác.subtractExact
: Phép trừ chính xác.multiplyExact
: Phép nhân chính xác.incrementExact
: Tăng giá trị một cách chính xác.decrementExact
: Giảm giá trị một cách chính xác.negateExact
: Đảo dấu một cách chính xác.toIntExact
: Chuyển đổi sang kiểu số nguyên một cách chính xác.
Ví dụ thể hiện cách sử dụng phương thức addExact()
:
public class ExactMethodDemo {
public static void main(String args[]) {
int ex1 = 50000000000000;
int ex2 = 25000000000000;
System.out.println(Math.addExact(ex1, ex2));
}
}
Lý tưởng, nếu ex1
và ex2
chỉ được cộng bằng dấu +
, chương trình sẽ không báo lỗi, nhưng sẽ tạo ra một kết quả không chính xác vì nó vượt quá giới hạn tối đa cho số nguyên. Do đó, ở đây, việc sử dụng addExact()
được khuyến nghị để nó sẽ ném ra một ngoại lệ và cảnh báo người dùng thay vì hiển thị kết quả không chính xác.
Đoạn mã 9 khi được thực thi tạo ra ngoại lệ như sau:
Exception in thread "main" java.lang.ArithmeticException: integer overflow
at java.lang.Math.addExact(Math.java:790)
Điều này cho thấy phương thức addExact()
đã ném ra một ngoại lệ “tràn số nguyên” (integer overflow) khi kết quả vượt quá giới hạn số nguyên.
Các phép toán số giá trị gần nhất/tiếp theo (Next Numeric Operations)
Lớp Math
cũng giới thiệu một tập hợp các phương thức để thực hiện các phép tính số học được áp dụng khi bạn cần hiển thị giá trị gần nhất của một số đã cho. Ví dụ, để hiển thị một giá trị lớn hơn một giá trị đã cho, bạn có thể sử dụng phương thức nextUp()
.
Dưới đây là các phương thức “next” trong lớp Math
:
nextUp
: Tìm giá trị gần nhất lớn hơn của một số đã cho.nextDown
: Tìm giá trị gần nhất nhỏ hơn của một số đã cho.nextAfter
: Tìm giá trị gần nhất theo một số đã cho.
Ví dụ:
public class MathNextDownExample {
public static void main(String[] args) {
double number = 2.0;
// Find the next smaller representable floating-point number
double nextDownNumber = Math.nextDown(number);
System.out.println("Original number: " + number);
System.out.println("Next smaller representable number: " + nextDownNumber);
}
}
Output:
Original number: 2.0
Next smaller representable number: 1.9999999999999998
Một số cách khác để khởi tạo đối tượng
Cách khác để tạo đối tượng ngoài từ khóa new
là sử dụng phương thức newInstance()
cùng với Class.forName()
nếu bạn biết tên của lớp và lớp đó có một constructor mặc định công khai. Phương thức Class.forName()
thực tế là tải lớp trong Java, nhưng không tạo bất kỳ đối tượng nào. Để tạo đối tượng của lớp, bạn phải sử dụng phương thức newInstance()
của lớp.
Ví dụ về cách sử dụng phương thức này:
public class NewInstanceDemo {
String name = "objNewInstanceDemo";
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> cls = Class.forName("NewInstanceDemo");
NewInstanceDemo obj = (NewInstanceDemo) cls.newInstance();
System.out.println(obj.name);
}
}
Trong đoạn mã này, chúng ta gán một biến chuỗi và gọi phương thức Class.forName()
với tên của lớp hiện tại. Giá trị trả về của phương thức này được gán cho biến cls
, một đối tượng của lớp Class
. Sau đó, chúng ta gọi newInstance()
trên cls
và tạo một đối tượng obj
.
Cuối cùng, chúng ta in ra tên của đối tượng. Phương thức main
có thể gây ra một số ngoại lệ tại thời điểm chạy, do đó, các ngoại lệ này được bao gồm trong khai báo của main
. Kết quả của mã sẽ là “objNewInstanceDemo”.
Sử dụng phương thức clone():
Gọi phương thức clone()
trên bất kỳ đối tượng nào sẽ khiến JVM tạo một đối tượng mới và sao chép toàn bộ nội dung của đối tượng trước đó vào nó. Việc tạo một đối tượng bằng phương thức clone
không gọi constructor. Để sử dụng phương thức clone()
trên một đối tượng, bạn phải triển khai Cloneable
và định nghĩa phương thức clone()
trong đó.
Đoạn mã 12 thể hiện một ví dụ về cách sử dụng phương thức này:
import java.util.*;
public class CloneDemo implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
String name = "objCloneDemo";
public static void main(String[] args) throws CloneNotSupportedException {
CloneDemo obj1 = new CloneDemo();
CloneDemo obj2 = (CloneDemo) obj1.clone();
System.out.println(obj2.name);
}
}
Trong đoạn mã này, chúng ta tạo ra một bản sao của một đối tượng hiện có và không phải là một đối tượng mới. Mã có thể gây ra CloneNotSupportedException
, do đó nó được khai báo với các ngoại lệ tương ứng. Kết quả của mã sẽ là “objCloneDemo”.
Sử dụng phương thức newInstance()
của lớp Constructor:
Cách này tương tự với phương thức newInstance()
của lớp thông thường. Có một phương thức newInstance()
trong lớp java.lang.reflect.Constructor
mà chúng ta có thể sử dụng để tạo đối tượng. Phương thức này cũng có thể gọi constructor có tham số và constructor riêng tư bằng cách sử dụng phương thức newInstance()
. Cả hai phương thức newInstance()
đều được gọi là các cách tạo đối tượng dựa vào reflection. Đoạn mã 13 minh họa một ví dụ về cách sử dụng phương thức này:
import java.lang.reflect.*;
public class ReflectionDemo {
private String name;
ReflectionDemo() {
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<ReflectionDemo> constructor = ReflectionDemo.class.getDeclaredConstructor();
ReflectionDemo objReflectionDemo = constructor.newInstance();
objReflectionDemo.setName("objReflectionDemo");
System.out.println(objReflectionDemo.name);
}
}
Trong đoạn mã này, chúng ta sử dụng reflection để tạo đối tượng ReflectionDemo
. Chúng ta lấy ra constructor không có tham số bằng cách sử dụng getDeclaredConstructor()
và sau đó sử dụng phương thức newInstance()
để tạo một đối tượng. Kết quả của mã sẽ là “objReflectionDemo”.
Java Modules
Trong Java 9, đã giới thiệu một cơ chế đóng gói mới cho phép các nhà phát triển đóng gói một ứng dụng Java hoặc Java API như một module Java riêng biệt. Tính năng này được gọi là ‘module’ và có thể chỉ định những gói Java mà nó chứa và những gói nào sẽ được hiển thị cho các module Java khác. Một module Java được đóng gói dưới dạng một tệp JAR modular, do đó, một module có thể được định nghĩa là một nhóm các gói và tài nguyên có liên quan cùng với một tệp mô tả module mới.
Các gói bên trong một module giống như các gói Java bạn đã sử dụng cho đến nay. Tài nguyên có thể bao gồm các tệp phương tiện, tệp cấu hình (tệp XML) và cấu hình khác.
Các lợi ích khác nhau của module Java bao gồm:
Gói ứng dụng nhỏ hơn
Trong các phiên bản Java trước đây, trước phiên bản 9, các nhà phát triển đã phải đóng gói tất cả các API Nền tảng Java với ứng dụng Java của họ vì không có cách nào để xác định chính xác các lớp Java mà ứng dụng Java của họ đã sử dụng. Điều này thường dẫn đến kích thước lớn của các ứng dụng trong quá trình triển khai và phân phối, với một lượng lớn các lớp Java được bao gồm, trong đó có nhiều lớp mà ứng dụng có thể thậm chí không sử dụng. Điều này là hạn chế tiềm năng trên các thiết bị nhỏ như thiết bị di động, Raspberry Pi, và như vậy.
Từ Java 9 trở đi, các API Nền tảng Java hiện đã được chia thành các module riêng biệt. Lợi ích của điều này là bạn có thể chỉ định các module của nền tảng Java mà ứng dụng của bạn cần. Trong quá trình triển khai, Java có thể đóng gói ứng dụng của bạn chỉ bao gồm các module Nền tảng Java mà thực sự được sử dụng bởi ứng dụng của bạn. Kết quả của điều này là gói ứng dụng nhỏ hơn.
Ẩn các chi tiết không cần thiết
Một module Java phải chỉ ra rõ những gói Java bên trong nó sẽ được xuất hoặc hiển thị cho các module khác sử dụng module này. Một module Java cũng có thể chứa các gói không được xuất. Các gói không được xuất còn được gọi là gói ẩn hoặc gói đóng gói. Lớp trong các gói không được xuất không thể được sử dụng bởi các module Java khác. Những gói như vậy chỉ có thể được sử dụng bên trong module Java mà chúng được định nghĩa.
Do đó, việc sử dụng các module có thể giúp ẩn các chi tiết không cần thiết và nội bộ một cách an toàn và nâng cao bảo mật.
Mối quan hệ ít kết nối giữa các thành phần
Các module giúp hỗ trợ mối quan hệ ít kết nối giữa các thành phần.
Phát hiện nhanh hơn các module bị thiếu
Từ Java 9 trở đi, ngay khi JVM Java khởi động, nó có thể kiểm tra biểu đồ phụ thuộc module từ module ứng dụng và xa hơn. Trong trường hợp không tìm thấy bất kỳ module nào cần thiết khi khởi động, JVM Java sẽ phát ra một lỗi về việc thiếu các module và tắt máy. Điều này không thể xảy ra trong các phiên bản Java trước phiên bản 9, nơi các lớp bị thiếu không được phát hiện cho đến khi ứng dụng thực sự cố gắng sử dụng lớp bị thiếu. Phát hiện lớp bị thiếu vào thời điểm khởi động ứng dụng tốt hơn nhiều so với việc phát hiện chúng trong quá trình chạy ứng dụng.
Các loại của module
Từ Java 9 trở đi, có bốn loại module trong hệ thống module mới:
System Modules (Module hệ thống): Đây là các module do hệ thống Java SE và JDK định nghĩa sẵn. Bạn có thể xem danh sách các module này bằng cách chạy lệnh “list-modules”. Dưới đây là hình mô tả danh sách các modules:
Application Modules (Module ứng dụng): Đây là các module mà các nhà phát triển muốn xây dựng khi họ quyết định sử dụng module. Chúng có tên và được định nghĩa trong tệp module-info.class biên dịch được bao gồm trong tệp JAR đã được tạo.
Automatic Modules (Module tự động): Bạn có thể bao gồm các module không chính thức tự động bằng cách thêm các tệp JAR hiện có vào module path. Tên của module sẽ được tạo từ tên của tệp JAR. Các module tự động sẽ có quyền truy cập đọc đầy đủ vào mọi module khác được tải bởi đường dẫn.
Unnamed Module (Module không đặt tên): Khi một lớp hoặc JAR được tải lên classpath, nhưng không phải là module path, nó sẽ tự động được thêm vào module không đặt tên.
Mục đích của module không đặt tên là duy trì tính tương thích ngược với mã Java đã được viết trước đây.
Tạo một Java Module
Để tạo một module trong Java (từ phiên bản 9 trở đi), bạn cần thực hiện các bước sau:
1. Tạo tệp module descriptor: Để thiết lập một module, bạn cần tạo một tệp đặc biệt gọi là module descriptor ở gốc của gói của bạn với tên module-info.java. Tệp này bao gồm tất cả thông tin cần thiết để xây dựng và sử dụng module mới của bạn. Bạn đặt nó trong một thư mục có tên giống tên của module của bạn. Ví dụ, nếu tên module của bạn là com.test.testmodule, tệp module-info.java của bạn nên được đặt trong thư mục com/test/testmodule.
2. Tạo khai báo module: Bạn có thể tạo module với một khai báo có thể có nội dung trống hoặc bao gồm các chỉ thị module. Ví dụ:
module com.test.testmodule {
}
3. Thêm các chỉ thị (directives): Các chỉ thị (directives) module khác nhau thực hiện các mục đích khác nhau. Dưới đây là một số ví dụ về chỉ thị:
Chỉ thị requires module cho phép bạn khai báo các phụ thuộc module. Đơn giản, từ khóa requires cho biết rằng module này phụ thuộc vào một module khác.
Cú pháp:
module my.module { requires module.name; }
Đoạn mã dưới cho biết module của chúng ta phụ thuộc vào module java.base.
module com.test.testmodule { requires java.base; }
Chỉ thị requires còn có các tùy chọn sau:
- Bằng cách sử dụng chỉ thị requires static, bạn có thể tạo denpendency compile time only.
- Chỉ thị requires transitive buộc phải đọc các phụ thuộc được yêu cầu.
Chúng ta có thể sử dụng chỉ thị exports để tiết lộ tất cả các thành viên công cộng của gói được đặt tên, như được thể hiện trong Đoạn mã dưới
module com.test.testmodule {
exports com.test.testmodule.testpackage;
}
Mặc định, tất cả các gói đều là riêng tư cho module. Từ khóa exports chỉ định rằng những gói này sẽ sẵn có cho các module khác. Điều này có nghĩa rằng các lớp công cộng mặc định chỉ có thể truy cập trong module, trừ khi được chỉ định trong khai báo module info.
Ngoài ra, một chỉ thị khác là chỉ thị uses, nó chỉ định rằng module sử dụng một service.
4. Cài đặt dự án
Việc thiết lập cấu trúc dự án trong Java có thể được thực hiện theo nhiều cách khác nhau, tùy thuộc vào nhu cầu cụ thể của dự án của bạn. Dưới đây là một ví dụ về cấu trúc thư mục cho một dự án với một module tên là testmodule
:
testmodule/
src/
com/
test/
testmodule/
module-info.java
testpackage/
TestClass.java
out/
production/
testmodule/
module-info.class
testpackage/
TestClass.class
lib/
.gitignore
- Thư mục
src/
chứa mã nguồn của moduletestmodule
. Trong đó: module-info.java
là tệp module descriptor, chứa các chỉ thị module.testpackage/
là gói chứa mã nguồn, và bạn có thể có nhiều gói khác trong đó.TestClass.java
là một ví dụ về lớp bên trong góitestpackage
.
- Thư mục
out/
chứa các tệp sau khi biên dịch dự án. production/
chứa mã nguồn được biên dịch sẵn của moduletestmodule
.- Thư mục
lib/
là nơi bạn có thể đặt thư viện hoặc phụ thuộc của dự án nếu cần. - Tệp
.gitignore
chứa các mẫu tên tệp hoặc thư mục được loại trừ khỏi Git.
Cấu trúc này chỉ là một ví dụ, và bạn có thể điều chỉnh nó để phù hợp với dự án của bạn. Chắc chắn rằng bạn tuân theo các quy tắc cụ thể của module Java và quản lý mã nguồn của mình một cách hiệu quả.
5. Thêm mã vào module và chạy ứng dụng
Dưới đây là cách bạn có thể cấu trúc thư mục và tạo tệp Main.java
:
testmodule/
src/
com/
test/
testmodule/
module-info.java
testpackage/
Main.java // Tạo tệp Main.java tại đây
out/
production/
testmodule/
module-info.class
testpackage/
Main.class
lib/
.gitignore
Sau khi bạn đã tạo Main.java
và thêm code vào đó, bạn có thể biên dịch và chạy module testmodule
. Vui lòng chắc chắn rằng bạn đã tuân thủ đúng cấu trúc package và module descriptor.
Sau đó, bạn có thể chạy ứng dụng bằng cách sử dụng lệnh sau từ thư mục gốc của dự án:
java -p out/production -m com.test.testmodule/com.test.testmodule.Main
Lệnh trên sẽ chạy class Main
trong module testmodule
và in ra thông báo “Hello World from Module!”.
Lưu ý rằng cách sử dụng java
và cờ -p
có thể thay đổi tùy theo cấu trúc dự án và môi trường của bạn.
Here’s the paragraph with improved grammar and syntax, along with the translation into Vietnamese:
Cải tiến Câu Lệnh Switch và Biểu Thức Switch
Câu lệnh switch truyền thống trong các phiên bản trước của Java đã được cải thiện trong các phiên bản gần đây để bao gồm cú pháp mới với dấu mũi tên (còn gọi là ‘quy tắc được gán cho switch – switch labeled rules’).
Ví dụ:
public class EnhancedSwitchStatementDemo {
public static void main(String[] args) {
System.out.println("Câu lệnh Switch Tối Ưu:");
final int integer = 3;
String numericString;
switch (integer) {
case 1 -> numericString = "một";
case 2 -> numericString = "hai";
case 3 -> numericString = "ba";
default -> numericString = "N/A";
}
System.out.println("\t" + integer + "\n\n" + numericString);
}
}
Cú pháp ‘mũi tên’ (‘quy tắc được gán’) thực hiện việc chuyển đổi mà không cần chỉ định rõ ràng từ khóa “break”. Điều này không chỉ làm giảm bớt mã nguồn mà quan trọng hơn là loại bỏ khả năng xảy ra hiện tượng “bỏ qua” (fall through) trong các câu lệnh switch trước đây.
Từ Java 13 và 14 trở đi, bạn có thể sử dụng biểu thức switch. Biểu thức switch là một biểu thức đa hình, có nghĩa là nếu kiểu mục tiêu đã biết, kiểu này sẽ được đưa xuống vào từng khối. Kiểu của một biểu thức switch chính là kiểu mục tiêu của nó, trong trường hợp kiểu này đã biết; nếu kiểu chưa biết, một kiểu độc lập sẽ được xác định trong khối ‘case L ->’ của nhãn switch.
Ví dụ:
public class SwitchExpressionDemo {
public static void main(String[] args) {
final int integer = 1;
System.out.println("Enhanced switch:");
final String numericString = switch (integer) {
case 1 -> "Grade A";
case 2 -> "Grade B";
case 3 -> "Grade C";
case 4 -> "Grade D";
default -> "N/A";
};
System.out.println("\t" + integer + "\n\n>" + numericString);
}
}
Vì câu lệnh switch hiện nay có thể được sử dụng như một biểu thức, bạn không cần phải bao gồm break; cho từng khối, cũng không phải lo lắng về việc tìm kiếm các break bị thiếu trong chuỗi dài của các khối khi gỡ lỗi. Điều này xảy ra vì mỗi khi nhãn case được khớp, nó chỉ kích hoạt dòng mã thích hợp, không phải là sự “rơi qua” của khối đã cho chỉ được dừng lại bằng các câu lệnh break;.
JShell
Công cụ Java Shell (JShell) là một công cụ tương tác giúp bạn thử nghiệm mã Java và dễ dàng khám phá các tùy chọn khi bạn phát triển chương trình của mình. Bạn có thể kiểm tra từng câu lệnh riêng lẻ, tìm hiểu về các API không quen thuộc trong phiên JShell và thử nghiệm các phiên bản quá tải khác nhau của một phương thức. Quan trọng phải nhớ rằng JShell không thay thế một môi trường phát triển tích hợp (IDE); thay vào đó, bạn có thể dán mã vào JShell để thử nó và sau đó dán mã hoạt động từ JShell vào trình soạn thảo chương trình hoặc IDE của bạn.
Start và stop JShell
JShell được bao gồm trong JDK từ phiên bản 9 trở đi. Để bắt đầu JShell, hãy gõ “jshell” trên dòng lệnh.
Để start JShell với chế độ verbose, gõ lệnh:
jshell -v
Để stop Jshell, gõ lệnh
/exit
Tính năng chính của JShell:
Here’s the improved paragraph with grammar and syntax corrections, along with the translation into Vietnamese:
Khai báo lại biến
Trong Java, không thể khai báo lại một biến. Tuy nhiên, với sự trợ giúp của JShell, bạn luôn có thể khai báo lại biến dựa trên yêu cầu. Lưu ý rằng điều này áp dụng cho cả biến nguyên thủy và biến tham chiếu. Trên thực tế, người dùng có thể khai báo lại bất kỳ cấu trúc nào bao nhiêu lần mà họ muốn.
Ví dụ:
jshell> String msg = "Buổi sáng"
msg
"Buổi sáng"
jshell> Integer msg = 90
90
msg
Biến tạm thời (Scratch variables)
Bất kỳ biểu thức nào được đánh giá từ dòng lệnh JShell đều được gán cho một số biến nếu không được gán một cách tường minh bởi người dùng. Những biến như vậy được gọi là biến tạm thời (scratch variables).
Ví dụ:
jshell> "Bắc" + "Ánh sáng"
$1 ==> "Bắc Ánh sáng"
Khai báo lại biến
Trong Java, không thể khai báo lại một biến. Tuy nhiên, với sự trợ giúp của JShell, bạn luôn có thể khai báo lại biến dựa trên yêu cầu. Lưu ý rằng điều này áp dụng cho cả biến nguyên thủy và biến tham chiếu. Trên thực tế, người dùng có thể khai báo lại bất kỳ cấu trúc nào bao nhiêu lần mà họ muốn.
Ví dụ:
jshell> String msg = "Hello"
msg => "Hello"
jshell> Integer msg = 10
msg => 10
Biến tạm thời (Scratch variables)
Bất kỳ biểu thức nào được đánh giá từ dòng lệnh JShell đều được gán cho một số biến nếu không được gán một cách tường minh bởi người dùng. Những biến như vậy được gọi là biến tạm thời (scratch variables).
Ví dụ:
jshell> "North" + "Hello"
$1 ==> "NorthHello"
Xử lý Ngoại lệ trong JShell
Trong ví dụ dưới đây, chúng ta không bắt bất kỳ ngoại lệ nào được ném bởi phương thức divide(). Shell tự động xử lý nó. Chúng ta cũng không nhập lớp IOException, tuy nhiên, mã vẫn được biên dịch và thực thi. Điều này xảy ra vì đối với bất kỳ phiên JShell nào, một số gói được nhập mặc định.
Ví dụ:
jshell> int divide(int num1, int num2) throws TOException {
if (num2 == 0) {
throw new IOException();
}
return num1 / num2;
}
| created method divide(int, int)
jshell> divide(34, 0)
| Java.io.IOException throưn:
| at divide (#2:3)
| at (3:1)
Nhắc code bằng Tab trong shell
JShell cho phép các nhà phát triển tự động hoàn thành cấu trúc mã của họ bằng cách sử dụng phím Tab.
Hidden Classes và Sealed Classes
Lớp ẩn (hidden class) là các lớp không thể được sử dụng trực tiếp bởi mã bytecode của các lớp khác. Lớp ẩn chủ yếu được thiết kế để sử dụng bởi các framework tạo ra các lớp tại thời gian chạy và sử dụng chúng gián tiếp thông qua phản chiếu. Lớp ẩn là một tính năng chủ yếu hữu ích cho các nhà phát triển framework và không phải là các nhà phát triển ứng dụng tổng quát.
Mốt phạm vi ‘sealed’ trong Java 15 cung cấp sự kiểm soát thừa kế tinh vi cho các lớp và giao diện. Mốt này, khi áp dụng cho một lớp hoặc giao diện, giới hạn các lớp hoặc giao diện khác có thể mở rộng nó. Các lớp bị niêm phong cũng hữu ích để tạo ra các hệ thống phân cấp an toàn bằng cách tách rời tính truy cập và tính mở rộng.
Ví dụ, hãy tưởng tượng một lĩnh vực kinh doanh chỉ làm việc với ô tô và xe tải, không phải là mô-tô. Khi tạo lớp trừu tượng ‘Ô tô’, bạn nên có khả năng cho phép chỉ các lớp ‘Ô tô’ và ‘Xe tải’ mở rộng nó. Như vậy, bạn có thể đảm bảo rằng không có sự lạm dụng của lớp trừu tượng ‘Ô tô’ trong lĩnh vực của bạn.
Tính năng niêm phong (sealed class) giới thiệu một số từ khóa và điều khoản mới trong Java: ‘sealed’, ‘non-sealed’, và ‘permits’. Điều khoản ‘permits’ sau đó chỉ định các lớp được phép thực hiện giao diện hoặc lớp bị niêm phong. Điều khoản này nên được xác định sau bất kỳ điều khoản ‘extends’ hoặc ‘implements’ nào.
Ví dụ:
public abstract sealed class Automobile permits Car, Truck {
protected final String regID;
public Automobile(String regID) {
this.regID = regID;
}
public String getRegID() {
return regID;
}
}
Một lớp con được phép phải xác định một từ khóa bổ sung. Nó có thể được khai báo là ‘final’ để ngăn chặn bất kỳ sự mở rộng nào khác.
Ví dụ:
public final class Truck extends Automobile implements Service {
private final int loadCapacity;
public Truck(int loadCapacity, String regID) {
super(regID);
this.loadCapacity = loadCapacity;
}
public int getLoadCapacity() {
return loadCapacity;
}
@Override
public int getMaxServiceIntervalInMonths() {
return 24;
}
}
Một lớp con được phép cũng có thể được khai báo là bị niêm phong. Tuy nhiên, nếu bạn khai báo nó là ‘sealed’, thì nó sẽ mở rộng cho phép.
Bài tập
Bài Tập: Quản Lý Kho Hàng
Mô tả Bài Tập:
Bạn được yêu cầu xây dựng một hệ thống quản lý kho hàng, bao gồm các loại sản phẩm khác nhau như thực phẩm, đồ điện tử, và đồ gia dụng.
1. Tạo Interface và Các Lớp:
- **Interface `IProduct`:** Định nghĩa các phương thức sau:
```java
void addStock(int quantity);
void removeStock(int quantity);
void displayInfo();
```
- **Lớp `Product`:** Cài đặt từ `IProduct`. Bao gồm các thuộc tính:
```java
private String id;
private String name;
private double price;
private int quantity;
```
- **Các lớp cụ thể kế thừa từ `Product`:**
- **`Food`:** Thêm các thuộc tính:
```java
private String expirationDate;
private boolean isPerishable;
```
Cung cấp phương thức:
```java
public void updateExpirationDate(String expirationDate);
public void displayInfo();
```
- **`Electronics`:** Thêm các thuộc tính:
```java
private String warrantyPeriod;
private String brand;
```
Cung cấp phương thức:
```java
public void extendWarranty(String additionalPeriod);
public void displayInfo();
```
- **`Household`:** Thêm các thuộc tính:
```java
private String material;
private String usage;
```
Cung cấp phương thức:
```java
public void updateUsage(String usage);
public void displayInfo();
```
2. Tạo Lớp `InventoryManager`:
- Quản lý các sản phẩm với các chức năng:
- **Thêm, xóa** (theo `ID`).
- **Tìm sản phẩm** theo `name` và `category` (thực phẩm, điện tử, đồ gia dụng).
- **Gọi phương thức `updateExpirationDate`** của `Food` theo `ID`.
- **Gọi phương thức `extendWarranty`** của `Electronics` theo `ID`.
- **Cập nhật thông tin sử dụng** của `Household` theo `ID`.
- **Hiển thị thông tin** của tất cả các sản phẩm.
3. Yêu Cầu Bổ Sung:
- Mỗi loại sản phẩm phải có cách triển khai khác nhau cho phương thức `displayInfo()`. Ví dụ: khi hiển thị thông tin thực phẩm, in ra thông tin về hạn sử dụng và tính chất có thể bảo quản, và tương tự cho các loại sản phẩm khác.
- Cập nhật thông tin sản phẩm như hạn sử dụng, thời gian bảo hành hoặc cách sử dụng thông qua các phương thức riêng biệt.
- Tạo một lớp `Main` với menu tương tác cho phép người dùng chọn các chức năng như thêm, xóa, tìm kiếm, và hiển thị thông tin sản phẩm.
- Sử dụng cấu trúc dữ liệu phù hợp như `HashMap` để lưu trữ sản phẩm và xử lý các yêu cầu hiệu quả.
Bài tập 2: quản lý SV
Hiểu rồi! Dưới đây là đề bài đã được chỉnh sửa theo yêu cầu của bạn:
### Câu 1:
Tạo Interface, tên là `IStudent`, với các phương thức sau, trong package `com.hvc`:
| Kiểu trả về | Tên |
| ----------- | --- |
| `void` | `input` |
| `void` | `display` |
---
### Câu 2:
Tạo class, tên là `Student`, triển khai interface `IStudent` và có các thuộc tính sau, trong package `com.hvc`:
| Kiểu dữ liệu | Tên |
| ------------ | --- |
| `int` | `id` |
| `String` | `fullname` |
| `String` | `email` |
| `float` | `mark` |
- Tạo 2 constructor cho class `Student`:
- Một constructor không có tham số.
- Một constructor có đủ 4 tham số để gán giá trị cho các thuộc tính.
- Tạo phương thức getter/setter cho mỗi thuộc tính.
- Triển khai phương thức `input()` và `display()` kế thừa từ interface `IStudent`:
- `input()`: cho phép người dùng nhập các giá trị cho tất cả thuộc tính của đối tượng `Student`.
- `display()`: in ra tất cả các thuộc tính của đối tượng `Student`.
---
### Câu 3:
Tạo class `StudentManagement`, trong package `com.hvc.system`, với các phương thức sau:
| Modifier | Kiểu trả về | Tên |
| -------- | ----------- | --- |
| `public` | `void` | `addStudent` |
| `public` | `void` | `showStudent` |
| `public` | `Student` | `sortStudentByMark` |
- `addStudent`: cho phép người dùng nhập 3 đối tượng `Student` vào một mảng `Student`.
- `showStudent`: duyệt qua tất cả các phần tử của mảng `Student` và hiển thị từng đối tượng.
- `sortStudentByMark`: sắp xếp mảng `Student` theo `mark`.
---
### Câu 4:
Tạo class `MainClass`, trong package `com.hvc.system`, có phương thức `main` để chạy ứng dụng. Phương thức `main` thực hiện các tác vụ sau:
Hiển thị Menu cho người dùng chọn:
```
=== MENU ===
1. Add New.
2. Show All.
3. Sort.
4. Exit.
Your choice:
```
- Khi người dùng chọn số 1: thực thi phương thức `addStudent` trong class `StudentManagement`.
- Khi người dùng chọn số 2: thực thi phương thức `showStudent` trong class `StudentManagement`.
- Khi người dùng chọn số 3: thực thi phương thức `sortStudentByMark` trong class `StudentManagement`.
- Khi người dùng chọn số 4: thoát chương trình.
Bài tập 3:
**Bài Tập: Quản Lý Thư Viện Sách**
### Mô tả Bài Tập:
Bạn được yêu cầu xây dựng một hệ thống quản lý thư viện sách, bao gồm các loại sách khác nhau như sách giáo khoa, tiểu thuyết, và tài liệu tham khảo.
### 1. Tạo Interface và Các Lớp:
- **Interface `IBook`:** Định nghĩa các phương thức sau:
```java
void addCopies(int quantity);
void removeCopies(int quantity);
void displayInfo();
```
- **Lớp `Book`:** Cài đặt từ `IBook`. Bao gồm các thuộc tính:
```java
private String id;
private String title;
private String author;
private double price;
private int quantity;
```
- **Các lớp cụ thể kế thừa từ `Book`:**
- **`Textbook`:** Thêm các thuộc tính:
```java
private String subject;
private String academicLevel;
```
Cung cấp phương thức:
```java
public void updateAcademicLevel(String academicLevel);
public void displayInfo();
```
- **`Novel`:** Thêm các thuộc tính:
```java
private String genre;
private boolean isBestseller;
```
Cung cấp phương thức:
```java
public void markAsBestseller();
public void displayInfo();
```
- **`ReferenceBook`:** Thêm các thuộc tính:
```java
private String fieldOfStudy;
private String edition;
```
Cung cấp phương thức:
```java
public void updateEdition(String edition);
public void displayInfo();
```
### 2. Tạo Lớp `LibraryManager`:
- Quản lý các sách với các chức năng:
- **Thêm, xóa** (theo `ID`).
- **Tìm sách** theo `title` và `category` (giáo khoa, tiểu thuyết, tài liệu tham khảo).
- **Cập nhật học vấn** của `Textbook` theo `ID`.
- **Đánh dấu sách bán chạy** của `Novel` theo `ID`.
- **Cập nhật phiên bản** của `ReferenceBook` theo `ID`.
- **Hiển thị thông tin** của tất cả các sách.