Вложенные и внутренние классы

В литературе по Java встречаются такие термины, как "внутренние классы" inner classes и "вложенные классы" nested classes. Для вложенных классов inner классы являются подмножеством. Тем не менее часто под внутренними классами подразумеваются все вложенные - вот такой вот парадокс.

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

Существуют два типа вложенных класса - статические (static-nested-class) и нестатические (non-static). Собственно нестатические вложенные классы имеют и другое название - внутренние классы. Внешний класс иногда называют еще обрамляющим классом.

Вложенные классы, nested classes

Если связь между объектом внутреннего класса и объектом внешнего класса не нужна, можно сделать внутренний класс статическим static. Такой класс называют вложенным nested. Применение статического внутреннего класса означает следующее :

  • для создания объекта статического внутреннего класса не нужен объект внешнего класса;
  • из объекта вложенного класса нельзя обращаться к нестатическим членам внешнего класса.

Вложенный класс имеет доступ к членам своего внешнего класса, в том числе и к закрытым членам. Однако, внешний класс не имеет доступа к членам вложенного класса. Вложенный класс при этом является членом внешнего класса.

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

Внутренние классы, inner classes

Нестатические вложенные классы называют также внутренними классами inner class. Внутренний класс имеет доступ ко всем переменным и методам своего внешнего класса и может непосредственно ссылаться на них. Внутренние классы создаются внутри окружающего класса :

// внешний класс
class Outer {
    int x1 = 0;
    int x2 = 0;
    
    void summa(final int x1, final int x2) {
        this.x1 = x1;
        this.x2 = x2;
        Inner inner = new Inner();
        inner.display();
    }
    // внутренний класс
    class Inner {
        void display() {
            System.out.println ("summa = " + String.valueOf(x1 + x2));
        }
    }
}
...

class MainActivity {
    Outer outer = new Outer();
    outer.summa(12 + 11);
}

Внутренний класс Inner определён в области видимости класса Outer. Поэтому любой код в классе Inner может непосредственно обращаться к переменным x1, x2 внешнего класса. После создания экземпляра класса Outer вызывается его метод summa () с параметрами, который создаёт экземпляр класса Inner с вызовом метода display ().

Внутренний класс можно определить не только на уровне класса, но и внутри метода или внутри тела цикла. Если понадобится создать объект внутреннего класса не в статическом методе внешнего класса, тип этого объекта должен задаваться в формате ИмяВнешнегоКласса.ИмяВнутреннегоКласса.

Объект внутреннего класса связан с внешним объектом-создателем и может обращаться к его членам без каких-либо дополнительных описаний. Для внутренних классов доступны все элементы внешнего класса. Если необходимо получить ссылку на объект внешнего класса, то следует использовать наименование внешнего класса с ключевым словом this, разделенных точкой, например : Outer.this.

Статические внутренние классы

Статические внутренние классы декларируются внутри основного класса и обозначаются ключевым словом static. Они не имеют доступа к членам внешнего класса за исключением статических.

Статический внутренний класс может содержать статические поля, методы и классы, в отличие от других типов внутренних классов. Пример :

class OuterClass 
{
    private int outerField;
    static int staticOuterField;

    public OuterClass() {}
    static class InnerClass
    {
         int getOuterField()
         {
             return OuterClass.this.outerField; // Эта линия кода вызывает ошибку при компиляции
         }
         int getStaticOuterField()
         {
             return OuterClass.staticOuterField; // Эта линия кода синтаксически корректна
         }
    }
}

Локальные классы

Локальные классы объявляются внутри методов основного класса и могут быть использованы только внутри этих методов. Они имеют доступ к членам внешнего класса, а также как к локальным переменным, так и к параметрам метода при одном условии - переменные и параметры используемые локальным классом должны быть задекларированы final. Локальные классы не могут содержать определение (но могут наследовать) статических полей, методов и классов (кроме констант). Пример :

class OuterClass
{
    public OuterClass(){}
    private int outerField;
    InnerClass inner; // Эта линия кода вызывает ошибку при компиляции
	
