Як використовуються внутрішні класи Anonymous у Java?


309

Яке використання анонімних класів на Java? Чи можна сказати, що використання анонімного класу є однією з переваг Java?


67
Це не стільки перевага Java, скільки спосіб подолати відсутність закриття в Java.
Ерік Вілсон

4
Java 8 також представила вирази лямбда. Перевірте відповідь: stackoverflow.com/questions/355167 / ...
akhil_mittal

Відповіді:


369

Під "анонімним класом" я вважаю, що ви маєте на увазі анонімний внутрішній клас .

Анонімний внутрішній клас може стати корисним при створенні екземпляра об'єкта з певними "додатками", такими як переосмислювальні методи, без фактичного підкласу класу.

Я схильний використовувати його як ярлик для приєднання слухача подій:

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

Використання цього методу робить кодування трохи швидшим, оскільки мені не потрібно робити додатковий клас, який реалізується ActionListener- я можу просто інстанціювати анонімний внутрішній клас, фактично не складаючи окремого класу.

Я використовую цю техніку лише для "швидких і брудних" завдань, коли цілий клас відчуває себе непотрібним. Маючи кілька анонімних внутрішніх класів, які роблять точно те саме, слід переробити фактичний клас, будь то внутрішній клас або окремий клас.


5
Або ви можете рефактор дублювати анонімні внутрішні класи в один метод з анонімним внутрішнім класом (і, можливо, іншим дублюваним кодом).
Том Хотін - тайклін

3
Чудова відповідь, але швидке запитання. Чи означає це, що Java може жити без анонімних внутрішніх класів, і вони є додатковим вибором?
realPK

5
Дуже добре пояснено, навіть важко, я б запропонував будь-кому, хто читає це, подивитися і подивитися, що вирази java 8 та лямбда можуть зробити, щоб кодування було швидше і читабельніше.
П'євіс

2
@ user2190639 Точно, не можу просити кращого з Lambda в Java8
bonCodigo

3
чому ти сказав, overloading methodsа ні overriding methods?
Тарун

73

Анонімні внутрішні класи - це фактично закриття, тому їх можна використовувати для імітації лямбда-виразів або "делегатів". Наприклад, візьміть цей інтерфейс:

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

Ви можете використовувати це анонімно, щоб створити функцію першого класу на Java. Скажімо, у вас є наступний метод, який повертає перше число, більший за i в даному списку, або i, якщо жодне число не перевищує:

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

І тоді у вас є інший метод, який повертає перше число, менше за i, у вказаному списку, або i, якщо жодне число не менше:

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

Ці методи майже ідентичні. Використовуючи функцію першого класу типу F, ми можемо переписати їх в один метод наступним чином:

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

Ви можете використовувати анонімний клас для використання методу firstMatch:

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

Це дійсно надуманий приклад, але легко зрозуміти, що можливість передавати функції, як ніби вони є значеннями, є досить корисною функцією. Див. "Чи може це зробити ваша мова програмування" від самого Джоеля.

Приємна бібліотека для програмування Java в такому стилі: Функціональна Java.


20
на жаль, багатослівність виконання функціонального програмування в java, IMHO, переважає його надбання - один з яскравих моментів функціонального програмування полягає в тому, що він прагне зменшити розмір коду, а також полегшує читання та зміну речей. Але функціональна Java, схоже, не робить цього взагалі.
Chii

27
Вся зрозумілість функціонального програмування, з лаконічністю Java!
Адам Яскевич

3
На мій досвід, функціональний стиль на Яві оплачується багатослів’ям на передньому плані, але це дає короткоту в перспективі. Наприклад, myList.map (f) значно менш багатослівний, ніж відповідний for-цикл.
Апокаліпс

2
Scala , мова функціонального стилю програмування, нібито добре працює всередині JVM і може бути варіантом для сценаріїв функціонального програмування.
Darrell Teague

51

Анонімний внутрішній клас використовується в наступному сценарії:

1.) Для переосмислення (підкласифікації), коли визначення класу не використовується, крім поточного випадку:

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

2.) Для реалізації інтерфейсу, коли реалізація інтерфейсу потрібна лише для поточного випадку:

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

3.) Анонімний внутрішній клас, визначений аргументом:

 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
Чудова відповідь, схоже на 3.) - це шаблон, який використовується для слухачів подій
xdl

47

Я використовую їх іноді як синтаксичний злом для створення інстанції Map:

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

проти

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

