Jak anonimowe klasy wewnętrzne są używane w Javie?


Odpowiedzi:


369

Przez „anonimową klasę” rozumiem, że masz na myśli anonimową klasę wewnętrzną .

Anonimowa klasa wewnętrzna może się przydać, gdy tworzy się instancję obiektu z pewnymi „dodatkami”, takimi jak metody przesłonięcia, bez konieczności faktycznej podklasy klasy.

Zwykle używam go jako skrótu do dołączania detektora zdarzeń:

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // do something
    }
});

Zastosowanie tej metody sprawia, że ​​kodowanie jest trochę szybsze, ponieważ nie muszę tworzyć dodatkowej klasy, która się implementuje ActionListener- mogę po prostu utworzyć anonimową klasę wewnętrzną bez tworzenia osobnej klasy.

Używam tej techniki tylko do zadań „szybkich i brudnych”, w których tworzenie całej klasy wydaje się niepotrzebne. Posiadanie wielu anonimowych klas wewnętrznych, które robią dokładnie to samo, należy przekształcić w klasę rzeczywistą, czy to klasę wewnętrzną, czy osobną.


5
Lub możesz zmienić powielone anonimowe klasy wewnętrzne w jedną metodę za pomocą anonimowej klasy wewnętrznej (i ewentualnie innego powielonego kodu).
Tom Hawtin - tackline

3
Świetna odpowiedź, ale szybkie pytanie. Czy to oznacza, że ​​Java może żyć bez anonimowych klas wewnętrznych i są one dodatkową opcją do wyboru?
realPK

5
Bardzo dobrze wyjaśnione, nawet trudne Sugeruję, aby każdy, kto to czyta, spojrzał w górę i zobaczył, co mogą zrobić wyrażenia java 8 i lambda, aby kodowanie było szybsze i bardziej czytelne.
Pievis

2
@ user2190639 Dokładnie, nie mogę prosić o lepszą wersję
bonCodigo

3
dlaczego powiedziałeś, overloading methodsa nie overriding methods?
Tarun

73

Anonimowe klasy wewnętrzne są skutecznie zamknięciami, więc można ich używać do emulacji wyrażeń lambda lub „delegatów”. Na przykład weź ten interfejs:

public interface F<A, B> {
   B f(A a);
}

Możesz użyć tego anonimowo, aby stworzyć pierwszorzędną funkcję w Javie. Załóżmy, że masz następującą metodę, która zwraca pierwszą liczbę większą niż i na podanej liście lub i, jeśli żadna liczba nie jest większa:

public static int larger(final List<Integer> ns, final int i) {
  for (Integer n : ns)
     if (n > i)
        return n;
  return i;
}

A następnie masz inną metodę, która zwraca pierwszą liczbę mniejszą niż i na podanej liście lub i, jeśli żadna liczba nie jest mniejsza:

public static int smaller(final List<Integer> ns, final int i) {
   for (Integer n : ns)
      if (n < i)
         return n;
   return i;
}

Te metody są prawie identyczne. Używając funkcji pierwszej klasy typu F, możemy przepisać je na jedną z metod w następujący sposób:

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

Możesz użyć anonimowej klasy, aby użyć metody firstMatch:

F<Integer, Boolean> greaterThanTen = new F<Integer, Boolean> {
   Boolean f(final Integer n) {
      return n > 10;
   }
};
int moreThanMyFingersCanCount = firstMatch(xs, greaterThanTen, x);

To naprawdę wymyślony przykład, ale łatwo zauważyć, że możliwość przekazywania funkcji tak, jakby były wartościami, jest dość przydatną funkcją. Zobacz „Czy Twój język programowania może to zrobić” autorstwa samego Joela.

Ładna biblioteka do programowania Java w tym stylu: funkcjonalna Java.


20
niestety, gadatliwość robienia programowania funkcjonalnego w Javie, IMHO, przewyższa jego zyski - jednym z uderzających punktów programowania funkcjonalnego jest to, że ma tendencję do zmniejszania rozmiaru kodu oraz ułatwia czytanie i modyfikację. Ale funkcjonalna Java wcale tego nie robi.
Chii,

27
Cała zrozumiałość programowania funkcjonalnego, zwięzłość języka Java!
Adam Jaskiewicz,

