Почему мы используем внутренние классы?

Я хочу спросить вас, зачем нам нужны внутренние классы и почему мы их используем?
Я знаю, как использовать внутренние классы, но не знаю зачем.


person Mohamad Alhamoud    schedule 25.04.2010    source источник


Ответы (7)


Некоторые внутренние классы доступны публично (например, Map.Entry в Java), но это скорее исключение, чем норма.

Внутренние классы — это, по сути, деталь реализации.

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

По сути, внутренние классы являются формой области видимости. Доступ к пакету скрывает классы вне пакета. Частные внутренние классы скрывают этот класс снаружи этого класса.

Внутренние классы в Java также заменяют отсутствие указателей функций или делегатов методов (которые есть в C#) или замыканий. Они являются средством передачи функции в другую функцию. Например, в классе Executor у вас есть:

void execute(Runnable r);

так что вы можете передать метод. В C/С++ это может быть достигнуто с помощью:

void execute(void (*run)());

быть указателем на функцию.

person cletus    schedule 25.04.2010

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

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

Возьмем абстрактное понятие автомобиля с четырьмя колесами. У наших колес есть специфическая особенность, которая зависит от того, что они являются частью нашего автомобиля. Это понятие не представляет колеса как колеса в более общем виде, которые могли бы быть частью транспортного средства. Вместо этого он представляет их как специфичные для этого. Мы можем смоделировать это понятие, используя внутренние классы следующим образом:

У нас есть Автомобиль класса высшего уровня. Экземпляры класса Car состоят из четырех экземпляров класса Wheel. Эта конкретная реализация Wheel специфична для автомобиля, поэтому код не моделирует общее понятие Wheel, которое было бы лучше представлено как класс верхнего уровня. Следовательно, он семантически связан с классом Car, а код Wheel каким-то образом связан с его внешним классом.

Внутренние классы предоставляют нам механизм для точного моделирования этой связи. Мы говорим, что наш класс колеса — это Car.Wheel, где Car — класс верхнего уровня, а Wheel — внутренний класс.

Поэтому внутренние классы допускают объектную ориентацию определенных частей программы, которые в противном случае не были бы инкапсулированы в класс.

person codaddict    schedule 25.04.2010

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

person MAK    schedule 25.04.2010

Анонимные внутренние классы в Java — это способ использования шаблона адаптера.

interface Bar
{
  public void bar();
}

class Foo
{
  public void foo()
  {
    // do something relevant
  }

  // it happens that foo() defines the same contract (or a compatible one) as
  // Bar.bar(); with an anonymous inner class we can adapt Foo to the Bar
  // interface
  public Bar asBar()
  {
    // return an instance of an anonymous inner class that implements
    // the Bar inteface
    return new Bar()
    {
      public void bar()
      {
        // from an inner class, we can access the enclosing class methods
        // as the "this pointers" are "linked"
        foo();
      }
    };
  }
}

В Java убедитесь, что вы понимаете разницу между внутренними классами и вложенными классами. класс:

внутренний класс связан с экземпляром окружающего его класса и имеет прямой доступ к методам и полям этого объекта.

C# не имеет внутренних классов в смысле Java, только вложенные классы.

См. также этот пример внутреннего класса.

person Gregory Pakosz    schedule 25.04.2010

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

ebookPrice может наследоваться от Price, который находится в более высокой области и связан с любым другим классом.

(Просто мои два цента).

person Francisco Soto    schedule 25.04.2010

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

Подробное описание этого видения см. на "Сколько концепций для модулей нам нужно?" в блоге объектных команд. См. также работу Гилада Брахи в его блоге...

person akuhn    schedule 25.04.2010

Преимущество объектно-ориентированного подхода

По моему скромному мнению, наиболее важной особенностью внутреннего класса является то, что он позволяет вам превращать вещи в объекты, которые вы обычно не превращаете в объекты. Это позволяет вашему коду быть еще более объектно-ориентированным, чем без внутренних классов.

Давайте посмотрим на класс-член. Поскольку его экземпляр является членом его родительского экземпляра, он имеет доступ к каждому члену и методу в родительском экземпляре. На первый взгляд может показаться, что это немного; у нас уже есть такой доступ из метода в родительском классе. Однако класс-член позволяет нам извлечь логику из родителя и объективировать ее. Например, класс дерева может иметь метод и множество вспомогательных методов, которые выполняют поиск или обход дерева. С объектно-ориентированной точки зрения дерево — это дерево, а не алгоритм поиска. Однако для выполнения поиска необходимо глубокое знание структур данных дерева.

Внутренний класс позволяет нам удалить эту логику и поместить ее в отдельный класс. Итак, с объектно-ориентированной точки зрения, мы взяли функциональность оттуда, где она не принадлежит, и поместили ее в отдельный класс. Благодаря использованию внутреннего класса мы успешно отделили алгоритм поиска от дерева. Теперь, чтобы изменить алгоритм поиска, мы можем просто поменять местами новый класс. Я мог бы продолжать, но это открывает наш код для многих преимуществ, предоставляемых объектно-ориентированными методами.

Организационное преимущество

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

package1
   class 1
      class 2
      ...
      class n
...
package n

С внутренними классами мы можем сделать следующее:

package 1
   class 1
   class 2
      class 1
      class 2
      ...
      class n

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

Преимущество обратного вызова

Внутренние классы-члены и анонимные классы предоставляют удобный метод для определения обратных вызовов. Самый очевидный пример относится к коду GUI. Однако применение обратного вызова может распространяться на многие области.

Большинство графических интерфейсов Java имеют какой-либо компонент, который инициирует вызов метода actionPerformed(). К сожалению, у большинства разработчиков главное окно просто реализует ActionListener. В результате все компоненты используют один и тот же метод actionPerformed(). Чтобы выяснить, какой компонент выполнил действие, обычно в методе actionPerformed() есть гигантский уродливый переключатель.

Вот пример монолитной реализации:

public class SomeGUI extends JFrame implements ActionListener {

    protected JButton button1;
    protected JButton button2;
    //...
    protected JButton buttonN;

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == button1) {
        // do something
        } else if (e.getSource() == button2) {
            //... you get the picture
        }
    }
}