Це економить зайву кількість зайвих заяв. Однак я також зіткнувся з проблемами цього, коли зовнішній клас потрібно серіалізувати за допомогою видалення.


56
Щоб було зрозуміло, перший набір дужок - це анонімний внутрішній клас (підкласифікація HashMap). Другий набір дужок - це ініціалізатор екземплярів (а не статичний), який потім встановлює значення підкласу HashMap. +1 за згадування про нього, -1 за те, що не було написано для нообів. ;-D
Спенсер Кормос

4
Детальніше про синтаксис подвійних дужок читайте тут .
Мартін Андерссон


18

Вони зазвичай використовуються як багатослівна форма зворотного виклику.

Я гадаю, ви можете сказати, що вони є перевагою порівняно з тим, що їх немає, і щоразу потрібно створювати названий клас, але подібні концепції реалізуються набагато краще в інших мовах (як закриття чи блоки)

Ось гойдаючий приклад

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

Хоча це все ще безладно багатослівно, це набагато краще, ніж змушувати вас визначати названий клас для кожного слухача, який викидає, як це (хоча залежно від ситуації та повторного використання, це може бути кращим підходом)


1
Ви мали на увазі сказати коротко? Якщо він буде багатослівним, зворотний виклик залишатиметься окремо, зробивши його трохи більшим і, таким чином, зробивши його багатослівним. Якщо ви скажете, що це все-таки багатослівна, яка б тоді була лаконічна форма?
користувач3081519

1
@ user3081519, щось на кшталт myButton.addActionListener(e -> { /* do stuff here */})або myButton.addActionListener(stuff)було б коротше.
Семюел Едвін Уорд

8

Ви використовуєте його в ситуаціях, коли вам потрібно створити клас з певною метою всередині іншої функції, наприклад, як слухач, як виконаний (для нерестування потоку) тощо.

Ідея полягає в тому, що ви викликаєте їх всередині коду функції, щоб ви ніколи не посилалися на них в іншому місці, тому вам не потрібно їх називати. Компілятор просто їх перераховує.

Вони по суті є синтаксичним цукром, і зазвичай їх слід переміщувати в інше місце, коли вони зростають.

Я не впевнений, що це одна з переваг Java, хоча якщо ви їх використовуєте (і ми, на жаль, ми часто їх використовуємо), то ви можете стверджувати, що вони є одним.


6

GuideLines для анонімного класу.

  1. Анонімний клас оголошується та ініціалізується одночасно.

  2. Анонімний клас повинен поширюватися або впроваджуватися до одного і лише одного класу або інтерфейсу, відповідно.

  3. Оскільки клас анонімних мишей не має імені, його можна використовувати лише один раз.

наприклад:

button.addActionListener(new ActionListener(){

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

    }
});

Щодо №3: Не зовсім правда. Ви можете придбати кілька примірників анонімного класу з відображенням, наприклад ref.getClass().newInstance().
icza

Правила не відповідають на питання.
Маркіз Лорн

5

Так, анонімні внутрішні класи, безумовно, є однією з переваг Java.

З анонімним внутрішнім класом у вас є доступ до остаточних і членів змінних навколишнього класу, що стане в нагоді слухачам і т.д.

Але головна перевага полягає в тому, що код внутрішнього класу, який (принаймні повинен бути) щільно поєднаний з навколишнім класом / методом / блоком, має специфічний контекст (оточуючий клас, метод та блок).


1
Доступ до навколишнього класу дуже важливий! Я думаю, що це причина в багатьох випадках, коли використовується анонімний клас, тому що він потребує / використовує непублічні атрибути, методи та локальні змінні оточуючого класу / методу, які зовні (якщо буде використаний окремий клас) повинні були бути прийнятим або опублікованим.
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();

Це також один із прикладів для анонімного внутрішнього типу з використанням потоку


3

я використовую анонімні об'єкти для виклику нових тем ..

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

3

Внутрішній клас пов'язаний з екземпляром зовнішнього класу і є два особливих види: Місцевий клас і анонімний клас . Анонімний клас дозволяє нам оголошувати та інстанціювати клас одночасно, тому робить код стислим. Ми використовуємо їх, коли нам потрібен місцевий клас лише один раз, оскільки вони не мають імені.

Розглянемо приклад з doc, де у нас Personклас:

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

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

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

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

і у нас є метод друкувати членів, які відповідають критеріям пошуку:

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

де CheckPersonтакий інтерфейс:

