Почему Java запрещает статические поля во внутренних классах?

85
class OuterClass {
 class InnerClass {
  static int i = 100; // compile error
  static void f() { } // compile error
 }
} 

Хотя невозможно получить доступ к статическому полю с помощью OuterClass.InnerClass.i, если я хочу записать что-то, что должно быть статическим, например, количество созданных объектов InnerClass, было бы полезно сделать это поле статическим. Так почему же Java запрещает статические поля / методы во внутренних классах?

РЕДАКТИРОВАТЬ: Я знаю, как сделать компилятор довольным статическим вложенным классом (или статическим внутренним классом), но я хочу знать, почему Java запрещает статические поля / методы внутри внутренних классов (или обычного внутреннего класса) как из языковой конструкции, так и из аспекты реализации, если кто-то знает об этом больше.

Jichao
источник
3
Мой любимый пример - это Logger только для внутреннего класса. Он не может быть статичным, как все остальные регистраторы.
Piotr Findeisen

Ответы:

32

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

8.1.2 Внутренние классы и закрывающие экземпляры

Внутренний класс - это вложенный класс, который явно или неявно не объявлен статическим. Внутренние классы не могут объявлять статические инициализаторы (§8.7) или интерфейсы-члены. Внутренние классы не могут объявлять статические члены, если только они не являются полями констант времени компиляции (§15.28).

Грегори Пакош
источник
18
может быть , это просто решил так
Грегори Pakosz
3
вы не можете создать нестатический внутренний объект без родительской ссылки, но вы все равно можете его инициализировать .
skaffman
Если загрузчики ClassLoaders хранят кеш, в котором указано «Класс X был инициализирован», то их логика не может использоваться для инициализации нескольких экземпляров Class [объектов, представляющих] X (что необходимо, когда объекты класса должны быть созданы как внутренние классы внутри нескольких отдельные объекты).
Эрвин Смаут,
@skaffman Это все еще не имеет смысла. Статические свойства внутреннего класса будут инициализированы только один раз, так в чем же проблема? Прямо сейчас у меня есть статическая хэш-карта, и у меня есть около 4 методов, которые управляют только этой картой, что делает более идеальным группировку всего во внутреннем классе. Однако статическая хеш-карта теперь должна будет жить снаружи и, возможно, с другими связанными с ней вещами, что просто глупо. В чем будет проблема с инициализацией статических свойств?
ммм
54

я хочу знать, почему Java запрещает статические поля / методы внутри внутренних классов

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

Поскольку они являются «экземпляровыми» классами, нет никакого смысла разрешать staticфункции, поскольку они staticизначально предназначены для работы без экземпляра.

Это как если бы вы одновременно пытались создать статический / экземплярный атрибут.

Возьмем следующий пример:

class Employee {
    public String name;
}

Если вы создаете два экземпляра сотрудника:

Employee a = new Employee(); 
a.name = "Oscar";

Employee b = new Employee();
b.name = "jcyang";

Понятно, почему у каждого своя стоимость собственности name?

То же самое происходит с внутренним классом; каждый экземпляр внутреннего класса не зависит от другого экземпляра внутреннего класса.

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

class Employee {
    public String name;
    class InnerData {
        static count; // ??? count of which ? a or b? 
     }
}

Когда вы создаете экземпляр aи bв приведенном выше примере, какое значение будет правильным для статической переменной count? Определить его невозможно, поскольку существование InnerDataкласса полностью зависит от каждого из окружающих объектов.

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

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

OscarRyz
источник
4
Я куплю ваше объяснение статических свойств внутреннего класса, но, как указывает @skaffman в комментарии к моему ответу, как насчет статических методов ? Кажется, что методы должны быть разрешены, не требуя их отделения от какого-либо экземпляра. Действительно, в java вы можете вызывать статические методы для экземпляров (хотя это считается плохим стилем). BTW: Я попросил коллегу , чтобы попытаться скомпилировать код в OP в качестве C # и это делает компиляции. Итак, C #, очевидно, допускает это, что демонстрирует, что то, что хочет сделать OP, не нарушает какой-то фундаментальный принцип объектно-ориентированного программирования.
Асаф,
2
Точно так же происходит и с методами. Дело здесь не в том, являются ли атрибуты или методы статистическими или нет, а в том, что внутренний класс сам является экземпляром "материала". Я имею в виду, проблема здесь в том, что экземпляр такого внутреннего класса не существует, пока не будет создан внешний класс. Следовательно, какой метод будет отправлен, если нет ничего. Вы попадете в воздух только потому, что вам в первую очередь понадобится экземпляр.
OscarRyz
1
Насчет C # ... ну. Это не объектно-ориентированный подход только потому, что это позволяет C #, я не имею в виду, что это неправильно, но C # включает несколько парадигм, упрощающих разработку даже за счет согласованности (вам нужно изучать новые вещи с каждой версией .NET), и это позволяет это и другие вещи среди прочего. Я думаю, это хорошо. Если сообщество сочтет, что дополнительная функция - это достаточно круто, в C # она может появиться в будущем.
OscarRyz
2
@OscarRyz Зачем вам нужны экземпляры внутреннего класса для использования его статических методов / полей? И примером [полезного] статического метода во внутреннем классе является частный вспомогательный метод.
Леонид Семенов
1
Использование finalстатических полей разрешено во внутреннем классе в java. Как вы объясните этот сценарий?
Number945 09
34

