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

У меня есть класс типа 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);
}

Я пытаюсь объявить переменную static, специфичную для PLUS, но получаю ошибку:

поле «имя» не может быть объявлено статическим в нестатическом внутреннем типе.

Почему я не могу определить статическую константу внутри PLUS, если PLUS является анонимным классом? Два сообщения об ошибке звучат противоречиво, так как первое сообщение об ошибке говорит, что PLUS(){...} имеет статический контекст, а второе сообщение об ошибке говорит, что PLUS(){...} имеет нестатический контекст. Теперь я еще больше запутался.


person user842225    schedule 10.03.2015    source источник
comment
Мой вопрос заключается в том, почему я получаю эту ошибку и есть ли способ ее решить, но оставить printMe() нестатическим   -  person user842225    schedule 10.03.2015
comment
сделать printMe() защищенным и вызвать this.printMe(val) внутри apply()   -  person Alex Salauyou    schedule 10.03.2015
comment
@SashaSalauyou, да, переход на защищенный заставляет его работать. Но опять же, мой вопрос в том, почему я получаю эту ошибку, и, пожалуйста, также объясните, почему изменение на защищенное заставляет его работать? Пожалуйста, сделайте ответ, который я могу принять. Суть в том, чтобы понять причину, а не просто заставить ее работать. Спасибо.   -  person user842225    schedule 10.03.2015
comment
Метод printMe является закрытым в вашем перечислении, каждое из ваших перечислений является реализацией класса перечисления, поэтому у них нет доступа к частному методу в их родительском классе. Меня озадачивает характер ошибки.   -  person Edwin Dalorzo    schedule 10.03.2015
comment
@EdwinDalorzo это неудивительно, анонимный подкласс enum не увидит поле своего родителя. Исправление состоит в том, чтобы сделать private void printMe(double val) { protected   -  person EpicPandaForce    schedule 15.04.2015


Ответы (8)


Ну странный случай.

Похоже, что проблема в следующем:

  • #P3# <блочная цитата> #P4#
  • #P5# <блочная цитата> #P6#
  • #P7# <блочная цитата> #P8# #P9#
  • #P10# <цитата> #P11# #P12#

Короче говоря, поскольку 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):

Необязательное тело класса константы перечисления неявно определяет объявление анонимного класса, которое расширяет непосредственно охватывающий тип перечисления. Тело класса регулируется обычными правилами анонимных классов [...].

person Radiodef    schedule 10.03.2015
comment
Это определенно лучший ответ, который я когда-либо читал по этому поводу. Очень интересно. - person Edwin Dalorzo; 10.03.2015

Я не думаю, что у меня есть ответ о характере ошибки, но, возможно, я могу немного внести свой вклад в обсуждение.

Когда компилятор 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 в одном из ваших классов перечисления.

Этот код выше дает мне ту же самую ошибку, которую вы получаете в своем перечислении: «ошибка: на нестатический метод bar() нельзя ссылаться из статического контекста».

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

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

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

person Edwin Dalorzo    schedule 10.03.2015
comment
В вашем примере, когда вы делаете System.out.println(Operation.ONE.getClass());, результат будет class Operation$1, как и следовало ожидать. Однако, когда вы делаете то же самое с версией перечисления, результатом будет просто class Operation. Вы знаете, почему это так? - person Paul Boddington; 10.03.2015
comment
Я не знаю, что вы имеете в виду. Я получаю Operation$1 в качестве имени класса, когда я делаю System.out.println(Operation.ONE.getClass()) как в версии enum, так и в версии, определенной вручную, которую я сделал выше. Учтите, что анонимный класс не определен, если у вас нет абстрактного метода в перечислении. - person Edwin Dalorzo; 10.03.2015
comment
Я использую Java 8, но это не имеет значения. Это не изменилось. Ради аргумента я только что попробовал в Java 7, и это то же самое. - person Edwin Dalorzo; 10.03.2015
comment
Извините, вы абсолютно правы. Я очень тугой. Я просто делал enum Operation { ONE }. - person Paul Boddington; 10.03.2015
comment
В этом случае это имеет смысл, потому что в этом отношении не будет определен анонимный класс. Он может повторно использовать сам класс перечисления, поскольку класс является конкретным. Но когда класс enum является абстрактным, он требует определения из него конкретных классов, которые реализуют абстрактные методы. В этих случаях мы видим, что анонимный класс определяется в синтетическом коде, выполненном компилятором. - person Edwin Dalorzo; 10.03.2015
comment
Спасибо, не могли бы вы проверить мое ОБНОВЛЕНИЕ в моем сообщении? Спасибо. - person user842225; 10.03.2015

