Interface (Giao diện) và Nested Class (lớp lồng) trong Java
- 08-10-2023
- Toanngo92
- 0 Comments
Java không hỗ trợ kế thừa đa lớp. Tuy nhiên, có một số trường hợp khi việc kế thừa các thuộc tính từ nhiều lớp trở nên bắt buộc để tránh sự lặp lại và phức tạp trong mã code. Để đáp ứng mục tiêu này, Java cung cấp một giải pháp bằng cách sử dụng các giao diện (interfaces).
Mục lục
Khái niệm Interface
Một interface trong Java là một hợp đồng chỉ định các tiêu chuẩn phải được tuân thủ bởi các loại mà nó thực hiện. Các lớp chấp nhận hợp đồng phải tuân theo nó.
Một interface và một class tương tự nhau trong các khía cạnh sau:
- Một interface có thể chứa nhiều phương thức.
- Một interface được lưu trữ với phần mở rộng .java và tên của tệp phải phù hợp với tên của interface giống như một lớp Java.
- Bytecode của một interface cũng được lưu trữ trong một tệp class.
- Các interface được lưu trữ trong các gói và tệp bytecode được lưu trữ trong một cấu trúc thư mục phù hợp với tên gói.
Tuy nhiên, một interface và một class khác biệt về một số điểm sau đây:
- Một interface không thể được khởi tạo.
- Một interface không thể có các hàm khởi tạo.
- Tất cả các phương thức của một interface được ngầm định là trừu tượng (abstract).
- Các trường được khai báo trong một interface nếu có phải là cả static và final. Interface không thể có trường thể hiện (instance variable).
- Một interface không được mở rộng mà phải được triển khai bởi một class.
- Một interface có thể mở rộng nhiều interface khác.
Mục đích của các Giao diện (Interface)
Các đối tượng trong Java tương tác với thế giới bên ngoài thông qua các phương thức được tiết lộ bởi chúng. Do đó, có thể nói rằng các phương thức đóng vai trò như giao diện của đối tượng với thế giới bên ngoài.
Điều này tương tự như các nút trên một chiếc TV. Các nút này đóng vai trò như giao diện giữa người dùng và mạch điện và dây cáp ở phía bên kia của vỏ nhựa. Khi nút ‘nguồn’ được nhấn, chiếc TV được bật và tắt.
Trong Java, một giao diện là một tập hợp các phương thức liên quan mà không có phần thân . Các phương thức này tạo thành hợp đồng mà lớp thực hiện (instance class) phải tuân theo. Khi một lớp triển khai một giao diện, nó trở nên chính thức hơn về hành vi mà nó hứa hẹn cung cấp. Hợp đồng này được áp dụng vào thời gian biên dịch bởi trình biên dịch. Nếu một lớp triển khai một giao diện, tất cả các phương thức được khai báo bởi giao diện đó phải xuất hiện trong lớp đang triển khai để lớp đó biên dịch thành công.
Do đó, ở trong Java, một giao diện là một loại tham chiếu tương tự như một lớp.
Tuy nhiên, nó chỉ có thể chứa các khai báo phương thức, hằng số và các kiểu dữ liệu lồng nhau. Phương thức sẽ không định nghĩa mà chỉ khai báo (abstract method). Ngoài ra, khác với một lớp, giao diện không thể được khởi tạo và phải được thực hiện bởi các lớp khác hoặc được mở rộng (extend) bởi các giao diện khác để sử dụng chúng.
Có một số tình huống trong kỹ thuật phần mềm khi cần thiết cho các nhóm phát triển khác nhau phải đồng ý với một ‘hợp đồng’ xác định cách phần mềm của họ tương tác. Tuy nhiên, mỗi nhóm nên có tự do viết mã của họ theo cách mong muốn mà không cần biết cách các nhóm khác đang viết mã của họ. Giao diện Java có thể được sử dụng để xác định các hợp đồng như vậy.
Giao diện không thuộc bất kỳ hệ thống lớp nào, mặc dù chúng hoạt động cùng với các lớp. Java không cho phép đa kế thừa nên giao diện cung cấp một giải pháp thay thế. Trong Java, một lớp chỉ có thể kế thừa từ một lớp, nhưng có thể triển khai nhiều giao diện. Do đó, các đối tượng của một lớp có thể có nhiều kiểu như kiểu của lớp chính nó cũng như kiểu của các giao diện mà lớp đó triển khai. Cú pháp cho việc khai báo một giao diện như sau:
Cú pháp:
<visibility> interface <interface_name> extends <other_interfaces, .. > {
// Declare constants (public static final)
// Declare abstract methods (public abstract)
}
Trong đó:
<visibility>: Cho biết quyền truy cập của giao diện. Quyền truy cập của một giao diện luôn luôn là public.
<interface_name>: Cho biết tên của giao diện.
<other_interfaces>: Danh sách các giao diện mà giao diện hiện tại kế thừa từ chúng.
Ví dụ:
public interface Sample extends Interface1 {
int someInteger = 42; // Constants in interfaces are implicitly static and final
void someMethod(); // Abstract method in the interface
}
Trong Java, tên của các giao diện thường được viết theo kiểu CamelCase, tức là chữ cái đầu của mỗi từ được viết hoa. Tên của giao diện thường mô tả một hoạt động mà một lớp có thể thực hiện. Ví dụ:
- IEnumerable (Liệt kê)
- IComparable (So sánh)
Thường theo quy ước các lập trình viên bao gồm mình có thể thêm tiền tố “I” (ngắn gọn cho “Interface”) vào tên giao diện để phân biệt chúng với các lớp, tuy nhiên lưu ý tiền tố này là tùy chọn.
Lưu ý rằng khai báo phương thức không có dấu ngoặc nhọn và được kết thúc bằng dấu chấm phẩy (;).
Phần thân của giao diện chỉ chứa các phương thức trừu tượng và không có phương thức cụ thể. Tuy nhiên, vì tất cả các phương thức trong một giao diện đều được gán mặc định là trừu tượng, nên từ khóa abstract không được chỉ định rõ ràng với chữ ký phương thức.
Khi một lớp triển khai một giao diện, nó phải triển khai tất cả các phương thức của giao diện đó. Nếu lớp không triển khai tất cả các phương thức, nó phải được đánh dấu là trừu tượng. Ngoài ra, nếu lớp triển khai giao diện là lớp trừu tượng, một trong các lớp con của nó phải triển khai các phương thức chưa được triển khai. Một lần nữa, nếu bất kỳ lớp con trừu tượng nào của lớp trừu tượng không triển khai tất cả các phương thức của giao diện, lớp con đó cũng phải được đánh dấu là trừu tượng.
Các thành viên dữ liệu của một giao diện được gán mặc định là static, final và public.
Xem xét cấu trúc của các phương tiện vận chuyển trong đó IVehicle là giao diện khai báo các phương thức có thể được định nghĩa bởi các lớp triển khai như TwoWheeler, FourWheeler, và các lớp khác. Để tạo một giao diện mới trong NetBeans IDE, nhấp chuột phải vào tên gói và chọn New -> Java Interface như trong hình dưới:
Xuất hiện một hộp thoại nơi người dùng phải cung cấp tên cho giao diện và sau đó, nhấp vào OK. Thao tác này sẽ tạo ra một giao diện có tên được chỉ định.
Ví dụ định nghĩa interface IVehicle:
public interface IVehicle {
// Declare and initialize a constant
static final String STATE = "Idle"; // Constants are public, static, and final by default
// Abstract method to start a vehicle
void start();
// Abstract method to accelerate a vehicle
void accelerate(int speed);
// Abstract method to apply a brake
void brake();
// Abstract method to stop a vehicle
void stop();
}
Đoạn mã 1 định nghĩa một giao diện IVehicle với một hằng số tĩnh String là biến STATEID. Ngoài ra, nó khai báo một số phương thức trừu tượng như start(), accelerate(int), brake(), và stop(). Để sử dụng giao diện, cần một lớp để triển khai giao diện đó. Lớp được tạo ra để triển khai giao diện phải định nghĩa tất cả các phương thức này.
Cú pháp triển khai interface cho class:
Syntax:
class <class_name> implements <Interface1>, <Interface2>, ...
// Class members
// Overrides abstract methods of the interface(s)
Ví dụ định nghĩa class TwoWheeler triển khai interface IVehicle:
package session;
// Định nghĩa giao diện IVehicle
public interface IVehicle {
// Khai báo hằng số
String STATE = "Idle";
// Phương thức trừu tượng để bắt đầu một phương tiện
void start();
// Phương thức trừu tượng để tăng tốc một phương tiện
void accelerate(int speed);
// Phương thức trừu tượng để áp dụng phanh cho phương tiện
void brake();
// Phương thức trừu tượng để dừng một phương tiện
void stop();
}
// Định nghĩa lớp TwoWheeler thực thi giao diện IVehicle
class TwoWheeler implements IVehicle {
String ID; // Biến để lưu số xe
String type; // Biến để lưu loại xe
// Constructor có tham số để khởi tạo giá trị dựa trên đầu vào của người dùng
public TwoWheeler(String ID, String type) {
this.ID = ID;
this.type = type;
}
// Phương thức ghi đè, bắt đầu một phương tiện
@Override
public void start() {
System.out.println("Khởi động " + type);
}
// Phương thức ghi đè, tăng tốc một phương tiện
@Override
public void accelerate(int speed) {
System.out.println("Tăng tốc với tốc độ: " + speed + " km/h");
}
// Phương thức ghi đè, áp dụng phanh cho phương tiện
@Override
public void brake() {
System.out.println("Áp dụng phanh");
}
// Phương thức ghi đè, dừng một phương tiện
@Override
public void stop() {
System.out.println("Dừng " + type);
}
// Phương thức hiển thị thông tin của phương tiện
public void displayDetails() {
System.out.println("Số xe: " + STATE + " " + ID);
System.out.println("Loại xe: " + type);
}
public static void main(String[] args) {
// Kiểm tra số lượng đối số dòng lệnh
if (args.length == 3) {
// Khởi tạo đối tượng TwoWheeler
TwoWheeler objBike = new TwoWheeler(args[0], args[1]);
// Gọi các phương thức của lớp
objBike.displayDetails();
objBike.start();
objBike.accelerate(Integer.parseInt(args[2]));
objBike.brake();
objBike.stop();
} else {
System.out.println("Sử dụng: java TwoWheeler <ID> <Type> <Speed>");
}
}
}
Đoạn mã trên định nghĩa lớp TwoWheeler triển khai giao diện IVehicle. Lớp này bao gồm một số biến thể hiện và một constructor để khởi tạo các biến này. Lưu ý rằng lớp triển khai tất cả các phương thức của giao diện IVehicle. Phương thức displayDetails() được sử dụng để hiển thị thông tin chi tiết về phương tiện được chỉ định.
Phương thức main() được định nghĩa trong một lớp khác là TestVehicle. Trong phương thức main(), số lượng đối số dòng lệnh được xác minh và tùy theo đó, đối tượng của lớp TwoWheeler được tạo ra. Tiếp theo, đối tượng này được sử dụng để gọi các phương thức khác của lớp.
Hình dưới hiển thị kết quả của mã khi người dùng truyền “CS-2723 Bike 80” như đối số dòng lệnh.
Triển khai nhiều Interface
Java không hỗ trợ đa thừa kế của các lớp, nhưng cho phép triển khai nhiều giao diện để mô phỏng đa thừa kế. Để triển khai nhiều giao diện, bạn viết tên giao diện sau từ khóa “implements” và phân tách chúng bằng dấu phẩy. Ví dụ:
public class Sample implements Interface1, Interface2 {
// Các thành viên của lớp
}
Đoạn code dưới mô tả ví dụ về interface Manufacturer:
package session;
public interface IManufacturer {
/**
* Abstract method to add contact details.
*
* @param detail a String variable storing manufacturer detail
*/
public void addContact(String detail);
/**
* Abstract method to call the manufacturer.
*
* @param phone a String variable storing phone number
*/
public void callManufacturer(String phone);
/**
* Abstract method to make payment.
*
* @param amount a float variable storing the amount
*/
public void makePayment(float amount);
}
Giao diện IManufacturer khai báo 3 phương thức trừu tượng là addContact(String), callManufacturer(String) và makePayment(Float) mà các lớp triển khai phải được định nghĩa.
Lớp TwoWheeler đã chỉnh sửa để triển khai cả giao diện IVehicle và IManufacturer được hiển thị trong đoạn mã dưới:
package comm;
class TwoWheeler implements IVehicle, IManufacturer {
String ID; // Biến để lưu số xe
String type; // Biến để lưu loại xe
// Constructor có tham số để khởi tạo giá trị dựa trên đầu vào của người dùng
public TwoWheeler(String ID, String type) {
this.ID = ID;
this.type = type;
}
// Phương thức ghi đè, bắt đầu một phương tiện
@Override
public void start() {
System.out.println("Khởi động " + type);
}
// Phương thức ghi đè, tăng tốc một phương tiện
@Override
public void accelerate(int speed) {
System.out.println("Tăng tốc với tốc độ: " + speed + " km/h");
}
// Phương thức ghi đè, áp dụng phanh cho phương tiện
@Override
public void brake() {
System.out.println("Áp dụng phanh");
}
// Phương thức ghi đè, dừng một phương tiện
@Override
public void stop() {
System.out.println("Dừng " + type);
}
// Phương thức hiển thị thông tin của phương tiện
public void displayDetails() {
System.out.println("Số xe: " + STATE + " " + ID);
System.out.println("Loại xe: " + type);
}
public static void main(String[] args) {
// Kiểm tra số lượng đối số dòng lệnh
if (args.length == 3) {
// Khởi tạo đối tượng TwoWheeler
TwoWheeler objBike = new TwoWheeler(args[0], args[1]);
// Gọi các phương thức của lớp
objBike.displayDetails();
objBike.start();
objBike.accelerate(Integer.parseInt(args[2]));
objBike.brake();
objBike.stop();
objBike.addContact("abc");
objBike.callManufacturer("def");
objBike.makePayment(Float.parseFloat("1000"));
} else {
System.out.println("Sử dụng: java TwoWheeler <ID> <Type> <Speed>");
}
}
@Override
public void addContact(String detail) {
System.out.println("Nhà sản xuất: " + detail);
}
@Override
public void callManufacturer(String phone) {
System.out.println("Gọi tới nhà sản xuất: " + phone);
}
@Override
public void makePayment(float amount) {
System.out.println("Số tiền phải thanh toán: $" + amount);
}
}
Lớp TwoWheeler hiện đã triển khai cả hai giao diện; IVehicle và IManufacturer. Nó cũng triển khai tất cả các phương thức của cả hai giao diện.
Hình dưới hiển thị kết quả của mã khi người dùng truyền CS-2737 Bike 80 BN-Bikes 808-283-2828 300 như là đối số dòng lệnh.
Private Methods trong Interfaces
Java 9 giới thiệu phương thức riêng tư và phương thức tĩnh riêng tư trong giao diện. Các phương thức này chỉ hiển thị trong phạm vi giao diện mà chúng được khai báo, vì vậy nên sử dụng phương thức riêng tư cho mã nhạy cảm.
Có một số hướng dẫn cho việc sử dụng phương thức private trong giao diện, một số trong số đó như sau:
- Phương thức private trong giao diện không thể là abstract và bạn không thể sử dụng các từ khóa private và abstract cùng lúc.
- Phương thức private chỉ có thể sử dụng bên trong một giao diện và các phương thức giao diện tĩnh và không tĩnh khác.
- Phương thức non-static private không thể sử dụng trong phương thức static private.
Ví dụ:
package sessiontest;
interface Person {
default void display1(String msg) {
msg += " from display1";
printMessage(msg);
}
default void display2(String msg) {
msg += " from display2";
printMessage(msg);
}
private void printMessage(String msg) {
System.out.println(msg);
}
}
public class Employee implements Person {
public void printInterface() {
display1("Hello there");
display2("Hi there");
}
public static void main(String[] args) {
Employee objEmployee = new Employee();
objEmployee.printInterface();
}
}
Ở đây, Person là một giao diện khai báo hai phương thức mặc định, display1 và display2 một cách lần lượt và một phương thức riêng tư, printMessage. Phương thức riêng tư này chỉ có thể được gọi bên trong display1 hoặc display2 và không thể gọi từ bên ngoài của giao diện. Trong lớp Employee, mà triển khai Person, chúng ta định nghĩa một phương thức printInterface mà lần lượt gọi display1 và display2 với các tham số String thích hợp. Như vậy, phương thức printMessage có thể chứa một số hành động và đồng thời bị hạn chế chỉ trong phạm vi giao diện. Các lợi ích chính của phương thức riêng tư trong giao diện là bạn có thể thực hiện tính năng tái sử dụng mã và hạn chế quyền truy cập của các triển khai phương thức cụ thể đến các lớp hoặc giao diện khác.
Hiểu về ý tưởng của Abstract (trừu tượng)
Abstract là một yếu tố quan trọng trong lập trình hướng đối tượng. Trong Java, nó được định nghĩa là quá trình che giấu các chi tiết không cần thiết và chỉ tiết lộ các đặc điểm cần thiết của một đối tượng cho người dùng. Abstraction là một khái niệm được sử dụng bởi các lớp bao gồm các thuộc tính và phương thức thực hiện các hoạt động trên các thuộc tính này.
Abstraction cũng có thể được đạt được thông qua sự kết hợp. Ví dụ, một lớp Vehicle được tạo thành từ một động cơ, lốp xe, chìa khóa khởi động và nhiều thành phần khác. Để xây dựng lớp Vehicle, người ta không cần phải biết về cách hoạt động bên trong của các thành phần khác nhau, mà chỉ cần biết cách tương tác hoặc giao tiếp với chúng. Đó là, gửi và nhận các thông điệp đến và từ chúng cũng như làm cho các đối tượng khác nhau tạo nên lớp Vehicle tương tác với nhau.
Trong Java, các lớp trừu tượng và giao diện được sử dụng để thực hiện khái niệm về trừu tượng. Một lớp trừu tượng hoặc giao diện không phải là một lớp cụ thể. Nói cách khác, nó là một lớp không hoàn chỉnh. Để sử dụng một lớp trừu tượng hoặc giao diện, người ta cần mở rộng hoặc triển khai các phương thức trừu tượng với hành vi cụ thể phù hợp với ngữ cảnh sử dụng.
Trừu tượng được sử dụng để định nghĩa một đối tượng dựa trên các thuộc tính, chức năng và giao diện của nó.
Sự khác biệt giữa abstract class và interface:
Abstract Class | Interface |
Một lớp trừu tượng có thể có biến không phải là final. | Biến được khai báo trong một giao diện là final. |
Lớp trừu tượng có thể có các thành viên với các bộ phận truy cập khác nhau như private, protected, và nhiều loại khác. | Thành viên của giao diện mặc định là public. |
Lớp trừu tượng được kế thừa bằng cách sử dụng extends. | Giao diện được triển khai bằng cách sử dụng implements. |
Một lớp trừu tượng có thể kế thừa từ một lớp khác và triển khai nhiều giao diện. | Một giao diện có thể kế thừa từ một hoặc nhiều giao diện khác. |
Trong Java, Abstraction (Trừu tượng) và Encapsulation (Bao gói) là hai khái niệm quan trọng trong lập trình hướng đối tượng. Cả hai khái niệm này hoàn toàn khác nhau. Trong đó:
- Abstraction (Trừu tượng) đề cập đến việc trích xuất hành vi của một đối tượng mà không quan trọng “Làm thế nào” nó được thực hiện, trong khi Encapsulation (đóng gói) đề cập đến việc ẩn chi tiết của cài đặt để đảm bảo rằng bất kỳ thay đổi nào trong một lớp không ảnh hưởng đến các lớp phụ thuộc. Một số khác biệt giữa hai khái niệm này bao gồm:
- Trừu tượng được thực hiện bằng cách sử dụng một giao diện (interface) và một lớp trừu tượng (abstract class), trong khi tính đóng gói được triển khai bằng cách sử dụng quyền truy cập private, default hoặc package-private, và protected.
- Đóng gói còn được gọi là “ẩn dữ liệu”.
- Cơ sở của nguyên tắc thiết kế “lập trình cho giao diện chứ không phải cài đặt” là Trừu tượng, và cơ sở của “đóng gói mọi thay đổi” là đóng gói (Encapsulation).
Nested Class (lớp lồng nhau)
Java cho phép định nghĩa một lớp trong một lớp khác. Một lớp như vậy được gọi là lớp lồng nhau (Nested Class) như trong hình:
Cú pháp:
class Outer{
...
class Nested{
...
}
}
Trong đó, Lớp Outer là lớp bao ngoài và Lớp Nested là lớp được định nghĩa bên trong Lớp Outer.
Các lớp lồng nhau trong Java được phân loại thành lớp tĩnh (static) và lớp không tĩnh (non-static). Các lớp lồng nhau được khai báo tĩnh được gọi đơn giản là các lớp lồng tĩnh trong khi các lớp lồng nhau không tĩnh được gọi là các lớp nội bộ (class inner). Sự phân biệt này được thể hiện trong Đoạn mã dưới:
class Outer{
static class StaticNested {
// Định nghĩa lớp lồng tĩnh
}
class Inner {
// Định nghĩa lớp nội bộ
}
}
Ở đây, Lớp StaticNested là một lớp lồng tĩnh đã được khai báo tĩnh trong khi Lớp Inner là một lớp lồng nhau không tĩnh (lớp nội bộ).
Một lớp lồng nhau được coi là thành viên của lớp bao nó. Lưu ý rằng các lớp lồng nhau không tĩnh hoặc lớp nội bộ có thể truy cập các thành viên của lớp bao ngoài ngay cả khi chúng được khai báo là private. Ngược lại, các lớp lồng tĩnh không thể truy cập bất kỳ thành viên nào khác của lớp bao ngoài. Là một thành viên của lớp bao, một lớp lồng nhau có thể có bất kỳ quyền truy cập nào như public, private, protected hoặc mặc định (default – bao gói).
Lợi ích của Việc Sử dụng lớp lồng nhau:
Các lý do cho việc giới thiệu tính năng hữu ích này của việc định nghĩa lớp lồng trong Java như sau:
- Tạo nhóm hợp lý của các lớp: Nếu một lớp chỉ có ích cho một lớp khác, thì nó có thể được nhúng bên trong lớp đó và hai lớp có thể được giữ cùng nhau. Nói cách khác, điều này giúp nhóm các chức năng liên quan lại với nhau. Việc lồng chúng trong các “lớp trợ giúp” như vậy giúp làm cho gói (package) trở nên hiệu quả và được tối ưu hóa hơn.
- Tăng tính đóng gói: Trong trường hợp của hai lớp cấp cao như lớp A và B, khi lớp B muốn truy cập vào các thành viên của A mà được khai báo là private, lớp B có thể được lồng vào trong lớp A để B có thể truy cập các thành viên được khai báo là private. Ngoài ra, điều này sẽ ẩn lớp B khỏi toàn cục bên ngoài. Do đó, nó giúp truy cập tất cả các thành viên của lớp bao ngoài cùng mặc dù chúng được khai báo là private.
- Tăng khả năng đọc và bảo trì mã: Việc lồng các lớp nhỏ trong các lớp cấp cao lớn hơn giúp đặt mã gần nơi nó sẽ được sử dụng.
Các loại lớp lồng nhau khác nhau được thể hiện như các ý dưới:
- Các lớp thành viên hoặc lớp lồng không tĩnh (Member classes hoặc non-static nested classes)
- Các lớp cục bộ (Local classes)
- Các lớp vô danh (Anonymous classes)
- Các lớp lồng tĩnh (Static Nested classes)
Lớp thành viên (Member Classes)
Một lớp thành viên (member class) là một lớp lồng không tĩnh (non-static inner class). Nó được khai báo là một thành viên của lớp bên ngoài hoặc lớp bao. Lớp thành viên không thể có từ khóa static vì nó liên quan đến các thể hiện của lớp bên ngoài. Một lớp lồng có thể truy cập trực tiếp tất cả các thành viên, tức là trường và phương thức của lớp bao, bao gồm cả những thành viên private. Tuy nhiên, ngược lại không đúng. Tức là, lớp bao không thể truy cập trực tiếp các thành viên của lớp lồng, ngay cả khi chúng được khai báo là public. Điều này là do các thành viên của một lớp lồng được khai báo trong phạm vi của lớp lồng.
Một lớp lồng có thể được khai báo là public, private, protected, abstract, hoặc final. Các thể hiện (instance) của lớp lồng tồn tại trong một thể hiện của lớp bao. Để khởi tạo một lớp lồng, người dùng phải tạo một thể hiện của lớp bao. Sau đó, họ có thể truy cập đối tượng lớp lồng trong đối tượng lớp bao bằng câu lệnh được định nghĩa trong đoạn mã dưới:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
Ví dụ:
package session;
class Server {
String port; // Variable to store port number
/**
* Connects to the specified server.
*
* @param IP a String variable storing the IP address of the server
* @param port a String variable storing the port number of the server
*/
public void connectServer(String IP, String port) {
System.out.println("Connecting to Server at: " + IP + ":" + port);
}
/**
* Define an inner class.
*/
class IPAddress {
/**
* Returns the IP address of a server.
*
* @return String
*/
String getIP() {
return "101.232.28.12";
}
}
}
/**
* Define the class TestConnection and save the code as TestConnection.java.
*/
public class TestConnection {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// Check the number of command line arguments
if (args.length > 0) {
// Instantiate the outer class
Server objServer = new Server();
// Instantiate the inner class using the outer class object
Server.IPAddress objIP = objServer.new IPAddress();
// Invoke connectServer() method with the IP returned from getIP() method
// of the inner class
objServer.connectServer(objIP.getIP(), args[0]);
} else {
System.out.println("Usage: java Server <port-no>");
}
}
}
Lớp Server là một lớp bao (outer class) chứa biến port đại diện cho cổng mà máy chủ sẽ kết nối. Ngoài ra, phương thức connectServer(String, String) chấp nhận địa chỉ IP và port làm tham số. Lớp lồng IPAddress bao gồm phương thức getIP() trả về địa chỉ IP của máy chủ.
Phương thức main() được định nghĩa trong lớp TestConnection. Trong phương thức main(), số lượng đối số được kiểm tra và sau đó đối tượng của lớp Server được tạo ra tùy theo điều kiện. Tiếp theo, đối tượng objServer1 được sử dụng để tạo đối tượng objIP của lớp lồng IPAddress. Cuối cùng, đối tượng lớp bao được sử dụng để gọi phương thức connectServer(). Địa chỉ IP được lấy bằng câu lệnh objIP.getIP().
Output khi sử dụng argument là 8080:
Local Class (Lớp cục bộ)
Một lớp lồng (inner class) được định nghĩa trong một khối mã như thân của một phương thức, constructor hoặc khởi tạo (initializer) được gọi là lớp lồng cục bộ (local inner class). Phạm vi của một lớp lồng cục bộ chỉ nằm trong khối mã cụ thể đó. Khác với một lớp lồng thông thường, một lớp lồng cục bộ không phải là thành viên của lớp bao và do đó, nó không thể có bất kỳ bộ xác định truy cập nào. Nghĩa là, nó không thể sử dụng các từ khóa như public, protected, private hoặc static. Tuy nhiên, nó có thể truy cập tất cả các thành viên của lớp bao cũng như biến cục bộ được khai báo trong phạm vi mà nó được định nghĩa.
Lớp lồng cục bộ có các đặc điểm sau:
- Nó liên kết với một phiên bản/thể hiện (instance) của lớp bao.
- Nó có thể truy cập bất kỳ thành viên nào, bao gồm các thành viên riêng tư, của lớp bao.
- Nó có thể truy cập bất kỳ biến cục bộ, tham số của phương thức hoặc tham số ngoại lệ nào nằm trong phạm vi định nghĩa của phương thức cục bộ, miễn là chúng được khai báo là final.
Ví dụ:
package session;
class Employee {
/**
* Evaluates employee status.
*
* @param empID a String variable storing employee ID
* @param empAge an integer variable storing employee age
*/
public void evaluateStatus(String empID, int empAge) {
// Local final variable
final int age = 40;
/**
* Local inner class Rank
*/
class Rank {
/**
* Returns the rank of an employee.
*
* @param empID a String variable that stores the employee ID
* @return char
*/
public char getRank(String empID) {
System.out.println("Getting Rank of employee: " + empID);
// Assuming that rank 'A' was returned from the server
return 'A';
}
}
// Check the specified age
if (empAge >= age) {
// Instantiate the Rank class
Rank objRank = new Rank();
// Retrieve the employee's rank
char rank = objRank.getRank(empID);
// Verify the rank value
if (rank == 'A') {
System.out.println("Employee rank is: " + rank);
System.out.println("Status: Eligible for upgrade");
} else {
System.out.println("Status: Not Eligible for upgrade");
}
} else {
System.out.println("Status: Not Eligible for upgrade");
}
}
}
/**
* Define the class TestEmployee and save the code as TestEmployee.java.
*/
public class TestEmployee {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// Check the number of command line arguments
if (args.length == 2) {
// Object of the outer class
Employee objEmp1 = new Employee();
// Invoke the evaluateStatus() method
objEmp1.evaluateStatus(args[0], Integer.parseInt(args[1]));
} else {
System.out.println("Usage: java Employee <Emp-Id> <age>");
}
}
}
Lớp Employee là lớp bao với một phương thức có tên là evaluateStatus(String, int). Phương thức này bao gồm một biến cục bộ final có tên là age. Lớp Rank là một lớp lồng cục bộ trong phương thức.
Lớp Rank bao gồm một phương thức getRank() trả về hạng của nhân viên được chỉ định.
Tiếp theo, nếu tuổi của nhân viên lớn hơn 40, đối tượng của lớp Rank được tạo ra và hạng được lấy ra. Nếu hạng bằng ‘A’ thì nhân viên đủ điều kiện để nâng cấp, ngược lại thì nhân viên không đủ điều kiện.
Phương thức main() được định nghĩa trong lớp TestEmployee. Trong phương thức main(), một đối tượng objEmp1 của lớp Employee được tạo ra và phương thức evaluateStatus() được gọi với các đối số thích hợp.
Output khi người dùng đưa tham số đầu vào Employee Id là E001 và age là 50:
Anonymous class (lớp ẩn danh)
Lớp nội danh (Anonymous inner class) là một lớp được khai báo mà không có tên, thường được định nghĩa bên trong một khối mã như thân của một phương thức. Do không có tên, lớp nội danh chỉ có thể truy cập tại điểm mà nó được định nghĩa.
Lớp nội danh là một loại của lớp cục bộ (local class) và không thể sử dụng các từ khóa extends và implements, cũng không thể chỉ định các từ khóa truy cập như public, private, protected và static. Nó không thể định nghĩa một hàm khởi tạo (constructor), các trường (fields) tĩnh (static), phương thức tĩnh (static), hoặc các lớp tĩnh (static). Ngoài ra, lớp nội danh không thể triển khai các giao diện (interface) ẩn danh bởi vì một giao diện không thể triển khai mà không có tên.
Vì lớp nội danh không có tên, nó không thể có một hàm khởi tạo có tên, nhưng có thể có một khối khởi tạo thể hiện (instance initializer). Quy tắc để truy cập một lớp nội danh giống như quy tắc cho lớp cục bộ.
Thông thường, lớp nội danh được sử dụng để triển khai một lớp cha hoặc triển khai một interface và chứa các triển khai của các phương thức. Lớp nội danh có phạm vi giới hạn trong lớp bao bọc (outer class) và có thể truy cập các thành viên và phương thức nội bộ hoặc riêng tư của lớp bao bọc. Lớp nội danh thường được sử dụng khi bạn cần kiểm soát việc truy cập vào chi tiết nội bộ của một lớp khác và khi bạn chỉ cần một thể hiện duy nhất của một lớp đặc biệt.
Ví dụ:
package anonymousclass;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class AnonymousClassComparatorExample {
static class MyComparator<T> implements Comparator<T> {
@Override
public int compare(T o1, T o2) {
if(o1 instanceof Integer && o2 instanceof Integer){
return (Integer)o2 - (Integer)o1;
}
return 0;
}
}
public static void main(String[] args) {
// Create a list of integers
List<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
numbers.add(4);
// viet tuong minh
// MyComparator<Integer> mcp = new MyComparator<Integer>();
// numbers.sort(mcp);
// anonymous class
// numbers.sort(new Comparator<Integer>() {
// @Override
// public int compare(Integer num1, Integer num2) {
// // Sort in descending order
// return num2 - num1;
// }
// });
// using lambda expression
// numbers.sort( (num1,num2) -> {
// // todo
// return num1 - num2;
// });
numbers.sort((num1, num2) -> num2 - num1);
// MyComparator<Integer> mcp = new MyComparator<Integer>();
// Collections.sort(numbers, mcp);
// Print the sorted list
System.out.println("Sorted numbers (descending): " + numbers);
}
}
Lớp Authenticate bao gồm một đối tượng ẩn danh (anonymous object) thuộc kiểu Account. Phương thức displayBalance(String) được sử dụng để lấy và hiển thị số dư của tài khoản cụ thể. Phương thức main() được định nghĩa trong lớp TestAuthentication. Trong phương thức main(), một đối tượng của lớp Authenticate được tạo ra. Số lượng đối số dòng lệnh được kiểm tra. Nếu số lượng đối số đúng, tên người dùng và mật khẩu sẽ được kiểm tra và tùy thuộc vào đó, đối tượng của lớp ẩn danh được sử dụng để gọi phương thức displayBalance() với số tài khoản là đối số.
Hình dưới cho thấy đầu ra của mã nguồn khi người dùng truyền vào các đối số ‘admin‘, ‘abc@123‘, và ‘akdle26152‘:
Static Nested Class (lớp lồng tĩnh)
Một lớp lồng tĩnh (static nested class) liên quan đến lớp ngoài giống như biến và phương thức. Một lớp lồng tĩnh không thể trực tiếp tham chiếu đến các biến hoặc phương thức thể hiện của lớp ngoài giống như các phương thức tĩnh, nhưng có thể truy cập thông qua một tham chiếu đối tượng. Lớp lồng tĩnh, theo cách hoạt động, là một lớp cấp cao mà đã được lồng vào một lớp cấp cao khác để thuận tiện trong việc đóng gói.
Các lớp lồng tĩnh được truy cập bằng cách sử dụng tên đầy đủ của lớp, tức là, OuterClass.StaticNestedClass. Một lớp lồng tĩnh có thể có các bộ chỉ thị truy cập public, protected, private, default hoặc package private, final và abstract.
Ví dụ:
Tạo file AtmMachine.java
package staticnestedclass;
import java.util.Calendar;
class AtmMachine {
/**
* Define the static nested class.
*/
static class BankDetails {
// Instantiate the Calendar class of java.util package
static Calendar objNow = Calendar.getInstance();
/**
* Displays the bank and transaction details.
*/
public static void printDetails() {
System.out.println("State Bank of America");
System.out.println("Branch: New York");
System.out.println("Code: K39832KSIE");
// Retrieving current date and time using Calendar object
System.out.println("Date-Time: " + objNow.getTime());
}
}
/**
* Displays balance.
*
* @param accNo a String variable that stores the account number
*/
public void displayBalance(String accNo) {
// Assume that the server returns $200,000
System.out.println("Balance of account number " + accNo.toUpperCase() + " is $200,000");
}
}
Tạo file TestAtm.java
class TestAtm {
public static void main(String[] args) {
AtmMachine atmmc = new AtmMachine();
atmmc.displayBalance("1234567890");
AtmMachine.BankDetails.printDetails();
}
}
Output:
State Bank of America
Branch: New York
Code: K39832KSIE
Date-Time: Mon Oct 16 10:58:17 ICT 2023
Balance of account number ATM001 is $200,000
Lớp AtmMachine bao gồm một lớp lồng tĩnh có tên là BankDetails. Lớp lồng tĩnh tạo một đối tượng của lớp Calendar trong gói java.util. Lớp Calendar chứa các phương thức tích hợp để thiết lập hoặc truy xuất ngày và giờ hệ thống. Phương thức printDetails() được sử dụng để in ra thông tin ngân hàng cùng với ngày và giờ hiện tại sử dụng phương thức getTime() của lớp Calendar.
Phương thức main() được định nghĩa trong lớp TestAtm. Trong phương thức main(), số lượng đối số dòng lệnh được kiểm tra và sau đó một đối tượng của lớp AtmMachine được tạo. Tiếp theo, phương thức tĩnh printDetails() của lớp lồng tĩnh BankDetails được gọi và truy cập trực tiếp bằng tên lớp của lớp ngoài AtmMachine. Cuối cùng, đối tượng objatm của lớp ngoài được sử dụng để gọi phương thức displayBalance() của lớp ngoài với số tài khoản là đối số.
Kết quả đầu ra của mã khi người dùng truyền ‘akd1e26152’ làm số tài khoản.
Lưu ý rằng đầu ra về ngày và giờ hiển thị định dạng mặc định được chỉ định trong việc triển khai của phương thức getTime(). Định dạng có thể được sửa đổi theo yêu cầu của người dùng bằng cách sử dụng lớp SimpleDateFormat trong gói java.text. Lớp SimpleDateFormat là một lớp cụ thể được sử dụng để định dạng và phân tích các ngày theo cách cụ thể cho từng khu vực. Lớp SimpleDateFormat cho phép xác định các mẫu được định nghĩa bởi người dùng cho định dạng ngày-giờ. Mã được hiển thị trong ví dụ phía dưới là phiên bản đã được sửa đổi của lớp BankDetails sử dụng lớp SimpleDateFormat:
import java.text.SimpleDateFormat;
import java.util.Calendar;
class AtmMachine {
/**
* Define the static nested class.
*/
static class BankDetails {
// Instantiate the Calendar class of java.util package
static Calendar objNow = Calendar.getInstance();
/**
* Displays the bank and transaction details.
*/
public static void printDetails() {
System.out.println("State Bank of America");
System.out.println("Branch: New York");
System.out.println("Code: K39832KSIE");
// Format the output of date-time using SimpleDateFormat class
SimpleDateFormat objFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
// Retrieve the current date and time using Calendar object
System.out.println("Date-Time: " + objFormat.format(objNow.getTime()));
}
/**
* Displays balance.
*
* @param accNo a String variable that stores the account number
*/
public void displayBalance(String accNo) {
// Assume that the server returns $200,000
System.out.println("Balance of account number " + accNo.toUpperCase() + " is $200,000");
}
}
public static void main(String[] args) {
if (args.length == 1) {
// Instantiate the outer class
AtmMachine objAtm = new AtmMachine();
// Invoke the static nested class method using the outer class object
AtmMachine.BankDetails.printDetails();
// Invoke the instance method of the outer class
objAtm.displayBalance(args[0]);
} else {
System.out.println("Usage: java AtmMachine <account-no>");
}
}
}
Lớp SimpleDateFormat có hàm tạo nhận mẫu ngày dưới dạng một chuỗi. Trong đoạn code trên, mẫu ‘dd/MM/yyyy HH:mm:ss’ sử dụng một số biểu tượng là các ký tự mẫu được nhận diện bởi lớp SimpleDateFormat. Bảng sau đây liệt kê các ký tự mẫu được sử dụng trong mã với mô tả của chúng:
- ‘dd’: Ngày trong tháng (01-31)
- ‘MM’: Tháng trong năm (01-12)
- ‘yyyy’: Năm bốn chữ số
- ‘HH’: Giờ trong ngày (00-23)
- ‘mm’: Phút trong giờ (00-59)
- ‘ss’: Giây trong phút (00-59)
Bài tập
Bài 1:
Sử dụng interface & abstract class triển khai ứng dụng sau:
Một thư viện cần quản lý các tài liệu bao gồm Sách, Tạp chí, Báo. Mỗi tài liệu gồm có các thuộc tính sau: Mã tài liệu(Mã tài liệu là duy nhất), Tên nhà xuất bản, số bản phát hành.
Các loại sách cần quản lý thêm các thuộc tính: tên tác giả, số trang.
Các tạp chí cần quản lý thêm: Số phát hành, tháng phát hành.
Các báo cần quản lý thêm: Ngày phát hành.
Yêu cầu 1: Xây dựng các lớp để quản lý tài liệu cho thư viện một cách hiệu quả.
Yêu cầu 2: Xây dựng lớp QuanLySach có các chức năng sau
- Thêm mới tài liêu: Sách, tạp chí, báo.
- Xoá tài liệu theo mã tài liệu.
- Hiện thị thông tin về tài liệu.
- Tìm kiếm tài liệu theo loại: Sách, tạp chí, báo.
- Thoát khỏi chương trình
Bài 2:
Sử dụng interface & abstract class triển khai ứng dụng sau:
Các thí sinh dự thi đại học bao gồm các thí sinh thi khối A, B, và khối C. Các thí sinh cần quản lý các thông tin sau: Số báo danh, họ tên, địa chỉ, mức ưu tiên.
Thí sinh thi khối A thi các môn: Toán, Lý, Hoá.
Thí sinh thi khối B thi các môn: Toán, Hoá, Sinh.
Thí sinh thi khối C thi các môn: Văn, Sử, Địa.
Yêu cầu 1: Xây dựng các lớp để quản lý các thi sinh dự thi đại học.
Yêu cầu 2: Xây dựng lớp TuyenSinh có các chức năng:
- Thêm mới thí sinh.
- Hiện thị thông tin của thí sinh và khối thi của thí sinh.
- Tìm kiếm theo số báo danh.
- Thoát khỏi chương trình.