Вложенные, внутренние и локальные классы. Видимость new и this

Вложенный класс

Иногда один класс имеет смысл только в контексте другого.

package online.javanese.basics.oop.nested;

import java.util.Random;
import java.util.UUID;

public class Nexus5 extends MobilePhone {

    private Screen screen = new Screen();
    
    // конструкторы (без изменений)
    
    public void wakeUp() {
        screen.enableBacklight(true);
    }

    public void sleep() {
        screen.enableBacklight(false);
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() +
                "(serialNumber=" + serialNumber +
                ", IMEI=" + imei +
                ", batteryVoltage=" + batteryVoltage + "mV" +
                ", screen=" + screen + ")";
    }

    // остальные методы (без изменений)
    
    // вложенный (nested) класс
    private static class Screen {
        
        private boolean enabled;
        
        void enableBacklight(boolean enable) {
            enabled = enable;
        }

        @Override
        public String toString() {
            return getClass().getSimpleName() + "(enabled=" + enabled + ")";
        }
    }
}

Создадим два телефона и поиграем с их экранами.

    public static void main(String[] args) {
        Nexus5 firstNexus = new Nexus5();
        firstNexus.wakeUp(); // зажигаем экран
        System.out.println(firstNexus);

        Nexus5 secondNexus = new Nexus5();
        System.out.println(secondNexus);

        // обменяем экранами
        Nexus5.Screen firstScreen = firstNexus.screen;
        firstNexus.screen = secondNexus.screen;
        secondNexus.screen = firstScreen;
        
        System.out.println(); // пустая строка


        System.out.println(firstNexus);
        System.out.println(secondNexus);
    }

Выводится:

Nexus5(serialNumber=NEX-51436641..., ..., ..., screen=Screen(enabled=true))
Nexus5(serialNumber=NEX-b8235806..., ..., ..., screen=Screen(enabled=false))

Nexus5(serialNumber=NEX-51436641..., ..., ..., screen=Screen(enabled=false))
Nexus5(serialNumber=NEX-b8235806..., ..., ..., screen=Screen(enabled=true))
Примечание

Для того чтобы поля serialNumber, imei и batteryVoltage класса MobilePhone было видно из класса Nexus5, их доступ был расширен до protected:

package online.javanese.basics.oop.nested;

public abstract class MobilePhone extends Phone {

    protected final String serialNumber;
    protected final String imei;

    protected int batteryVoltage = 3900;

    ...
    
}

Внутренний класс

Если со вложенного класса снять модификатор static, экземпляры этого класса будет навсегда привязаны к экземпляру внешнего класса. Такая разновидность вложенного класса называется внутренним классом.

    // внутренний (inner) класс
    private class Screen {

        // метод enableBacklight без изменений

        @Override
        public String toString() {
            return getClass().getSimpleName() + 
                    "(phone's serialNumber=" + serialNumber + ")";
        }
    }

Теперь результат обмена экранами выглядит так:

Nexus5(serialNumber=NEX-e86dbe32(…), screen=Screen(phone's serialNumber=NEX‑e86dbe32…))
Nexus5(serialNumber=NEX-1bf2a657(…), screen=Screen(phone's serialNumber=NEX‑1bf2a657…))

Nexus5(serialNumber=NEX-e86dbe32(…), screen=Screen(phone's serialNumber=NEX‑1bf2a657…))
Nexus5(serialNumber=NEX-1bf2a657(…), screen=Screen(phone's serialNumber=NEX‑e86dbe32…))

this во внутреннем классе

В классе Nexus5.Screen ключевое слово this, очевидно, указывает на сам экземпляр Screen. Чтобы добраться до экземпляра внешнего класса, пишут ИмяКласса.this.

    private class Screen {

        // enableBacklight, toString без изменений
        
        Nexus5 getPhone() {
            return Nexus5.this;
        }
    }
        Nexus5 nexus = new Nexus5();
        Nexus5.Screen screen = nexus.screen;
        System.out.println(screen.getPhone());
Nexus5(serialNumber=NEX-9836aadd(…), screen=Screen(phone's serialNumber=NEX‑9836aadd…))

new для внутреннего класса

Внутри класса Nexus5 мы писали new Screen(), и экземпляр внутреннего класса создавался для данного экземпляра Nexus5. Вне Nexus5 нельзя написать new Nexus5.Screen() — экран обязан принадлежать конкретному телефону. Для этого пишут outerClassInstance.new:

        Nexus5 nexus = new Nexus5();
        nexus.screen = null; // теряем экран
        nexus.screen = nexus.new Screen(); // делаем новый

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

Некоторые классы ещё более узкоспециализированы: их видно только в пределах метода. Для примера вернёмся к immutable-версии класса Rectangle.

public final class Rectangle {

    // поля x1, y1, x2, y2
    // конструктор
    // метод toString
    // (без изменений)

    public Rectangle moveAndResize(
            double dx, double dy, 
            double rx, double ry) {
        // объявляем локальный класс
        class MutableRectangle {

            // те же координаты, только изменяемые
            double x1, y1, x2, y2;

            // создать новый ИзменяемыйПрямоугольник
            // на основе существующего Прямоугольника
            MutableRectangle(Rectangle original) {
                x1 = original.x1;
                y1 = original.y1;
                x2 = original.x2;
                y2 = original.y2;
            }

            // сдвинуть
            void move(double dx, double dy) {
                x1 += dx;
                y1 += dy;
                x2 += dx;
                y2 += dy;
            }

            // изменить размер:
            // сдвинуть один угол,
            // другой не трогать
            void resize(double rx, double ry) {
                x2 += rx;
                y2 += ry;
            }

            // преобразовать в Прямоугольник
            Rectangle toRectangle() {
                return new Rectangle(x1, y1, x2, y2);
            }
        }

        // используем локальный класс:
        // создаём изменяемую копию данного прямоугольника
        MutableRectangle mutableCopy = new MutableRectangle(this);
        // мутируем копию
        mutableCopy.move(dx, dy);
        // ещё мутируем
        mutableCopy.resize(rx, ry);
        // преобразовываем в неизменяемый прямоугольник
        return mutableCopy.toRectangle();
    }

    // тест
    public static void main(String[] args) {
        Rectangle rect = new Rectangle(0, 0, 0, 0);
        System.out.println(rect.moveAndResize(-10, -10, 4, 2));
    }

}

Результат:

Rectangle(-10.0; -10.0 — -6.0; -8.0)
Комментарии к уроку

Сообщить об ошибке