Việc sử dụng các lớp ẩn danh trong Java là gì? Chúng ta có thể nói rằng việc sử dụng lớp ẩn danh là một trong những lợi thế của Java không?
Việc sử dụng các lớp ẩn danh trong Java là gì? Chúng ta có thể nói rằng việc sử dụng lớp ẩn danh là một trong những lợi thế của Java không?
Câu trả lời:
Theo một "lớp ẩn danh", ý tôi là lớp bên trong ẩn danh .
Một lớp bên trong ẩn danh có thể trở nên hữu ích khi tạo một thể hiện của một đối tượng với các "phần bổ sung" nhất định như các phương thức ghi đè, mà không phải thực sự phân lớp một lớp.
Tôi có xu hướng sử dụng nó như một phím tắt để đính kèm một trình lắng nghe sự kiện:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// do something
}
});
Sử dụng phương pháp này giúp mã hóa nhanh hơn một chút, vì tôi không cần phải tạo thêm một lớp thực hiện ActionListener
- tôi chỉ có thể khởi tạo một lớp bên trong ẩn danh mà không thực sự tạo một lớp riêng.
Tôi chỉ sử dụng kỹ thuật này cho các nhiệm vụ "nhanh và bẩn" trong đó làm cho cả lớp cảm thấy không cần thiết. Có nhiều lớp bên trong ẩn danh thực hiện chính xác cùng một thứ nên được cấu trúc lại thành một lớp thực tế, có thể là một lớp bên trong hoặc một lớp riêng biệt.
overloading methods
và không overriding methods
?
Các lớp bên trong ẩn danh có hiệu quả đóng cửa, vì vậy chúng có thể được sử dụng để mô phỏng các biểu thức lambda hoặc "đại biểu". Ví dụ: lấy giao diện này:
public interface F<A, B> {
B f(A a);
}
Bạn có thể sử dụng ẩn danh này để tạo một hàm hạng nhất trong Java. Giả sử bạn có phương thức sau trả về số đầu tiên lớn hơn i trong danh sách đã cho hoặc nếu không có số nào lớn hơn:
public static int larger(final List<Integer> ns, final int i) {
for (Integer n : ns)
if (n > i)
return n;
return i;
}
Và sau đó, bạn có một phương thức khác trả về số đầu tiên nhỏ hơn i trong danh sách đã cho hoặc i nếu không có số nào nhỏ hơn:
public static int smaller(final List<Integer> ns, final int i) {
for (Integer n : ns)
if (n < i)
return n;
return i;
}
Những phương pháp này gần như giống hệt nhau. Sử dụng hàm F loại đầu tiên, chúng ta có thể viết lại chúng thành một phương thức như sau:
public static <T> T firstMatch(final List<T> ts, final F<T, Boolean> f, T z) {
for (T t : ts)
if (f.f(t))
return t;
return z;
}
Bạn có thể sử dụng một lớp ẩn danh để sử dụng phương thức FirstMatch:
F<Integer, Boolean> greaterThanTen = new F<Integer, Boolean> {
Boolean f(final Integer n) {
return n > 10;
}
};
int moreThanMyFingersCanCount = firstMatch(xs, greaterThanTen, x);
Đây là một ví dụ thực sự dễ hiểu, nhưng thật dễ dàng để thấy rằng có thể truyền các hàm xung quanh như thể chúng là các giá trị là một tính năng khá hữu ích. Xem "Ngôn ngữ lập trình của bạn có thể làm điều này" bởi chính Joel.
Một thư viện đẹp để lập trình Java theo phong cách này: Java chức năng.
Lớp bên trong ẩn danh được sử dụng trong kịch bản sau đây:
1.) Đối với Ghi đè (Phân lớp phụ), Khi định nghĩa lớp không thể sử dụng được trừ trường hợp hiện tại:
class A{
public void methodA() {
System.out.println("methodA");
}
}
class B{
A a = new A() {
public void methodA() {
System.out.println("anonymous methodA");
}
};
}
2.) Để thực hiện giao diện, Khi chỉ thực hiện giao diện cho trường hợp hiện tại:
interface interfaceA{
public void methodA();
}
class B{
interfaceA a = new interfaceA() {
public void methodA() {
System.out.println("anonymous methodA implementer");
}
};
}
3.) Đối số được xác định Lớp bên trong ẩn danh:
interface Foo {
void methodFoo();
}
class B{
void do(Foo f) { }
}
class A{
void methodA() {
B b = new B();
b.do(new Foo() {
public void methodFoo() {
System.out.println("methodFoo");
}
});
}
}
Đôi khi tôi sử dụng chúng như một cú pháp hack để khởi tạo Map:
Map map = new HashMap() {{
put("key", "value");
}};
đấu với
Map map = new HashMap();
map.put("key", "value");
Nó tiết kiệm một số dư thừa khi thực hiện nhiều tuyên bố đặt. Tuy nhiên, tôi cũng gặp phải vấn đề khi làm điều này khi lớp bên ngoài cần được nối tiếp thông qua điều khiển từ xa.
Chúng thường được sử dụng như một hình thức gọi lại dài dòng.
Tôi cho rằng bạn có thể nói rằng họ là một lợi thế so với việc không có chúng và phải tạo một lớp có tên mỗi lần, nhưng các khái niệm tương tự được triển khai tốt hơn nhiều trong các ngôn ngữ khác (dưới dạng đóng hoặc chặn)
Đây là một ví dụ swing
myButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
// do stuff here...
}
});
Mặc dù nó vẫn còn dài dòng lộn xộn, nhưng sẽ tốt hơn nhiều so với việc buộc bạn phải xác định một lớp được đặt tên cho mỗi người nghe vứt đi như thế này (mặc dù tùy thuộc vào tình huống và tái sử dụng, đó vẫn có thể là cách tiếp cận tốt hơn)
myButton.addActionListener(e -> { /* do stuff here */})
hoặc myButton.addActionListener(stuff)
sẽ trở nên phức tạp hơn.
Bạn sử dụng nó trong các tình huống mà bạn cần tạo một lớp cho một mục đích cụ thể bên trong một chức năng khác, ví dụ như là một người nghe, như một runnable (để sinh ra một chủ đề), v.v.
Ý tưởng là bạn gọi chúng từ bên trong mã của hàm để bạn không bao giờ đề cập đến chúng ở nơi khác, vì vậy bạn không cần đặt tên cho chúng. Trình biên dịch chỉ liệt kê chúng.
Chúng chủ yếu là đường cú pháp, và thường nên được chuyển đi nơi khác khi chúng lớn hơn.
Tôi không chắc liệu đó có phải là một trong những lợi thế của Java hay không, mặc dù nếu bạn sử dụng chúng (và tất cả chúng ta thường xuyên sử dụng chúng, thì bạn có thể tranh luận rằng chúng là một.
GuideLines cho lớp ẩn danh.
Lớp ẩn danh được khai báo và khởi tạo đồng thời.
Lớp ẩn danh phải mở rộng hoặc triển khai cho một và chỉ một lớp hoặc giao diện.
Vì lớp ẩn danh không có tên, nó chỉ có thể được sử dụng một lần.
ví dụ:
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
}
});
ref.getClass().newInstance()
.
Đúng, các lớp bên trong ẩn danh chắc chắn là một trong những lợi thế của Java.
Với một lớp bên trong ẩn danh, bạn có quyền truy cập vào các biến thành viên và cuối cùng của lớp xung quanh, và điều đó có ích trong người nghe, v.v.
Nhưng một lợi thế lớn là mã lớp bên trong, (ít nhất nên được) kết hợp chặt chẽ với lớp / phương thức / khối xung quanh, có một bối cảnh cụ thể (lớp, phương thức và khối xung quanh).
new Thread() {
public void run() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
System.out.println("Exception message: " + e.getMessage());
System.out.println("Exception cause: " + e.getCause());
}
}
}.start();
Đây cũng là một trong những ví dụ cho kiểu bên trong ẩn danh sử dụng luồng
tôi sử dụng các đối tượng ẩn danh để gọi Chủ đề mới ..
new Thread(new Runnable() {
public void run() {
// you code
}
}).start();
Một lớp bên trong được liên kết với một thể hiện của lớp bên ngoài và có hai loại đặc biệt: Lớp cục bộ và Lớp ẩn danh . Một lớp ẩn danh cho phép chúng ta khai báo và khởi tạo một lớp cùng một lúc, do đó làm cho mã ngắn gọn. Chúng tôi sử dụng chúng khi chúng tôi cần một lớp địa phương chỉ một lần vì chúng không có tên.
Hãy xem xét ví dụ từ doc nơi chúng ta có một Person
lớp:
public class Person {
public enum Sex {
MALE, FEMALE
}
String name;
LocalDate birthday;
Sex gender;
String emailAddress;
public int getAge() {
// ...
}
public void printPerson() {
// ...
}
}
và chúng tôi có một phương pháp để in các thành viên phù hợp với tiêu chí tìm kiếm là:
public static void printPersons(
List<Person> roster, CheckPerson tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}
nơi CheckPerson
là một giao diện như sau:
interface CheckPerson {
boolean test(Person p);
}
Bây giờ chúng ta có thể sử dụng lớp ẩn danh thực hiện giao diện này để chỉ định tiêu chí tìm kiếm là:
printPersons(
roster,
new CheckPerson() {
public boolean test(Person p) {
return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25;
}
}
);
Ở đây giao diện rất đơn giản và cú pháp của lớp ẩn danh có vẻ khó sử dụng và không rõ ràng.
Java 8 đã giới thiệu một thuật ngữ Giao diện chức năng là một giao diện chỉ có một phương thức trừu tượng, do đó chúng ta có thể nói CheckPerson
là một giao diện chức năng. Chúng ta có thể sử dụng Lambda Expression cho phép chúng ta truyền hàm dưới dạng đối số phương thức như:
printPersons(
roster,
(Person p) -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);
Chúng ta có thể sử dụng một giao diện chức năng tiêu chuẩn Predicate
thay cho giao diện CheckPerson
, điều này sẽ làm giảm thêm số lượng mã cần thiết.
Lớp bên trong ẩn danh có thể có lợi trong khi đưa ra các triển khai khác nhau cho các đối tượng khác nhau. Nhưng nên được sử dụng rất tiết kiệm vì nó tạo ra vấn đề cho khả năng đọc chương trình.
Một trong những cách sử dụng chính của các lớp ẩn danh trong quyết toán lớp được gọi là người giám hộ quyết định . Trong thế giới Java, sử dụng các phương thức hoàn thiện nên tránh cho đến khi bạn thực sự cần chúng. Bạn phải nhớ, khi bạn ghi đè phương thức hoàn thiện cho các lớp con, bạn phải luôn gọisuper.finalize()
, vì phương thức hoàn thiện của siêu lớp sẽ không tự động gọi và bạn có thể gặp rắc rối với rò rỉ bộ nhớ.
Vì vậy, xem xét thực tế được đề cập ở trên, bạn chỉ có thể sử dụng các lớp ẩn danh như:
public class HeavyClass{
private final Object finalizerGuardian = new Object() {
@Override
protected void finalize() throws Throwable{
//Finalize outer HeavyClass object
}
};
}
Sử dụng kỹ thuật này, bạn cảm thấy nhẹ nhõm cho chính mình và các nhà phát triển khác của bạn để gọi super.finalize()
cho từng lớp con của HeavyClass
phương thức cần hoàn thiện.
Có vẻ như không ai đề cập ở đây nhưng bạn cũng có thể sử dụng lớp ẩn danh để giữ đối số loại chung (thường bị mất do xóa kiểu) :
public abstract class TypeHolder<T> {
private final Type type;
public TypeReference() {
// you may do do additional sanity checks here
final Type superClass = getClass().getGenericSuperclass();
this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
public final Type getType() {
return this.type;
}
}
Nếu bạn khởi tạo lớp này theo cách ẩn danh
TypeHolder<List<String>, Map<Ineger, Long>> holder =
new TypeHolder<List<String>, Map<Ineger, Long>>() {};
sau đó như vậy holder
dụ sẽ chứa định nghĩa không xóa của kiểu đã qua.
Điều này rất thuận tiện cho việc xây dựng trình xác nhận / giải thích. Ngoài ra, bạn có thể khởi tạo kiểu chung với sự phản chiếu (vì vậy nếu bạn muốn làm new T()
theo kiểu tham số - bạn được chào đón!) .
Cách tốt nhất để tối ưu hóa mã. Ngoài ra, chúng ta có thể sử dụng cho một phương thức ghi đè của một lớp hoặc giao diện.
import java.util.Scanner;
abstract class AnonymousInner {
abstract void sum();
}
class AnonymousInnerMain {
public static void main(String []k){
Scanner sn = new Scanner(System.in);
System.out.println("Enter two vlaues");
int a= Integer.parseInt(sn.nextLine());
int b= Integer.parseInt(sn.nextLine());
AnonymousInner ac = new AnonymousInner(){
void sum(){
int c= a+b;
System.out.println("Sum of two number is: "+c);
}
};
ac.sum();
}
}
Một lớp bên trong ẩn danh được sử dụng để tạo một đối tượng sẽ không bao giờ được tham chiếu lại. Nó không có tên và được khai báo và tạo trong cùng một tuyên bố. Điều này được sử dụng trong đó bạn thường sử dụng biến của một đối tượng. Bạn thay thế biến bằng new
từ khóa, một cuộc gọi đến hàm tạo và định nghĩa lớp bên trong {
và }
.
Khi viết một Chương trình có luồng bằng Java, nó thường sẽ trông như thế này
ThreadClass task = new ThreadClass();
Thread runner = new Thread(task);
runner.start();
Việc ThreadClass
sử dụng ở đây sẽ được xác định bởi người dùng. Lớp này sẽ thực hiện Runnable
giao diện cần thiết để tạo chủ đề. Trong ThreadClass
các run()
phương pháp (phương pháp duy nhất trong Runnable
) cần phải được xây dựng chặt chẽ. Rõ ràng là thoát khỏiThreadClass
sẽ hiệu quả hơn và đó chính xác là lý do tại sao các Lớp Nội tâm Vô danh tồn tại.
Nhìn vào đoạn mã sau
Thread runner = new Thread(new Runnable() {
public void run() {
//Thread does it's work here
}
});
runner.start();
Mã này thay thế tham chiếu được thực hiện task
trong ví dụ hàng đầu. Thay vì có một lớp riêng biệt, Lớp bên trong ẩn danh bên trong hàm Thread()
tạo sẽ trả về một đối tượng không tên, thực hiện Runnable
giao diện và ghi đè run()
phương thức. Phương thức run()
này sẽ bao gồm các câu lệnh bên trong thực hiện công việc theo yêu cầu của luồng.
Trả lời câu hỏi về việc liệu Lớp bên trong ẩn danh có phải là một trong những lợi thế của Java hay không, tôi sẽ phải nói rằng tôi không chắc lắm vì hiện tại tôi không quen với nhiều ngôn ngữ lập trình. Nhưng những gì tôi có thể nói là nó chắc chắn là một phương pháp mã hóa nhanh hơn và dễ dàng hơn.
Tài liệu tham khảo: Sams Dạy bản thân Java trong 21 ngày Phiên bản thứ bảy