InnerClassне может иметь staticчленов, потому что принадлежит экземпляру (из OuterClass). Если вы объявите InnerClassкак staticотсоединить его от экземпляра, ваш код будет скомпилирован.

class OuterClass {
    static class InnerClass {
        static int i = 100; // no compile error
        static void f() { } // no compile error
    }
}

Кстати: вы по-прежнему сможете создавать экземпляры InnerClass. staticв этом контексте позволяет этому произойти без включающего экземпляра OuterClass.

Асаф
источник
6
InnerClassвовсе не принадлежит OuterClass, экземпляры из него сделать. Сами по себе два класса не имеют такой связи. Вопрос, почему у вас не может быть статических методов в InnerClassнеподвижном состоянии, остается.
skaffman
9

Фактически, вы можете объявить статические поля, если они являются константами и написаны во время компиляции.

class OuterClass {
    void foo() {
        class Inner{
            static final int a = 5; // fine
            static final String s = "hello"; // fine
            static final Object o = new Object(); // compile error, because cannot be written during compilation
        }
    }
}
вмолчанов
источник
8
  1. Последовательность инициализации класса является важной причиной.

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

  • Используется статическое поле, объявленное T, и поле не является постоянной переменной.

Поэтому, если внутренний класс имеет доступ к статическому полю, это вызовет инициализацию внутреннего класса, но это не гарантирует, что включающий класс инициализирован.

  1. Это нарушило бы некоторые основные правила . вы можете перейти к последнему разделу (чтобы two cases), чтобы избежать нубов

Одна вещь , когда некоторые это он будет вести себя так же , как обычный класс во всех отношениях , и это связано с внешним классом.static nested classnested classstatic

Но концепция Inner class/ будет связана с классом внешнего / включающего. Обратите внимание, что связано с экземпляром, а не с классом. Теперь связь с экземпляром явно означает, что ( из концепции переменной экземпляра ) она будет существовать внутри экземпляра и будет отличаться от экземпляра. non-static nested classinstance

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

Итак, если Java позволяет нам использовать статическую переменную внутри не статического вложенного класса. будет два случая .

  • Если он используется совместно со всем экземпляром внутреннего класса, это нарушит концепцию context of instance(переменной экземпляра). Тогда это НЕТ.
  • Если он не является общим для всех экземпляров, это нарушит концепцию статичности. Снова НЕТ.
Саиф
источник
5

Вот мотивация, которую я считаю наиболее подходящей для этого "ограничения": вы можете реализовать поведение статического поля внутреннего класса как поле экземпляра внешнего объекта; Так что вам не нужны статические поля / методы . Я имею в виду, что все экземпляры внутреннего класса некоторого объекта совместно используют поле (или метод).

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

public class Outer{
    int nofInner; //this will count the inner class 
                  //instances of this (Outer)object
                  //(you know, they "belong" to an object)
    static int totalNofInner; //this will count all 
                              //inner class instances of all Outer objects
    class Inner {
        public Inner(){
            nofInner++;
            totalNofInner++;
        }
    }
}
Янос
источник
2
Но вопрос в том, что является причиной разрешения статических полей, когда они объявлены finalтогда?
Solace
если вы посмотрите на [ stackoverflow.com/a/1954119/1532220] (ответ OscarRyzs выше): его мотивация состоит в том, что значение не может быть связано с переменной. Конечно, если переменная окончательная, вы можете легко узнать, какое значение присвоить (вы должны знать).
ianos
2

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

ПРИМЕЧАНИЕ: всегда относитесь к внутренним классам как к переменной для внешнего класса, они могут быть статическими или нестатическими, как и любые другие переменные.

Манну
источник
Но у внутреннего класса могут быть static finalконстанты.
Raining
1

Потому что это вызвало бы двусмысленность в значении слова «статика».

Внутренние классы не могут объявлять статические члены, кроме констант времени компиляции. Значение слова «статика» было бы двусмысленным. Означает ли это, что на виртуальной машине только один экземпляр? Или только один экземпляр на внешний объект? Разработчики языка решили не заниматься этим вопросом.

Взято из "Core Java SE 9 для нетерпеливых" Кей С. Хорстманн. Стр. 90 Глава 2.6.3

Эйдж Хришикеш
источник
-1

Думаю, это для последовательности. Хотя, похоже, для этого нет никаких технических ограничений, вы не сможете получить доступ к статическим членам внутреннего класса извне, т. Е. OuterClass.InnerClass.iПотому, что средний шаг не статичен.

Chochos
источник
Но у внутреннего класса могут быть static finalконстанты.
Raining