Статический вложенный класс в Java, почему?

217

Я просматривал код Java LinkedListи заметил, что он использует статический вложенный класс Entry.

public class LinkedList<E> ... {
...

 private static class Entry<E> { ... }

}

В чем причина использования статического вложенного класса, а не нормального внутреннего класса?

Единственная причина, по которой я мог придумать, заключалась в том, что Entry не имеет доступа к переменным экземпляра, поэтому с точки зрения ООП он лучше инкапсулируется.

Но я подумал, что могут быть и другие причины, например, производительность. Что бы это могло быть?

Заметка. Я надеюсь, что мои термины верны, я бы назвал это статическим внутренним классом, но я думаю, что это неправильно: http://java.sun.com/docs/books/tutorial/java/javaOO/nested.html

Дэвид Тернер
источник

Ответы:

271

На странице Sun, на которую вы ссылаетесь, есть несколько ключевых отличий:

Вложенный класс является членом включающего его класса. Нестатические вложенные классы (внутренние классы) имеют доступ к другим членам включающего класса, даже если они объявлены закрытыми. Статические вложенные классы не имеют доступа к другим членам включающего класса.
...

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

Там нет необходимости , LinkedList.Entryчтобы быть класс верхнего уровня , как это только используется LinkedList(есть некоторые другие интерфейсы , которые также имеют статические вложенные классы с именами Entry, такими как Map.Entry- то же понятие). А поскольку ему не нужен доступ к членам LinkedList, имеет смысл, чтобы он был статичным - это гораздо более чистый подход.

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

Мэтт Б
источник
Бах, я не могу получить якорную ссылку на комментарий к работе, но вот этот комментарий:#comment113712_253507
Зак Лисобей
1
@matt b Если статический вложенный класс не имеет доступа к членам экземпляра класса Outer, как он взаимодействует с членами экземпляра класса Outer?
Компьютерщик
1
@mattb Но, как заметил @Geek, страница Sun противоречива: A static nested class interacts with the instance members of its outer class (and other classes) just like any other top-level class как это возможно, если один абзац перед документами говорит: « Static nested classes do not have access to other members of the enclosing class Может быть, они хотели бы сказать: A nested (non-static) class interacts with the instance members of its outer class (and other classes) just like any other top-level class
tonix
1
@DavidS Спасибо за ссылку! Да, я был неправ, читая мой комментарий, теперь я вижу, что моя перефразировка была неверной. Как вы сказали: An inner class interacts with the instance members through an implicit reference to its enclosing classи это свидетельствует еще одно интересное свойство non-static inner classes, а также anonymous inner classesили local classes defined inside a block: они не могут иметь в no-argконструктор причина в компилятор неявно предварять последовательность Arg каждого конструктора, чтобы передать ссылку на экземпляр объемлющего класс. Довольно просто
tonix
1
Вы можете использовать статический внутренний класс для создания экземпляра внешнего класса, который имеет только закрытый конструктор. Это используется в шаблоне строителя. Вы не можете сделать то же самое с внутренним классом.
seenimurugan
47

На мой взгляд, вопрос должен быть наоборот, когда вы видите внутренний класс - действительно ли он должен быть внутренним классом, с дополнительной сложностью и неявной (а не явной и более ясной, IMO) ссылкой на экземпляр вмещающего класса?

Имейте в виду, я предвзят как фанат C # - C # не имеет эквивалента внутренних классов, хотя у него есть вложенные типы. Я не могу сказать, что я пропустил внутренние классы еще :)

Джон Скит
источник
4
Я могу ошибаться, но это выглядит для меня как пример статического вложенного класса, а не внутреннего класса. Они даже указывают в примере, что у них нет доступа к переменным экземпляра в окружающем классе во вложенном классе.
ColinD
Да, Колин прав - у C # нет внутренних классов, у него есть вложенные классы. Напоминаем, что статический вложенный класс в C # - это не то же самое, что статический вложенный класс в Java :)
Jon Skeet,
2
Вложенные типы - это одна из тех областей, где C # делает это чрезвычайно правильно по сравнению с Java. Я всегда поражаюсь его семантической / логической правильности ..
nawfal
3
@nawfal: Да, за исключением нескольких недоразумений, я в восторге от того, насколько хорошо был разработан (и указан) язык C #.
Джон Скит
1
@JonSkeet у тебя есть статья или блог о том, что это за штуки? Я хотел бы пройти через то, что вы найдете как "ниггеры" :)
nawfal
27

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

Leigh
источник
Если «внешний» класс является окончательным и, следовательно, не может быть создан вообще, имеет ли этот аргумент смысл в этом случае? Потому что наличие / сохранение ссылки на внешний класс бесполезно, если последний является окончательным.
getsadzeg
10

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

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


источник
8

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

class OuterClass {
    private OuterClass(int x) {
        System.out.println("x: " + x);
    }