interface CheckPerson {
    boolean test(Person p);
}

Тепер ми можемо використовувати анонімний клас, який реалізує цей інтерфейс, щоб вказати критерії пошуку як:

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

Тут інтерфейс дуже простий, і синтаксис анонімного класу здається непростим і незрозумілим.

Java 8 представила термін функціональний інтерфейс, який є інтерфейсом лише з одним абстрактним методом, отже, можна сказати CheckPerson, це функціональний інтерфейс. Ми можемо використовувати Lambda Expression, який дозволяє нам передавати функцію як аргумент методу як:

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

Ми можемо використовувати стандартний функціональний інтерфейс Predicateзамість інтерфейсу CheckPerson, що ще більше зменшить кількість необхідного коду.


2

Анонімний внутрішній клас може бути корисним при наданні різних реалізацій для різних об'єктів. Але його слід використовувати дуже щадно, оскільки це створює проблеми для читабельності програми.


1

Одне з головних застосувань анонімних класів при доопрацюванні класу, яке називається опікуном фіналізатора . У світі Java використання методів доопрацювання слід уникати, поки вони вам справді не потрібні. Ви повинні пам’ятати, що коли ви переосмислюєте метод доопрацювання для підкласів, завжди слід також посилатись super.finalize(), тому що метод фіналізації суперкласу не викликатиметься автоматично, і у вас можуть виникнути проблеми з витоками пам’яті.

тож з огляду на зазначений вище факт, ви можете просто використовувати анонімні класи типу:

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

Використовуючи цю техніку, ви позбавили себе та інших розробників звертатися super.finalize()до кожного підкласу, HeavyClassякий потребує методу доопрацювання.


1

Ви можете використовувати анонімний клас таким чином

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

1

Здається, тут ніхто не згадується, але ви також можете використовувати анонімний клас для проведення аргументу загального типу (який зазвичай втрачається через стирання типу) :

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

Якщо ви будете призначати цей клас анонімним способом

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

тоді такий holderекземпляр буде містити неочищене визначення переданого типу.

Використання

Це дуже зручно для створення валідаторів / десяріалізаторів. Крім того, ви можете створити загальний тип із відображенням (тому, якщо ви коли-небудь хотіли робити new T()параметризований тип - вас вітає!) .

Недоліки / обмеження

  1. Ви повинні передати загальний параметр явно. Якщо цього не зробити, це призведе до втрати параметра параметра
  2. Кожна інстанція коштуватиме вам додатковий клас, який буде генеровано компілятором, що призводить до забруднення клапанних шляхів / здуття банок

1

Найкращий спосіб оптимізації коду. Також ми можемо використовувати для переважаючого класу або інтерфейсу.

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

Анонімний внутрішній клас використовується для створення об'єкта , який ніколи не буде посилатися знову. Він не має імені і оголошується та створюється в одному і тому ж висловлюванні. Він використовується там, де ви зазвичай використовуєте змінну об'єкта. Ви замінюєте змінну newключовим словом, викликом конструктора та визначенням класу всередині {та }.

Коли ви пишете програму Threaded на Java, це зазвичай виглядає так

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

ThreadClassВикористовується тут буде визначена користувачем. Цей клас реалізує Runnableінтерфейс, необхідний для створення потоків. У метод (єдиний метод в ) повинна бути реалізована , а також. Зрозуміло, що позбутися було б ефективніше, і саме тому існують анонімні внутрішні класи.ThreadClassrun()RunnableThreadClass

Подивіться на наступний код

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

Цей код замінює посилання, наведене taskу верхньому прикладі. Замість того, щоб мати окремий клас, Anonymous Inner Class всередині Thread()конструктора повертає неназваний об'єкт, який реалізує Runnableінтерфейс і замінює run()метод. Метод run()буде включати оператори всередині, які виконують роботу, необхідну для потоку.

Відповідаючи на питання про те, чи є Anonymous Inner Classes однією з переваг Java, я повинен би сказати, що я не зовсім впевнений, оскільки на сьогоднішній день я не знайомий з багатьма мовами програмування. Але те, що я можу сказати, це, безумовно, швидший і простіший метод кодування.

Список літератури: Самс навчить себе Java у сьомому виданні за 21 день


0

Ще одна перевага:
Оскільки ви знаєте, що Java не підтримує багатократне успадкування, тому якщо ви використовуєте тип "Thread" свого роду як анонімний клас, то в класі все ще залишається простір для розширення будь-якого іншого класу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.