3
Z mojego doświadczenia wynika, że ​​za funkcjonalny styl w Javie płaci się z dużą dokładnością, ale na dłuższą metę daje zwięzłość. Na przykład myList.map (f) jest znacznie mniej gadatliwy niż odpowiadająca mu pętla for.
Apocalisp

2
Scala , funkcjonalny język programowania, prawdopodobnie działa dobrze w JVM i może być opcją dla scenariuszy programowania funkcjonalnego.
Darrell Teague,

51

Anonimowa klasa wewnętrzna jest używana w następującym scenariuszu:

1.) W przypadku przesłonięcia (podklasy), gdy definicja klasy nie jest użyteczna z wyjątkiem bieżącego przypadku:

class A{
   public void methodA() {
      System.out.println("methodA");
    }
}
class B{
    A a = new A() {
     public void methodA() {
        System.out.println("anonymous methodA");
     }
   };
}

2.) W przypadku implementacji interfejsu, gdy implementacja interfejsu jest wymagana tylko w bieżącym przypadku:

interface interfaceA{
   public void methodA();
}
class B{
   interfaceA a = new interfaceA() {
     public void methodA() {
        System.out.println("anonymous methodA implementer");
     }
   };
}

3.) Anonimowa klasa wewnętrzna zdefiniowana w argumencie:

 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
Świetna odpowiedź, wygląda na 3.) to wzorzec używany dla detektorów zdarzeń
xdl

47

Czasami używam ich jako hacków składniowych do tworzenia instancji Map:

Map map = new HashMap() {{
   put("key", "value");
}};

vs

Map map = new HashMap();
map.put("key", "value");

Oszczędza to nadmiarowości przy wykonywaniu wielu instrukcji put. Jednak napotkałem również problemy podczas robienia tego, gdy klasa zewnętrzna musi być serializowana poprzez zdalne sterowanie.


56
Żeby było jasne, pierwszym zestawem nawiasów klamrowych jest anonimowa klasa wewnętrzna (podklasa HashMap). Drugi zestaw nawiasów klamrowych to inicjator instancji (a nie statyczny), który następnie ustawia wartości w podklasie HashMap. +1 za wzmiankę o tym, -1 za nie przeliterowanie go dla noobów. ;-D
Spencer Kormos,

4
Przeczytaj więcej o składni podwójnego nawiasu klamrowego tutaj .
Martin Andersson,


18

Są one powszechnie stosowane jako pełna forma oddzwaniania.

Przypuszczam, że można powiedzieć, że są zaletą w porównaniu z ich brakiem i koniecznością tworzenia nazwanej klasy za każdym razem, ale podobne koncepcje są wdrażane znacznie lepiej w innych językach (jako zamknięcia lub bloki)

Oto przykład huśtawki

myButton.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent e) {
        // do stuff here...
    }
});

Mimo że wciąż jest bałaganiarski, jest o wiele lepszy niż zmuszanie cię do zdefiniowania nazwanej klasy dla każdego takiego wyrzucającego słuchacza (chociaż w zależności od sytuacji i ponownego użycia może to być lepsze podejście)


1
Miałeś na myśli „krótkie”? Jeśli byłby pełny, wywołanie zwrotne pozostałoby osobno, co czyni go nieco większym, a tym samym czyniącym go bardziej szczegółowym. Jeśli powiesz, że to wciąż jest pełne, jaka byłaby wtedy zwięzła forma?
user3081519

1
@ user3081519, coś podobnego myButton.addActionListener(e -> { /* do stuff here */})lub myButton.addActionListener(stuff)byłoby terser.
Samuel Edwin Ward

8

Używasz go w sytuacjach, w których musisz utworzyć klasę do określonego celu w innej funkcji, np. Jako słuchacz, jako uruchamialny (w celu odrodzenia wątku) itp.

Chodzi o to, że wywołujesz je z wnętrza kodu funkcji, abyś nigdy nie odwoływał się do nich gdzie indziej, więc nie musisz ich nazywać. Kompilator po prostu je wylicza.

Zasadniczo są one cukrem syntaktycznym i powinny być przenoszone gdzie indziej, gdy rosną.

Nie jestem pewien, czy jest to jedna z zalet Javy, ale jeśli z nich korzystasz (i wszyscy często je wykorzystujemy, niestety), możesz argumentować, że są jedną z nich.


6

