Поиск…


Вступление

Используя Java, разработчики могут определять класс в другом классе. Такой класс называется вложенным классом . Вложенные классы называются внутренними классами, если они были объявлены как нестатические, если нет, их просто называют статическими вложенными классами. Эта страница предназначена для документирования и предоставления подробных сведений о том, как использовать Java Nested и Inner Classes.

Синтаксис

  • public class OuterClass {public class InnerClass {}} // Внутренние классы также могут быть приватными
  • public class OuterClass {public static class StaticNestedClass {}} // Статические вложенные классы также могут быть приватными
  • public void method () {private class LocalClass {}} // Локальные классы всегда приватные
  • SomeClass anonymousClassInstance = new SomeClass () {}; // Анонимные внутренние классы нельзя назвать, следовательно, доступ является спорным. Если «SomeClass ()» является абстрактным, тело должно реализовать все абстрактные методы.
  • SomeInterface anonymousClassInstance = new SomeInterface () {}; // Тело должно реализовать все методы интерфейса.

замечания

Терминология и классификация

Спецификация языка Java (JLS) классифицирует различные типы Java-классов следующим образом:

Класс верхнего уровня - это класс, который не является вложенным классом.

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

Внутренний класс представляет собой вложенный класс, который явно или неявно не объявлен статическим.

Внутренний класс может быть не статическим классом-членом , локальным классом или анонимным классом . Класс-член интерфейса неявно статичен, поэтому он никогда не считается внутренним классом.

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

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

Семантические различия

  • Классы верхнего уровня - это «базовый случай». Они видны другим частям программы, подчиненным нормальным правилам видимости, основанным на семантике модификатора доступа. Если они не являются абстрактными, они могут быть созданы каким-либо кодом, где соответствующие конструкторы видны на основе модификаторов доступа.

  • Статические вложенные классы следуют тем же правилам доступа и создания экземпляров, что и классы верхнего уровня, за двумя исключениями:

    • Вложенный класс может быть объявлен как private , что делает его недоступным вне его класса верхнего уровня.
    • Вложенный класс имеет доступ к private членам охватывающего класса верхнего уровня и всего его тестируемого класса.

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

  • Внутренние классы добавляют возможность доступа к нестационарным переменным, объявленным в охватываемых областях:

    • Нестатический класс-член может ссылаться на переменные экземпляра.
    • Локальный класс (объявленный внутри метода) также может ссылаться на локальные переменные метода, при условии, что они являются final . (Для Java 8 и более поздних версий они могут быть фактически окончательными .)
    • Анонимный внутренний класс может быть объявлен как в классе, так и в методе и может обращаться к переменным в соответствии с теми же правилами.

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

Простой стек с использованием вложенного класса

public class IntStack {

    private IntStackNode head;

    // IntStackNode is the inner class of the class IntStack
    // Each instance of this inner class functions as one link in the
    // Overall stack that it helps to represent
    private static class IntStackNode {

        private int val;
        private IntStackNode next;

        private IntStackNode(int v, IntStackNode n) {
            val = v;
            next = n;
        }
    }

    public IntStack push(int v) {
        head = new IntStackNode(v, head);
        return this;
    }

    public int pop() {
        int x = head.val;
        head = head.next;
        return x;
    }
}

И их использование, которое (в частности) вовсе не признает существование вложенного класса.

public class Main {
    public static void main(String[] args) {
 
        IntStack s = new IntStack();
        s.push(4).push(3).push(2).push(1).push(0);

        //prints: 0, 1, 2, 3, 4, 
        for(int i = 0; i < 5; i++) {
            System.out.print(s.pop() + ", ");
        }            
    }
}

Статические и нестатические вложенные классы

При создании вложенного класса вы сталкиваетесь с выбором наличия вложенного класса static:

public class OuterClass1 {

    private static class StaticNestedClass {

    }

}

Или нестатический:

public class OuterClass2 {

    private class NestedClass {

    }

}

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

public class OuterClass1 {

    private int aField;
    public void aMethod(){}

    private static class StaticNestedClass {
        private int innerField;

        private StaticNestedClass() {
             innerField = aField; //Illegal, can't access aField from static context 
             aMethod();           //Illegal, can't call aMethod from static context 
        }

        private StaticNestedClass(OuterClass1 instance) {
             innerField = instance.aField; //Legal
        }

    }

    public static void aStaticMethod() {
        StaticNestedClass s = new StaticNestedClass(); //Legal, able to construct in static context
        //Do stuff involving s...
    }

}

public class OuterClass2 {

    private int aField;

    public void aMethod() {}

    private class NestedClass {
        private int innerField;

        private NestedClass() {
             innerField = aField; //Legal   
             aMethod(); //Legal
        }
    }

    public void aNonStaticMethod() {
        NestedClass s = new NestedClass(); //Legal
    }

    public static void aStaticMethod() {
        NestedClass s = new NestedClass(); //Illegal. Can't construct without surrounding OuterClass2 instance.
                                         //As this is a static context, there is no surrounding OuterClass2 instance
    }
}

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

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

Модификаторы доступа для внутренних классов

Полное описание модификаторов доступа в Java можно найти здесь . Но как они взаимодействуют с внутренними классами?

