Các lớp bên trong ẩn danh được sử dụng trong Java như thế nào?


309

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?


67
Đây không phải là một lợi thế của Java vì đây là một cách để khắc phục tình trạng thiếu các bao đóng trong Java.
Eric Wilson

4
Java 8 cũng đã giới thiệu các biểu thức Lambda. Kiểm tra câu trả lời: stackoverflow.com/questions/355167/
Mạnh

Câu trả lời:


369

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.


5
Hoặc bạn có thể cấu trúc lại các lớp bên trong ẩn danh thành một phương thức với lớp bên trong ẩn danh (và có thể một số mã trùng lặp khác).
Tom Hawtin - tackline

3
Câu trả lời tuyệt vời nhưng một câu hỏi nhanh. Điều đó có nghĩa là Java có thể sống mà không cần các lớp bên trong ẩn danh và chúng giống như tùy chọn bổ sung để chọn?
realPK

5
Giải thích rất rõ, thậm chí khó khăn tôi sẽ đề nghị bất cứ ai đọc cái này để tra cứu và xem những biểu thức java 8 và lambda có thể làm gì để làm cho mã hóa nhanh hơn và dễ đọc hơn.
Pievis

2
@ user2190639 Chính xác, không thể yêu cầu tốt hơn với Lambda trong Java8
bonCodigo

3
tại sao bạn nói overloading methodsvà không overriding methods?
Tarun

73

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.


20
Thật không may, tính dài dòng của việc lập trình chức năng trong java, IMHO, vượt xa lợi ích của nó - một trong những điểm nổi bật của lập trình chức năng là nó có xu hướng giảm kích thước mã, và làm cho mọi thứ dễ đọc và sửa đổi hơn. Nhưng java chức năng dường như không làm điều đó cả.
Chii

27
Tất cả sự dễ hiểu của lập trình chức năng, với sự căng thẳng của Java!
Adam Jaskiewicz

3
Theo kinh nghiệm của tôi, phong cách chức năng trong Java được trả tiền với tính dài dòng lên phía trước, nhưng nó mang lại sự ngắn gọn trong thời gian dài. Ví dụ, myList.map (f) ít dài hơn đáng kể so với vòng lặp for tương ứng.
Apocalisp

2
Scala , một ngôn ngữ kiểu lập trình chức năng, có ý định chạy tốt bên trong JVM và có thể là một tùy chọn cho các kịch bản lập trình chức năng.
Darrell Teague

51

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");
       } 
     });
   } 
 } 

8
Câu trả lời tuyệt vời, giống như 3.) là mẫu được sử dụng cho người nghe sự kiện
xdl

47

Đô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.


56
Để rõ ràng, tập hợp dấu ngoặc đầu tiên là lớp bên trong ẩn danh (phân lớp HashMap). Nhóm niềng răng thứ hai là một trình khởi tạo cá thể (chứ không phải là một hàm tĩnh), sau đó đặt các giá trị trên lớp con HashMap của bạn. +1 khi đề cập đến nó, -1 vì không đánh vần nó cho các noobs. ;-D
Spencer Kormos

4
Đọc thêm về cú pháp cú đúp ở đây .
Martin Andersson


18

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)


1
Ý bạn là nói ngắn gọn? Nếu nó dài dòng, cuộc gọi lại sẽ ở riêng, làm cho nó lớn hơn một chút, và do đó làm cho nó dài dòng. Nếu bạn nói rằng đây vẫn còn dài dòng, thì hình thức ngắn gọn là gì?
dùng3081519

1
@ user3081519, một cái gì đó giống myButton.addActionListener(e -> { /* do stuff here */})hoặc myButton.addActionListener(stuff)sẽ trở nên phức tạp hơn.
Phường Samuel Edwin

8

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.


6

GuideLines cho lớp ẩn danh.

  1. Lớp ẩn danh được khai báo và khởi tạo đồng thời.

  2. 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.

  3. 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

    }
});

Về thứ 3: Không hoàn toàn đúng. Bạn có thể có được nhiều phiên bản của một lớp ẩn danh với sự phản chiếu, ví dụ ref.getClass().newInstance().
icza

Các quy tắc không trả lời câu hỏi.
Hầu tước Lorne

5

Đú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).


1
Có quyền truy cập vào các lớp xung quanh là rất quan trọng! Tôi nghĩ rằng đây là lý do trong nhiều trường hợp sử dụng lớp ẩn danh, bởi vì nó cần / sử dụng các thuộc tính, phương thức và biến cục bộ của lớp xung quanh mà bên ngoài (nếu một lớp riêng biệt sẽ được sử dụng) được thông qua hoặc xuất bản.
icza

5
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


3

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();

3

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 Personlớ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 CheckPersonlà 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 CheckPersonlà 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 Predicatethay cho giao diện CheckPerson, điều này sẽ làm giảm thêm số lượng mã cần thiết.


2

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.


1

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 HeavyClassphương thức cần hoàn thiện.


1

Bạn có thể sử dụng lớp ẩn danh theo cách này

TreeSet treeSetObj = new TreeSet(new Comparator()
{
    public int compare(String i1,String i2)
    {
        return i2.compareTo(i1);
    }
});

1

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.

Sử dụng

Đ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!) .

Hạn chế / hạn chế

  1. Bạn nên truyền tham số chung một cách rõ ràng. Không làm như vậy sẽ dẫn đến mất tham số loại
  2. Mỗi lần khởi tạo sẽ khiến bạn phải trả thêm lớp do trình biên dịch tạo ra, dẫn đến ô nhiễm classpath / đầy hơi

1

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();
    }

}

1

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 newtừ khóa, một cuộc gọi đến hàm tạo và định nghĩa lớp bên trong {}.

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 ThreadClasssử dụng ở đây sẽ được xác định bởi người dùng. Lớp này sẽ thực hiện Runnablegiao diện cần thiết để tạo chủ đề. Trong ThreadClasscá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 tasktrong 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 Runnablegiao 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


0

Một lợi thế nữa:
Như bạn biết rằng Java không hỗ trợ nhiều kế thừa, vì vậy nếu bạn sử dụng lớp kinda "Thread" làm lớp ẩn danh thì lớp vẫn còn một khoảng trống để bất kỳ lớp nào khác mở rộng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.