Почему мы используем внутренние классы? - PullRequest
9 голосов
/ 25 апреля 2010

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

Ответы [ 7 ]

11 голосов
/ 25 апреля 2010

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

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

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

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

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

void execute(Runnable r);

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

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

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

3 голосов
/ 25 апреля 2010

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

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

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

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

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

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

3 голосов
/ 25 апреля 2010

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

3 голосов
/ 25 апреля 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, только вложенные классы.

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

2 голосов
/ 25 апреля 2010

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

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

2 голосов
/ 25 апреля 2010

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

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

(только мои два цента).

1 голос
/ 04 сентября 2013

Объектно-ориентированное преимущество

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

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

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

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

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

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

Всякий раз, когда вы видите переключатели или большие, если / если еще блокирует, громкие сигналы тревоги должны начать звонить в вашем уме. В общем, такие конструкции являются плохим объектно-ориентированным проектом, поскольку изменение в одном разделе кода может потребовать соответствующего изменения в операторе 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-инструментов немного отстают от поддержки внутренних классов. Например, я использую VisualAge для Java от IBM для повседневного кодирования. В то время как внутренние классы будут компилироваться в VisualAge, нет внутреннего браузера класса или шаблона. Вместо этого вы должны просто ввести внутренний класс непосредственно в определение класса. К сожалению, это затрудняет просмотр внутреннего класса. Это также трудно набирать, так как вы теряете много средств для завершения кода VisualAge, когда вы вводите определение класса или используете внутренний класс

...