public , как обычно, предоставляет неограниченный доступ к любой области, доступной для доступа к типу.

public class OuterClass {

    public class InnerClass {

        public int x = 5;

    }

    public InnerClass createInner() {
        return new InnerClass();
    }
}

public class SomeOtherClass {

    public static void main(String[] args) {
        int x = new OuterClass().createInner().x; //Direct field access is legal
    }
}

оба protected а модификатор по умолчанию (ничего) ведет себя так же, как ожидалось, так же, как и для не-вложенных классов.

private , интересно, не ограничивает класс, к которому он принадлежит. Скорее, он ограничивает блок компиляции - .java-файл. Это означает, что внешние классы имеют полный доступ к полям и методам Inner, даже если они выделены как private .

public class OuterClass {

    public class InnerClass {

        private int x;
        private void anInnerMethod() {}
    }

    public InnerClass aMethod() {
        InnerClass a = new InnerClass();
        a.x = 5; //Legal
        a.anInnerMethod(); //Legal
        return a;
    }
}

Сам внутренний класс может иметь видимость, отличную от public . Пометив его private или другим модификатором ограниченного доступа, другим (внешним) классам не разрешается импортировать и присваивать тип. Однако они все равно могут получать ссылки на объекты этого типа.

public class OuterClass {

    private class InnerClass{}

    public InnerClass makeInnerClass() {
        return new InnerClass();
    }
}

public class AnotherClass {

    public static void main(String[] args) {
        OuterClass o = new OuterClass();
     
        InnerClass x = o.makeInnerClass(); //Illegal, can't find type
        OuterClass.InnerClass x = o.makeInnerClass(); //Illegal, InnerClass has visibility private
        Object x = o.makeInnerClass(); //Legal
    }
}

Анонимные внутренние классы

Анонимный внутренний класс является формой внутреннего класса, который объявляется и создается с помощью одного утверждения. Как следствие, нет названия для класса, который можно использовать в другом месте программы; т.е. анонимно.

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

public static Comparator<String> CASE_INSENSITIVE =
        new Comparator<String>() {
            @Override
            public int compare(String string1, String string2) {
                return string1.toUpperCase().compareTo(string2.toUpperCase());
            }
        };

Этот анонимный класс определяет объект Comparator<String> ( CASE_INSENSITIVE ), который сравнивает две строки, игнорируя различия в случае.

Другие интерфейсы, которые часто реализуются и создаются с использованием анонимных классов, являются Runnable и Callable . Например:

// An anonymous Runnable class is used to provide an instance that the Thread
// will run when started.
Thread t = new Thread(new Runnable() {
        @Override 
        public void run() {
              System.out.println("Hello world");
        }
    });
t.start();  // Prints "Hello world"

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

Конструкторы

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

SomeClass anon = new SomeClass(1, "happiness") {
            @Override
            public int someMethod(int arg) {
                // do something
            }
        };

SomeClass конструктор для нашего анонимного подкласса SomeClass вызовет конструктор SomeClass который соответствует сигнатуре вызова SomeClass(int, String) . Если конструктор недоступен, вы получите ошибку компиляции. Любые исключения, создаваемые согласованным конструктором, также выдаются неявным конструктором.

Естественно, это не работает при расширении интерфейса. Когда вы создаете анонимный класс из интерфейса, суперкласс класса представляет собой java.lang.Object которого есть только конструктор no-args.

Метод Местные Внутренние Классы

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

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

Пример использования локального внутреннего класса метода:

public class OuterClass {
    private void outerMethod() {
       final int outerInt = 1;
        // Method Local Inner Class
        class MethodLocalInnerClass {
            private void print() {
                System.out.println("Method local inner class " + outerInt);
            }
        }
        // Accessing the inner class
        MethodLocalInnerClass inner = new MethodLocalInnerClass();
        inner.print();
    }

    public static void main(String args[]) {
        OuterClass outer = new OuterClass();
        outer.outerMethod();
    }
}

Выполнение даст результат: Method local inner class 1 .

Доступ к внешнему классу из нестатического внутреннего класса

Ссылка на внешний класс использует имя класса, и this

public class OuterClass {
    public class InnerClass {
        public void method() {
            System.out.println("I can access my enclosing class: " + OuterClass.this);
        }
    }
}

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

public class OuterClass {
    private int counter;

    public class InnerClass {
        public void method() {
            System.out.println("I can access " + counter);
        }
    }
}

Но в случае столкновения имен вы можете использовать ссылку на внешний класс.

public class OuterClass {
    private int counter;

    public class InnerClass {
        private int counter;
        
        public void method() {
            System.out.println("My counter: " + counter);
            System.out.println("Outer counter: " + OuterClass.this.counter);
            
            // updating my counter
            counter = OuterClass.this.counter;
        }
    }
}

Создать экземпляр нестатического внутреннего класса извне

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

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

class OuterClass {

    class InnerClass {
    }
}

class OutsideClass {

    OuterClass outer = new OuterClass();
    
    OuterClass.InnerClass createInner() {
        return outer.new InnerClass();
    }
}

Обратите внимание на использование как outer.new .



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow