Является ли тело континуума определенным классом статическим или нестатическим?

У меня есть класс типа 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, но в этом случае это определенно помогло мне увидеть их как почти такую ​​же конструкцию.)

То же самое относится к ссылкам на защищенные и частные поля.