    static class InnerClass {
        public static void test() {
            OuterClass outer = new OuterClass(1);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        OuterClass.InnerClass.test();
        // OuterClass outer = new OuterClass(1); // It is not possible to create outer instance from outside.
    }
}

Это выведет x: 1

seenimurugan
источник
7

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

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

Пример такого использования вы можете найти в файле Android R.java (resources). Папка Res Android содержит макеты (содержащие рисунки экрана), папку для рисования (содержащую изображения, используемые для проекта), папку значений (которая содержит строковые константы) и т. Д.

Так как все папки являются частью папки Res, android tool генерирует файл R.java (resources), который внутренне содержит множество статических вложенных классов для каждой из своих внутренних папок.

Вот как выглядит файл R.java, созданный в Android: здесь они используются только для удобства упаковки.

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package com.techpalle.b17_testthird;

public final class R {
    public static final class drawable {
        public static final int ic_launcher=0x7f020000;
    }
    public static final class layout {
        public static final int activity_main=0x7f030000;
    }
    public static final class menu {
        public static final int main=0x7f070000;
    }
    public static final class string {
        public static final int action_settings=0x7f050001;
        public static final int app_name=0x7f050000;
        public static final int hello_world=0x7f050002;
    }
}
user1923551
источник
6

С http://docs.oracle.com/javase/tutorial/java/javaOO/whentouse.html :

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

John29
источник
4

Простой пример:

package test;

public class UpperClass {
public static class StaticInnerClass {}

public class InnerClass {}

public static void main(String[] args) {
    // works
    StaticInnerClass stat = new StaticInnerClass();
    // doesn't compile
    InnerClass inner = new InnerClass();
}
}

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

Vinze
источник
ваш StaticInnerClass на самом деле не является статическим вложенным / внутренним классом. это статический класс верхнего уровня.
theRiley
2

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

PS: я всегда понимал, что «вложенные» и «внутренние» взаимозаменяемы. В терминах могут быть тонкие нюансы, но большинство разработчиков Java поймут это.

Марк Ренуф
источник
1

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

Пистолет
источник
«статический внутренний» является противоречием в терминах.
Маркиз Лорн
1
@ EJP, блин ... люди действительно выходят из себя, указывая на это всякий раз, когда кто-то упоминает «статические внутренние классы» ...
Сакибой
0

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

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


источник
Внутренний класс также не является «частью экземпляра включающего класса».
Маркиз Лорн
внутренний класс экзистенциально зависит от включающего класса и имеет тесный доступ к членам включающего класса, поэтому он фактически является частью включающего класса. на самом деле, это член.
theRiley
0

Использование статического вложенного класса вместо нестатического может в некоторых случаях сэкономить место. Например: реализация Comparatorвнутри класса, скажем, Student.

public class Student {
  public static final Comparator<Student> BY_NAME = new ByName();
  private final String name;
  ...
  private static class ByName implements Comparator<Student> {
    public int compare() {...}
  }
}

Затем он staticгарантирует, что в классе Student есть только один Comparator, а не создает новый экземпляр каждый раз, когда создается новый экземпляр Student.

JenkinsY
источник
-1

Преимущество внутреннего класса--

  1. одноразовое использование
  2. поддерживает и улучшает инкапсуляцию
  3. readibility
  4. доступ к частному полю

Без существующего внешнего класса внутренний класс не будет существовать.

class car{
    class wheel{

    }
}

Есть четыре типа внутреннего класса.

  1. нормальный внутренний класс
  2. Метод Local Внутренний класс
  3. Анонимный внутренний класс
  4. статический внутренний класс

точка ---

  1. из статического внутреннего класса мы можем получить доступ только к статическому члену внешнего класса.
  2. Внутри внутреннего класса мы не можем объявить статический член.
  3. чтобы вызвать нормальный внутренний класс в статической области внешнего класса.

    Outer 0=new Outer(); Outer.Inner i= O.new Inner();

  4. inorder для вызова нормального внутреннего класса в области экземпляра внешнего класса.

    Inner i=new Inner();

  5. Чтобы вызвать нормальный внутренний класс снаружи внешнего класса.

    Outer 0=new Outer(); Outer.Inner i= O.new Inner();

  6. Внутренний класс Это указатель на внутренний класс.

    this.member-current inner class outerclassname.this--outer class

  7. для внутреннего класса применим модификатор - public, default,

    final,abstract,strictfp,+private,protected,static

  8. external $ inner - это имя внутреннего класса.

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

Класс 10.inner внутри статического метода, тогда мы можем получить доступ только к статическому полю

внешний класс.

class outer{

    int x=10;
    static int y-20;

    public void m1() {
        int i=30;
        final j=40;

        class inner{

            public void m2() {
                // have accees x,y and j
            }
        }
    }
}
Гаурав Тивари
источник