На вопрос, следующий за вашим обновлением, легко ответить. Анонимные классы никогда не допускают статических членов.

Что касается вашего первоначального вопроса, самый ясный способ понять, что происходит, - это попробовать вместо этого this.printMe();. Тогда сообщение об ошибке становится намного проще для понимания и дает настоящую причину, по которой printMe(); не работает:

'printMe(double)' has private access in 'Operation'

Причина, по которой вы не можете использовать printMe, заключается в том, что это private, а тип времени компиляции ссылки this является анонимным классом расширения Operation, а не самим Operation (см. ответ Эдвина Далорзо). Вы получаете другое сообщение об ошибке, когда просто пишете printMe();, потому что по какой-то причине компилятор даже не понимает, что вы пытаетесь вызвать метод экземпляра для this. Это дает сообщение об ошибке, которое имело бы смысл, если бы вы пытались вызвать printMe вообще без экземпляра (т. Е. Как если бы это был статический метод). Сообщение об ошибке не изменится, если вы сделаете это явным, написав Operation.printMe();.

Два способа обойти это: сделать printMe защищенным или написать

((Operation) this).printMe();
person Paul Boddington    schedule 10.03.2015

printMe не должно быть private, так как вы получаете новый анонимный класс с помощью PLUS.

protected void printMe(double val) {

Что касается характера ошибки, enum/Enum — это своего рода артефакт; на данный момент это ускользает от меня: внутренний класс может получить доступ к личным вещам...

person Joop Eggen    schedule 10.03.2015
comment
Думаю, это установлено. Вопрос в странной природе ошибки. Почему ошибка статического контекста, а не просто классическое сообщение о том, что метод не существует или недоступен? - person Edwin Dalorzo; 10.03.2015
comment
Извините, мой ответ уже добавлен к ответу. Я предполагаю, что может быть вызван статический метод private, как и обычный метод protected. И компилятор выдал слишком узкую ошибку. - person Joop Eggen; 10.03.2015

каков тип PLUS()?
ну, это в основном тип enum Operation.

Если вы хотите сравнить его с java class, это в основном object того же класса внутри себя.

теперь enum Operation имеет абстрактный метод apply, что означает, что любой из этого типа (а именно операция) должен реализовать этот метод. Все идет нормально.

теперь сложная часть, где вы получаете ошибку.

как видите, PLUS() в основном является типом Operation. Operation имеет метод private printMe(), что означает, что только сам enum Operation может видеть его, а не любое другое перечисление, включая подперечисления (точно так же, как подкласс и суперкласс в java). также этот метод не является static, что означает, что вы не можете вызвать его, если не создадите экземпляр класса. так что у вас есть два варианта решения проблемы,

  1. сделайте printMe() method static, как предлагает компилятор
  2. сделать метод protected так что любые sub-enum inherits эти методы.
person nafas    schedule 10.03.2015

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

Доступ к охватывающему методу printMe(double) из типа Operation эмулируется синтетическим методом доступа

Единственное, что в данном случае не работает, это приватный нестатический. Все остальные работы по обеспечению безопасности, т.е. частный статический, защищенный нестатический и т. д. Как кто-то еще упомянул, PLUS является реализацией операции, поэтому частный технически не работает, Java автоматически делает одолжение, исправляя его для вас с помощью функции автоматического синтетического доступа.

person Necreaux    schedule 10.03.2015

Статический метод printMe устраняет ошибку компиляции:

private static void printMe(long val) {
    System.out.println("val = " + val);
}
person Oscar    schedule 10.03.2015

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

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

person ajb    schedule 15.04.2015