GuideLines dla anonimowej klasy.

  1. Anonimowa klasa jest deklarowana i inicjowana jednocześnie.

  2. Anonimowa klasa musi rozszerzyć lub zaimplementować jedną lub tylko jedną klasę lub interfejs.

  3. Ponieważ klasa anonimowa nie ma nazwy, można jej użyć tylko raz.

na przykład:

button.addActionListener(new ActionListener(){

            public void actionPerformed(ActionEvent arg0) {
        // TODO Auto-generated method stub

    }
});

Odnośnie # 3: Nie do końca prawda. Możesz zdobyć wiele instancji anonimowej klasy z refleksją, np ref.getClass().newInstance().
icza

Reguły nie odpowiadają na pytanie.
Markiz Lorne

5

Tak, anonimowe klasy wewnętrzne są zdecydowanie jedną z zalet Javy.

Dzięki anonimowej klasie wewnętrznej masz dostęp do zmiennych końcowych i zmiennych klasy otaczającej, co przydaje się w słuchaczach itp.

Ale główną zaletą jest to, że wewnętrzny kod klasy, który jest (przynajmniej powinien być) ściśle powiązany z otaczającą klasą / metodą / blokiem, ma określony kontekst (otaczająca klasa, metoda i blok).


1
Dostęp do otaczającej klasy jest bardzo ważny! Myślę, że jest to powód w wielu przypadkach, w których używana jest klasa anonimowa, ponieważ potrzebuje ona / wykorzystuje niepubliczne atrybuty, metody i zmienne lokalne otaczającej klasy / metody, które poza nią (gdyby zastosowano odrębną klasę) musiałyby być przekazywane lub publikowane.
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();

Jest to również jeden z przykładów anonimowego typu wewnętrznego używającego wątku


3

używam anonimowych obiektów do wywoływania nowych wątków ..

new Thread(new Runnable() {
    public void run() {
        // you code
    }
}).start();

3

Klasa wewnętrzna jest związana z wystąpieniem zewnętrznej klasy i są dwa specjalne rodzaje: klasa lokalna i klasa Anonymous . Anonimowa klasa umożliwia nam deklarowanie i tworzenie instancji klasy w tym samym czasie, dzięki czemu kod jest zwięzły. Używamy ich, gdy potrzebujemy lokalnej klasy tylko raz, ponieważ nie mają nazwy.

Rozważ przykład z dokumentu, w którym mamy Personklasę:

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
        // ...
    }

    public void printPerson() {
        // ...
    }
}

i mamy metodę drukowania członków spełniających kryteria wyszukiwania jako:

public static void printPersons(
    List<Person> roster, CheckPerson tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

gdzie CheckPersonjest interfejs taki jak:

interface CheckPerson {
    boolean test(Person p);
}

Teraz możemy skorzystać z anonimowej klasy, która implementuje ten interfejs do określenia kryteriów wyszukiwania jako:

printPersons(
    roster,
    new CheckPerson() {
        public boolean test(Person p) {
            return p.getGender() == Person.Sex.MALE
                && p.getAge() >= 18
                && p.getAge() <= 25;
        }
    }
);

Tutaj interfejs jest bardzo prosty, a składnia anonimowej klasy wydaje się niewygodna i niejasna.

Java 8 wprowadziła termin Interfejs funkcjonalny, który jest interfejsem z tylko jedną metodą abstrakcyjną, dlatego możemy powiedzieć, że CheckPersonjest interfejsem funkcjonalnym. Możemy skorzystać z wyrażenia Lambda, które pozwala nam przekazać funkcję jako argument metody jako:

printPersons(
                roster,
                (Person p) -> p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25
        );

PredicateZamiast interfejsu możemy użyć standardowego interfejsu funkcjonalnego CheckPerson, co dodatkowo zmniejszy ilość wymaganego kodu.


2

Anonimowa klasa wewnętrzna może być korzystna, dając różne implementacje dla różnych obiektów. Ale należy go używać bardzo oszczędnie, ponieważ stwarza to problemy z czytelnością programu.


1

Jednym z głównych zastosowań anonimowych klas w procesie finalizacji klas, który nazywał opiekuna finalizatora . W świecie Java należy unikać stosowania metod finalizacji, dopóki nie będą one naprawdę potrzebne. Musisz pamiętać, że kiedy przesłonisz metodę finalizacji dla podklas, zawsze powinieneś wywoływaćsuper.finalize() , ponieważ metoda finalizacji superklasy nie wywoła się automatycznie i możesz mieć problemy z wyciekami pamięci.

uwzględniając powyższy fakt, możesz po prostu użyć anonimowych klas, takich jak:

public class HeavyClass{
    private final Object finalizerGuardian = new Object() {
        @Override
        protected void finalize() throws Throwable{
            //Finalize outer HeavyClass object
        }
    };
}

Korzystając z tej techniki, odciążyłeś siebie i innych programistów od wywołania super.finalize()każdej podklasy, HeavyClassktóra wymaga metody finalizacji.


1

W ten sposób możesz użyć anonimowej klasy

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

1

Wygląda na to, że nikt tu nie był wymieniony, ale możesz także użyć klasy anonimowej do przechowywania ogólnego argumentu typu (który zwykle traci się z powodu usunięcia typu) :

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

Jeśli utworzysz tę klasę w sposób anonimowy

TypeHolder<List<String>, Map<Ineger, Long>> holder = 
    new TypeHolder<List<String>, Map<Ineger, Long>>() {};

to takie holder instancja będzie zawierać nieusuniętą definicję przekazywanego typu.

Stosowanie

Jest to bardzo przydatne do budowania walidatorów / deserializatorów. Możesz także utworzyć typ ogólny z odbiciem (więc jeśli kiedykolwiek chciałeś zrobić new T()w parametryzowanym typie - nie ma za co!) .

Wady / ograniczenia

  1. Powinieneś przekazać parametr ogólny jawnie. Niezastosowanie się do tego spowoduje utratę parametru typu
  2. Każda instancja będzie kosztować dodatkową klasę generowaną przez kompilator, co prowadzi do zanieczyszczenia ścieżki / wzdęcia słoika

1

Najlepszy sposób na optymalizację kodu. możemy również użyć do nadrzędnej metody klasy lub interfejsu.

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

Anonymous Klasa wewnętrzna służy do tworzenia obiektu, który nigdy nie zostanie ponownie odwołuje. Nie ma nazwy, jest zadeklarowany i utworzony w tej samej instrukcji. Jest to używane tam, gdzie normalnie użyłbyś zmiennej obiektu. Zmienną zamieniasz newsłowo kluczowe, wywołanie konstruktora i definicję klasy wewnątrz {i }.

Podczas pisania programu wątkowego w Javie zwykle wyglądałby tak

ThreadClass task = new ThreadClass();
Thread runner = new Thread(task);
runner.start();

ThreadClassStosowane tutaj byłoby zdefiniowane przez użytkownika. Ta klasa zaimplementuje Runnableinterfejs wymagany do tworzenia wątków. W (metoda tylko w ) musi zostać wdrożony, jak również. Oczywiste jest, że się go pozbyćThreadClassrun()RunnableThreadClass byłoby bardziej wydajne i właśnie dlatego istnieją Anonimowe Klasy Wewnętrzne.

Spójrz na następujący kod

Thread runner = new Thread(new Runnable() {
    public void run() {
        //Thread does it's work here
    }
});
runner.start();

Ten kod zastępuje odniesienie do tasknajbardziej znanego przykładu. Zamiast mieć osobną klasę, Anonimowa Klasa Wewnętrzna wewnątrz Thread()konstruktora zwraca nienazwany obiekt, który implementuje Runnableinterfejs i zastępuje run()metodę. Metoda run()obejmowałaby wewnątrz instrukcje, które wykonują pracę wymaganą przez wątek.

Odpowiadając na pytanie, czy Anonimowe Klasy Wewnętrzne są jedną z zalet Javy, muszę powiedzieć, że nie jestem do końca pewien, ponieważ nie znam obecnie wielu języków programowania. Ale mogę powiedzieć, że jest to zdecydowanie szybsza i łatwiejsza metoda kodowania.

Referencje: Sams Teach Your Jav Java in 21 Days Seventh Edition


0

Jeszcze jedna zaleta:
ponieważ wiesz, że Java nie obsługuje wielokrotnego dziedziczenia, więc jeśli użyjesz klasy „Wątek” jako klasy anonimowej, w klasie pozostanie jeszcze jedno miejsce na rozszerzenie dowolnej innej klasy.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.