    void methodWithLocalClass (final int parameter)
    {
         InnerClass innerInsideMehod; // Эта линия кода синтаксически корректна
         int notFinal = 0;
         class InnerClass
         {
             int getOuterField()
             {
                 return OuterClass.this.outerField; // Эта линия кода синтаксически корректна
             }
             notFinal++; // Эта линия кода вызывает ошибку при компиляции
             int getParameter()
             {
                return parameter; // Эта линия кода синтаксически корректна
             }
         };
    }
};

Локальный класс можно объявить внутри любого блока операторов. Например, внутри методов, циклов или операторов if. В следующем примере, проверяющем правильность телефонных номеров, объявляется локальный класс PhoneNumber в методе validatePhoneNumber :

public class LocalClassExample
{
    static String regularExpression = "[^0-9]";
 
    public static void validatePhoneNumber (String phoneNumber1, String phoneNumber2)
    {
        final int numberLength = 10;
 
        class PhoneNumber {
            String formattedPhoneNumber = null;
 
            PhoneNumber(String phoneNumber){
                // numberLength = 7;
                String currentNumber = phoneNumber.replaceAll(regularExpression, "");
                if (currentNumber.length() == numberLength)
                    formattedPhoneNumber = currentNumber;
                else
                    formattedPhoneNumber = null;
            }
 
            public String getNumber() {
                return formattedPhoneNumber;
            }
        }
 
        PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
        PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
 
        if (myNumber1.getNumber() == null)
            System.out.println("First number is invalid");
        else
            System.out.println("First number is " + myNumber1.getNumber());
        if (myNumber2.getNumber() == null)
            System.out.println("Second number is invalid");
        else
            System.out.println("Second number is " + myNumber2.getNumber());
 
    }
 
    public static void main(String... args) {
        validatePhoneNumber("123-456-7890", "456-7890");
    }
}

Данная программа проверяет телефонные номера на соответствие некоторому стандарту. Сперва из номера удаляются все символы, отличные от цифр (0-9). После, происходит проверка длины номера и, если номер состоит из 10 цифр, то номер корректный. Данный пример выведет :

First number is 1234567890
Second number is invalid

У локальных классов есть несколько ограничений :

  • они видны только в пределах блока, в котором объявлены;
  • они не могут быть объявлены как private, public, protected или static;
  • они не могут иметь внутри себя статических объявлений (полей, методов, классов); исключением являются константы (static final);

Анонимные классы, anonymous class

Анонимные классы декларируются внутри методов основного класса и могут быть использованы только внутри этих методов. В отличие от локальных классов, анонимные классы не имеют названия. Главное требование к анонимному классу - он должен наследовать существующий класс или реализовывать существующий интерфейс. Анонимные классы не могут содержать определение статических полей, методов и классов (кроме констант), но могут их наследовать. Пример :

class OuterClass
{
    public OuterClass() {}
    void methodWithLocalClass (final int interval)
    {
        // При определении анонимного класса применен полиморфизм - переменная listener содержит экземпляр 
        // анонимного класса, реализующего существующий интерфейс ActionListener
        ActionListener listener = new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent event)
            {
                System.out.println("Эта строка выводится на экран каждые " + interval + " секунд");
            }  
        };
 
        Timer t = new Timer(interval, listener); // Объект анонимного класса использован внутри метода
        t.start();
    }
}

Анонимные классы широко используются Java-программистами. Анонимный класс не имеют имени. Классический пример анонимного класса:

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

На основании анонимного класса создается поток и запускается с помощью метода start класса Thread. Синтаксис создания анонимного класса базируется на использовании оператора new с именем класса (интерфейса) и телом новосозданного анонимного класса.

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

class Clazz
{ 
    Clazz(int param) { } 
 
    public static void main(String[] args) { 
        new Clazz(1) { }; // правильное создание анонимного класса 
        new Clazz( ) { }; // неправильное создание анонимного класса 
    } 
} 

Так как анонимный класс является локальным классом, он имеет все те же ограничения, что и локальный класс.

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

  • тело класса является очень коротким;
  • нужен только один экземпляр класса;
  • класс используется в месте его создания или сразу после него;
  • имя класса не важно и не облегчает понимание кода.
Наверх
  Рейтинг@Mail.ru