Статический вложенный класс в Java, почему?
Я смотрел на код Java для LinkedList
и заметил, что он использовал статический вложенный класс, Entry
,
public class LinkedList<E> ... {
...
private static class Entry<E> { ... }
}
В чем причина использования статического вложенного класса, а не нормального внутреннего класса?
Единственная причина, по которой я мог придумать, заключалась в том, что Entry не имеет доступа к переменным экземпляра, поэтому с точки зрения ООП он лучше инкапсулируется.
Но я подумал, что могут быть и другие причины, возможно, производительность. Что бы это могло быть?
Заметка. Я надеюсь, что мои термины верны, я бы назвал это статическим внутренним классом, но я думаю, что это неправильно: http://java.sun.com/docs/books/tutorial/java/javaOO/nested.html
13 ответов
На странице Sun, на которую вы ссылаетесь, есть несколько ключевых отличий:
Вложенный класс является членом включающего его класса. Нестатические вложенные классы (внутренние классы) имеют доступ к другим членам включающего класса, даже если они объявлены закрытыми. Статические вложенные классы не имеют доступа к другим членам включающего класса.
...Примечание. Статический вложенный класс взаимодействует с членами экземпляра своего внешнего класса (и других классов) так же, как и любой другой класс верхнего уровня. По сути, статический вложенный класс является поведенческим классом верхнего уровня, который был вложен в другой класс верхнего уровня для удобства упаковки.
Там нет необходимости LinkedList.Entry
быть классом верхнего уровня, так как он используется только LinkedList
(есть некоторые другие интерфейсы, которые также имеют статические вложенные классы с именем Entry
, такие как Map.Entry
- та же концепция). А поскольку ему не нужен доступ к членам LinkedList, имеет смысл, чтобы он был статичным - это гораздо более чистый подход.
Как отмечает Джон Скит, я думаю, что если вы используете вложенный класс, лучше начать с того, чтобы он был статическим, а затем решить, действительно ли он должен быть нестатичным, основываясь на вашем использовании.
На мой взгляд, вопрос должен быть наоборот, когда вы видите внутренний класс - действительно ли он должен быть внутренним классом, с дополнительной сложностью и неявной (а не явной и более ясной) ссылкой на экземпляр вмещающего класса?
Имейте в виду, я предвзят как фанат C# - C# не имеет эквивалента внутренних классов, хотя у него есть вложенные типы. Я не могу сказать, что я пропустил внутренние классы еще:)
Здесь есть неочевидные проблемы с хранением памяти, которые необходимо учитывать. Так как нестатический внутренний класс поддерживает неявную ссылку на свой "внешний" класс, если на экземпляр внутреннего класса жестко ссылаются, то на внешний экземпляр также жестко ссылаются. Это может привести к некоторым царапинам, когда внешний класс не является сборщиком мусора, даже если кажется, что ничто на него не ссылается.
Статический внутренний класс используется в шаблоне компоновщика. Статический внутренний класс может создавать экземпляр своего внешнего класса, который имеет только закрытый конструктор. Таким образом, вы можете использовать статический внутренний класс для создания экземпляра внешнего класса, который имеет только закрытый конструктор. Вы не можете сделать то же самое с внутренним классом, так как вам нужно создать объект внешнего класса до доступа к внутреннему классу.
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.
}
}
Это выведет х: 1
Статический вложенный класс, как и любой другой внешний класс, не имеет доступа к внешним элементам класса.
Просто для удобства упаковки мы можем объединить статические вложенные классы в один внешний класс для удобства чтения. Кроме этого нет другого варианта использования статического вложенного класса.
Пример такого использования вы можете найти в файле 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;
}
}
Ну, во-первых, нестатические внутренние классы имеют дополнительное скрытое поле, которое указывает на экземпляр внешнего класса. Таким образом, если бы класс Entry не был статичным, то, помимо доступа, в котором он не нуждается, он будет содержать около четырех указателей вместо трех.
Как правило, я бы сказал, что если вы определяете класс, который в основном должен действовать как набор элементов данных, например, как "struct" в C, подумайте о том, чтобы сделать его статичным.
С http://docs.oracle.com/javase/tutorial/java/javaOO/whentouse.html:
Используйте нестатический вложенный класс (или внутренний класс), если вам требуется доступ к непубличным полям и методам включающего экземпляра. Используйте статический вложенный класс, если вам не нужен этот доступ.
Простой пример:
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 является статической функцией)
JVM не знает вложенных классов. Вложенность - это просто синтаксический сахар.
На изображениях ниже показан файл Java:
На изображениях ниже показано представление файла java в файлах классов:
Обратите внимание, что генерируются 2 файла классов, один для родительского и другой для вложенного класса.
Объекты нестатического вложенного класса имеют доступ к охватывающей области. Этот доступ к охватывающей области поддерживается путем удержания неявной ссылки на охватывающий объект области во вложенном объекте.
Вложенный класс - это способ представить намерение, что тип вложенного класса представляет компонент родительского класса.
public class Message { private MessageType messageType; // component of parent class public enum MessageType { SENT, RECEIVE; } } class Otherclass { public boolean isSent(Message message) { if (message.getMessageType() == MessageType.SENT) { // accessible at other places as well return true; } return false; } }
частный статический вложенный класс представляет точку № 3 и тот факт, что вложенный тип может быть только подкомпонентом родительского класса. Его нельзя использовать отдельно.
public class Message { private Content content; // Component of message class private static class Content { // can only be a component of message class private String body; private int sentBy; public String getBody() { return body; } public int getSentBy() { return sentBy; } } } class Message2 { private Message.Content content; // Not possible }
Подробнее здесь.
Одна из причин статического и нормального состояния связана с загрузкой классов. Вы не можете создать экземпляр внутреннего класса в конструкторе его родителя.
PS: я всегда понимал, что "вложенные" и "внутренние" взаимозаменяемы. В терминах могут быть тонкие нюансы, но большинство разработчиков Java поймут это.
Нестатические внутренние классы могут привести к утечкам памяти, в то время как статический внутренний класс защитит от них. Если внешний класс содержит значительные данные, это может снизить производительность приложения.
Использование статического вложенного класса вместо нестатического может в некоторых случаях экономить место. Например: реализация Comparator
в классе, скажем, студент.
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.
Я не знаю о разнице в производительности, но, как вы говорите, статический вложенный класс не является частью экземпляра включающего класса. Кажется, проще создать статический вложенный класс, если вам действительно не нужно, чтобы он был внутренним классом.
Это немного похоже на то, почему я всегда делаю свои переменные окончательными в Java - если они не окончательные, я знаю, что с ними происходит что-то смешное. Если вы используете внутренний класс вместо статического вложенного класса, должна быть веская причина.
Преимущество внутреннего класса--
- одноразовое использование
- поддерживает и улучшает инкапсуляцию
- readibility
- доступ к частному полю
Без существующего внешнего класса внутренний класс не будет существовать.
class car{
class wheel{
}
}
Есть четыре типа внутреннего класса.
- нормальный внутренний класс
- Метод Local Внутренний класс
- Анонимный внутренний класс
- статический внутренний класс
точка ---
- из статического внутреннего класса мы можем получить доступ только к статическому члену внешнего класса.
- Внутри внутреннего класса мы не можем объявить статический член.
чтобы вызвать нормальный внутренний класс в статической области внешнего класса.
Outer 0=new Outer(); Outer.Inner i= O.new Inner();
inorder для вызова нормального внутреннего класса в области экземпляра внешнего класса.
Inner i=new Inner();
чтобы вызвать нормальный внутренний класс снаружи внешнего класса.
Outer 0=new Outer(); Outer.Inner i= O.new Inner();
Внутренний класс Это указатель на внутренний класс.
this.member-current inner class outerclassname.this--outer class
для внутреннего класса применим модификатор - public,default,
final,abstract,strictfp,+private,protected,static
external $inner - это имя внутреннего класса.
внутренний класс внутри метода экземпляра, тогда мы можем получить доступ к статическому и экземпляру поля внешнего класса.
Класс 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
}
}
}
}