Как анонимные внутренние классы используются в 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
Или вы можете рефакторировать дублирующие анонимные внутренние классы в один метод с анонимным внутренним классом (и, возможно, каким-то другим дублированным кодом).
Том Хотин - tackline

3
Отличный ответ, но быстрый вопрос. Означает ли это, что Java может жить без анонимных внутренних классов, и они как дополнительная опция для выбора?
realPK

5
Очень хорошо объясненный, даже жесткий, я бы посоветовал всем, кто читает это, посмотреть и посмотреть, что могут сделать java 8 и лямбда-выражения, чтобы сделать кодирование более быстрым и более читабельным.
Pievis

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, кажется, не делает этого вообще.
Чии

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

3
По моему опыту, функциональный стиль в Java заранее оплачен многословно, но в конечном итоге он дает краткость. Например, myList.map (f) значительно менее многословен, чем соответствующий цикл for.
Apocalisp

2
Scala , язык стилей функционального программирования, предположительно хорошо работает внутри JVM и может быть вариантом для сценариев функционального программирования.
Даррелл Тиг

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

Это экономит некоторую избыточность при выполнении множества операторов put. Тем не менее, я также столкнулся с проблемами при этом, когда внешний класс должен быть сериализован посредством удаленного взаимодействия.


56
Для ясности, первый набор скобок - это анонимный внутренний класс (подкласс HashMap). Второй набор скобок - это инициализатор экземпляра (а не статический), который затем устанавливает значения в вашем подклассе HashMap. +1 за упоминание об этом, -1 за то, что не разъяснил это новичкам. ;-D
Спенсер Кормос

4
Подробнее о синтаксисе двойных скобок читайте здесь .
Мартин Андерссон


18

Они обычно используются как многословная форма обратного вызова.

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

Вот пример свинга

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

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


1
Вы хотели сказать кратко? Если бы это было многословно, обратный вызов остался бы отдельно, делая его немного больше, и, таким образом, делая его многословным. Если вы скажете, что это все еще многословно, что тогда будет краткой формой?
user3081519

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

8

Вы используете его в ситуациях, когда вам нужно создать класс для определенной цели внутри другой функции, например, в качестве слушателя, в качестве исполняемого объекта (для создания потока) и т. Д.

Идея состоит в том, что вы вызываете их из кода функции, чтобы вы никогда не обращались к ним в другом месте, поэтому вам не нужно называть их. Компилятор просто перечисляет их.

Они по сути являются синтаксическим сахаром, и, как правило, их следует перемещать в другое место по мере их роста.

Я не уверен, является ли это одним из преимуществ Java, хотя, если вы используете их (и, к сожалению, мы все часто их используем), вы могли бы утверждать, что они едины.


6

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

  1. Анонимный класс объявляется и инициализируется одновременно.

  2. Анонимный класс должен расширяться или реализовываться до одного и только одного класса или интерфейса, соответственно.

  3. Так как у класса anonymouse нет имени, его можно использовать только один раз.

например:

button.addActionListener(new ActionListener(){

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

    }
});

Относительно № 3: не совсем верно. Вы можете получить несколько экземпляров анонимного класса с отражением, например ref.getClass().newInstance().
icza

Правила не отвечают на вопрос.
Маркиз Лорн

5

Да, анонимные внутренние классы - это одно из преимуществ Java.

С анонимным внутренним классом у вас есть доступ к final и переменным-членам окружающего класса, и это удобно для слушателей и т. Д.

Но главное преимущество заключается в том, что код внутреннего класса, который (по крайней мере, должен быть) тесно связан с окружающим классом / методом / блоком, имеет определенный контекст (окружающий класс, метод и блок).


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

Внутренний класс связан с экземпляром внешнего класса и есть два особых вида: Местный класс и анонимный класс . Анонимный класс позволяет нам одновременно объявлять и создавать экземпляр класса, что делает код кратким. Мы используем их, когда нам нужен локальный класс только один раз, поскольку у них нет имени.

Рассмотрим пример из документа, где у нас есть 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что это функциональный интерфейс. Мы можем использовать лямбда-выражение, которое позволяет нам передавать функцию в качестве аргумента метода в виде:

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

PredicateВместо интерфейса мы можем использовать стандартный функциональный интерфейс CheckPerson, что еще больше сократит объем требуемого кода.


2

Анонимный внутренний класс может быть полезен, предоставляя разные реализации для разных объектов. Но следует использовать очень экономно, так как это создает проблемы для читаемости программы.


1

Одно из основных применений анонимных классов в финализации классов, которое называется finalizer guardian . В мире Java следует избегать использования методов finalize до тех пор, пока они вам действительно не понадобятся. Вы должны помнить, что когда вы переопределяете метод finalize для подклассов, вы всегда должны вызывать его super.finalize(), потому что метод 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. Каждое создание будет стоить вам дополнительный класс, который будет сгенерирован компилятором, что приведет к загрязнению пути к классу / вздутию jar

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ключевым словом, вызовом конструктора и определением класса внутри {и }.

При написании многопоточной программы на 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()будет включать в себя операторы, которые выполняют работу, требуемую потоком.

Отвечая на вопрос о том, является ли анонимные внутренние классы одним из преимуществ Java, я должен сказать, что я не совсем уверен, поскольку в настоящее время я не знаком со многими языками программирования. Но я могу сказать, что это определенно более быстрый и простой метод кодирования.

Ссылки: Самс научит себя Java за 21 день, седьмое издание


0

Еще одно преимущество:
поскольку вы знаете, что Java не поддерживает множественное наследование, поэтому, если вы используете своего рода класс «Thread» в качестве анонимного класса, у класса все еще остается один пробел для расширения любого другого класса.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.