Является ли тело континуума определенным классом статическим или нестатическим?
У меня есть класс типа enum:
public enum Operation {
PLUS() {
@Override
double apply(double x, double y) {
// ERROR: Cannot make a static reference
// to the non-static method printMe()...
printMe(x);
return x + y;
}
};
private void printMe(double val) {
System.out.println("val = " + val);
}
abstract double apply(double x, double y);
}
Как вы видите выше, я определил один тип enum
, который имеет значение PLUS
. Он содержит тело с постоянной спецификой. В своем теле я попытался вызвать printMe(val);
, но я получил ошибку компиляции:
Невозможно сделать статическую ссылку на нестатический метод printMe().
Почему я получаю эту ошибку? Я имею в виду, что я переопределяю абстрактный метод в теле PLUS
. Почему он находится в области static
? Как избавиться от него?
Я знаю, что добавление ключевого слова static
на printMe(){...}
решает проблему, но мне интересно узнать, есть ли другой способ, если я хочу сохранить printMe()
нестатический?
Другая проблема, аналогичная предыдущей, но на этот раз сообщение об ошибке звучит наоборот, т.е. PLUS(){...}
имеет нестатический контекст:
public enum Operation {
PLUS() {
// ERROR: the field "name" can not be declared static
// in a non-static inner type.
protected static String name = "someone";
@Override
double apply(double x, double y) {
return x + y;
}
};
abstract double apply(double x, double y);
}
Я пытаюсь объявить переменную PLUS
-специфическая static
, но в итоге я получаю сообщение об ошибке:
поле "имя" не может быть объявлено статическим в нестационарном внутреннем типе.
Почему я не могу определить статическую константу внутри PLUS
, если PLUS
является анонимным классом? Два сообщения об ошибках звучат противоречиво друг другу, так как первое сообщение об ошибке говорит, что PLUS(){...}
имеет статический контекст, в то время как второе сообщение об ошибке сообщает, что PLUS(){...}
имеет нестатический контекст, Я еще больше запутался.
Ответы
Ответ 1
Ну, это странный случай.
Похоже, проблема такова:
-
В этом случае частный член должен быть доступен (6.6.1.):
В противном случае член или конструктор объявляется private
, и доступ разрешен тогда и только тогда, когда он встречается внутри тела класса верхнего уровня, который включает объявление члена или конструктора.
-
Однако частные члены не наследуются (8.2):
Члены класса, объявленные private
, не наследуются подклассами этого класса.
-
Поэтому printMe
не является членом анонимного подкласса, а компилятор ищет его в суперклассе * Operation
(15.12.1):
Если есть объявляющее объявление типа , из которого этот метод является членом, пусть T является самым внутренним объявлением такого типа. Класс или интерфейс для поиска - T.
Эта политика поиска называется "правил расческой". Он эффективно ищет методы в иерархии суперкласса вложенных классов, прежде чем искать методы в окружающем классе и его иерархию суперкласса.
-
И вот здесь странно. Поскольку printMe
находится в классе, который также заключает в себя PLUS
, объект, которому вызван метод, вместо этого определяется как охватывающий экземпляр Operation
, который не существует (15.12.4.1):
В противном случае пусть T - объявляющее объявление типа, членом которого является метод, и n - целое число, такое, что T является объявлением типа n'th lexically enclosing класса, объявление которого немедленно содержит вызов метода. Целевой ссылкой является n-й лексически охватывающий экземпляр this
.
Это ошибка времени компиляции, если n-й лексически охватывающий экземпляр this
не существует.
Короче говоря, поскольку printMe
является только членом Operation
(и не наследуется), компилятор вынужден вызывать printMe
в несуществующем внешнем экземпляре.
Однако этот метод все еще доступен, и мы можем найти его, указав вызов:
@Override
double apply(double x, double y) {
// now the superclass is searched
// but the target reference is definitely 'this'
// vvvvvv
super.printMe(x);
return x + y;
}
Два сообщения об ошибке звучат противоречиво друг другу [...].
Да, это запутанный аспект языка. С одной стороны, анонимный класс никогда не статичен (15.9.5), с другой стороны, выражение анонимного класса может появляться в статическом контексте и, следовательно, не имеет (8.1.3).
Анонимный класс всегда является внутренним классом; это никогда не static
.
Экземпляр внутреннего класса I
, чье объявление происходит в статическом контексте, не имеет лексически закрывающих экземпляров.
Чтобы понять, как это работает, вот отформатированный пример:
class Example {
public static void main(String... args) {
new Object() {
int i;
void m() {}
};
}
}
Все в italics
- это статический контекст. Анонимный класс, полученный из выражения в bold
, считается внутренним и нестатическим (но не содержит экземпляра Example
).
Так как анонимный класс не статичен, он не может объявлять статические не постоянные члены, несмотря на то, что сам он объявлен в статическом контексте.
* Помимо того, что немного затушевывает вопрос, что Operation
является перечислением, совершенно не имеет значения (8.9.1):
Необязательное тело класса константы enum неявно определяет анонимное объявление класса, которое расширяет сразу включаемый тип перечисления. Тело класса определяется обычными правилами анонимных классов [...].
Ответ 2
Я не думаю, что у меня есть ответ о характере ошибки, но, возможно, я могу внести свой вклад в обсуждение.
Когда компилятор Java компилирует ваш код перечисления, он производит синтетический класс, который выглядит следующим образом:
class Operation {
protected abstract void foo();
private void bar(){ }
public static final Operation ONE;
static {
ONE = new Operation() {
@Override
protected void foo(){
bar();
}
};
}
}
Вы можете проверить, что код перечисления выглядит примерно так: запуск javap в одном из классов enum.
Этот код, приведенный выше, дает мне ту же самую ошибку, которую вы получаете по вашему перечислению: "Ошибка: нестатическая панель методов() не может быть указана из статического контекста".
Итак, компилятор считает, что вы не можете вызвать метод bar()
, который является методом экземпляра, из статического контекста, в котором определяется анонимный класс.
Мне это не имеет смысла, оно должно быть либо доступным, либо лишенным доступа, но ошибка не кажется точным. Я все еще озадачен, но это похоже на то, что на самом деле происходит.
Было бы разумнее, если бы компиляторы сказали, что анонимный класс не имеет доступа к foo
, поскольку он является закрытым в своем родительском классе, но компилятор запускает эту другую ошибку.
Ответ 3
Вопрос, следующий за вашим обновлением, легко ответить. Анонимные классы никогда не допускают статические члены.
Что касается вашего первоначального вопроса, самый простой способ понять, что происходит, - попробовать вместо this.printMe();
. Тогда сообщение об ошибке намного легче понять и дает реальную причину printMe();
не работает:
'printMe(double)' has private access in 'Operation'
Причина, по которой вы не можете использовать printMe
, состоит в том, что она является private
, а тип времени компиляции this
является анонимным классом расширения Operation
, а не Operation
(см. Edwin Далорзо ответ). При написании printMe();
появляется другое сообщение об ошибке, потому что по какой-то причине компилятор даже не понимает, что вы пытаетесь вызвать метод экземпляра на this
. Он дает сообщение об ошибке, которое имело бы смысл, если бы вы пытались вызвать printMe
ни в одном экземпляре (т.е. Как если бы это был статический метод). Сообщение об ошибке не изменяется, если вы сделаете это явным, написав Operation.printMe();
.
Два способа обойти это - сделать printMe
protected или написать
((Operation) this).printMe();
Ответ 4
printMe
не должен быть private
, поскольку вы получаете новый анонимный класс с PLUS.
protected void printMe(double val) {
Что касается характера ошибки, enum/Enum, это немного артефакт; он ускользает от меня в данный момент: внутренний класс может получить доступ к частным вещам...
Ответ 5
какой тип PLUS()
?
ну это в основном тип enum Operation
.
Если вы хотите сравнить его с java class
, это в основном object
того же самого класса внутри себя.
теперь enum Operation
имеет абстрактный метод apply
, который означает, что любой из этого типа (а именно операция) должен реализовать этот метод. Все идет нормально.
теперь сложная часть, в которой вы получаете ошибку.
как вы видите, PLUS()
- это в основном тип Operation
. Operation
имеет метод private
printMe()
, означающий, что только enum Operation
сам может видеть это не какое-либо другое перечисление, включая под-перечисления (точно так же, как подкласс и суперкласс в java). также этот метод не является static
, означающим, что вы не можете его называть, если вы не создаете экземпляр класса. поэтому у вас есть два варианта решения проблемы,
- сделайте
printMe() method static
как компилятор предложить
- сделайте метод
protected
таким образом, любой sub-enum
inherits
этот метод.
Ответ 6
В этой ситуации причиной статических работ является функция автоматического синтетического доступа. Вы все равно получите предупреждение следующего компилятора, если оно является приватным.
Доступ к закрывающему методу printMe (double) из типа Operation is эмулируемый синтетическим методом доступа.
Единственное, что в этом случае не работает, - это частный нестатический. Все другие меры безопасности, например приватный статический, защищенный нестатический и т.д. Как упоминал еще один PLUS - это реализация операции, поэтому частная технически не работает, Java автоматически делает пользу для ее исправления с помощью автоматической синтетической функции доступа.
Ответ 7
Создание метода printMe static решает ошибку компиляции:
private static void printMe(long val) {
System.out.println("val = " + val);
}
Ответ 8
Я боролся с этим совсем немного, но я думаю, что лучший способ понять это - посмотреть на аналогичный случай, который не включает enum
:
public class Outer {
protected void protectedMethod() {
}
private void privateMethod() {
}
public class Inner {
public void method1() {
protectedMethod(); // legal
privateMethod(); // legal
}
}
public static class Nested {
public void method2() {
protectedMethod(); // illegal
privateMethod(); // illegal
}
}
public static class Nested2 extends Outer {
public void method3() {
protectedMethod(); // legal
privateMethod(); // illegal
}
}
}
Объект класса Inner
является внутренним классом; каждый такой объект содержит скрытую ссылку на объект охватывающего класса Outer
. Поэтому призывы к protectedMethod
и privateMethod
являются законными. Они вызываются в содержащем объекте Outer
, т.е. hiddenOuterObject.protectedMethod()
и hiddenOuterObject.privateMethod()
.
Объектом класса Nested
является статический вложенный класс; нет объекта класса Outer
, связанного с ним. Поэтому вызовы protectedMethod
и privateMethod
являются незаконными - нет объекта Outer
, над которым они будут работать. Сообщение об ошибке non-static method <method-name>() cannot be referenced from a static context
. (Обратите внимание, что privateMethod
все еще отображается на этом этапе.Если method2
имел другой объект Outer
типа Outer
, он мог бы называть outer.privateMethod()
легально. Но в примере кода для него нет объекта для работы.)
Объект класса Nested2
аналогично статический вложенный класс, но с твистом, который он расширяет Outer
. Поскольку защищенные члены Outer
будут унаследованы, это делает законным вызов protectedMethod()
; он будет работать на объекте класса Nested2
. Частный метод privateMethod()
не наследуется. Поэтому компилятор обрабатывает его так же, как и для Nested
, что приводит к той же ошибке.
Случай enum
очень похож на случай Nested2
. Каждая константа перечисления с телом вызывает создание нового анонимного подкласса Operation
, но это фактически статический вложенный класс (хотя анонимные классы обычно являются внутренними классами). Объект PLUS
не имеет скрытой ссылки на объект класса Operation
. Таким образом, публичные и защищенные члены, которые наследуются, могут ссылаться и работать на объекте PLUS
; но ссылки на частные члены в Operation
не наследуются, и к ним нельзя получить доступ, потому что нет скрытого объекта для работы. Сообщение об ошибке, Cannot make a static reference to the non-static method printMe()
, в значительной степени совпадает с non-static method cannot be referenced from a static context
, только со словами в другом порядке. (Я не утверждаю, что все языковые правила похожи на случай Nested2
, но в этом случае это определенно помогло мне увидеть их как почти такую же конструкцию.)
То же самое относится к ссылкам на защищенные и частные поля.