Всякий раз, когда вы видите переключатели или большие блоки if/if else, в вашем сознании должны начать звенеть громкие тревожные звоночки. В общем, такие конструкции являются плохим объектно-ориентированным дизайном, поскольку изменение в одном разделе кода может потребовать соответствующего изменения в операторе switch. Внутренние классы-члены и анонимные классы позволяют нам отказаться от переключаемого метода actionPerformed().

Вместо этого мы можем определить внутренний класс, который реализует ActionListener для каждого компонента, который мы хотим прослушивать. Это может привести к множеству внутренних классов. Однако мы можем избежать больших операторов switch и получить дополнительный бонус в виде инкапсуляции нашей логики действий. Более того, такой подход может повысить производительность. В коммутаторе, где есть n сравнений, мы можем ожидать n/2 сравнений в среднем. Внутренние классы позволяют нам установить соответствие 1:1 между исполнителем действия и слушателем действия. В большом графическом интерфейсе такие оптимизации могут существенно повлиять на производительность. Анонимный подход может выглядеть так:

public class SomeGUI extends JFrame {
    //  ... button member declarations ...

    protected void buildGUI() {
        button1 = new JButton();
        button2 = new JButton();
        //...
        button1.addActionListener(
                new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent e) {
                // do something
            }
        });
// .. repeat for each button
    }
}

Используя внутренние классы-члены, та же программа будет выглядеть так:

public class SomeGUI extends JFrame
{
   ... button member declarations ...
   protected void buildGUI()
   {
      button1 = new JButton();
      button2 = new JButton();
      ...
      button1.addActionListener(
         new java.awt.event.ActionListener()
         {
            public void actionPerformed(java.awt.event.ActionEvent e)
            {
               // do something
            }
         }
      );
      .. repeat for each button

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

Я предпочитаю использовать классы-члены в качестве обратных вызовов. Впрочем, это вопрос личных предпочтений. Я просто чувствую, что слишком много анонимных классов загромождают код. Я также чувствую, что анонимные классы могут стать громоздкими, если они больше одной или двух строк.

Недостатки?

Как и во всем остальном, вы должны принимать хорошее вместе с плохим. Внутренние классы имеют свои недостатки. С точки зрения сопровождения, неопытным Java-разработчикам может быть трудно понять внутренний класс. Использование внутренних классов также увеличит общее количество классов в вашем коде. Более того, с точки зрения разработки, большинство инструментов Java не поддерживают внутренние классы. Например, я использую IBM VisualAge for Java для повседневного написания кода. Хотя внутренние классы будут компилироваться в VisualAge, браузера или шаблона внутреннего класса нет. Вместо этого вы должны просто ввести внутренний класс непосредственно в определение класса. К сожалению, это затрудняет просмотр внутреннего класса. Его также сложно набирать, поскольку вы теряете многие средства завершения кода VisualAge, когда вводите определение класса или используете внутренний класс.

person Melih Altıntaş    schedule 04.09.2013