Подтвердить что ты не робот

Как использовать поля в перечислении java, переопределяя метод?

Задача состоит в том, чтобы реализовать красивый шаблон стратегии стратегии с помощью java enum:

public enum MyEnum {

    FIRST {
        @Override
        public String doIt() {
            return "1: " + someField; //error
        }
    },
    SECOND {
        @Override
        public String doIt() {
            return "2: " + someField; //error
        }
    };

    private String someField;

    public abstract String doIt();

} 

но при обращении к someField я получаю

Невозможно сделать статическую ссылку на нестатическое поле someField.

Что не так, и возможно ли это сделать лучше?

4b9b3361

Ответ 1

Специализированный enum - это не что иное, как подкласс с семантикой внутреннего класса. Если вы посмотрите на байтовый код после компиляции, вы заметите, что компилятор только вставляет метод accessor для чтения частного поля, но любое специализированное перечисление компилируется как собственный класс. Вы можете думать о своем enum как реализованном как:

public abstract class MyEnum {

  private static class First extends MyEnum {

    @Override
    public String doIt() {
        return "1: " + someField; //error
    }
  }

  private static class Second extends MyEnum {

    @Override
    public String doIt() {
        return "2: " + someField; //error
    }
  }

  public static final MyEnum FIRST = new First();
  public static final MyEnum SECOND = new Second();

  private String someField;

  public abstract String doIt();
} 

Как вы можете видеть, происходят одни и те же ошибки компилятора. Эффективно ваша проблема не связана с enum, а с их семантикой внутреннего класса.

Однако вы обнаружили пограничный случай, когда компилятор угадывал намерение вашего кода и пытался предупредить вас о том, что то, что вы намереваетесь, является незаконным. В общем поле someField видно любому специализированному enum. Однако есть два способа доступа к полю private из внутреннего класса, и только один из них является законным:

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

  • Для внутренних классов члены внешних классов доступны, даже если они private. Это достигается компилятором, вставляя методы доступа к внешним классам, которые раскрывают поля private с помощью методов доступа. Поле non static может быть доступно только в том случае, если внутренний класс не является static. Однако для enum s внутренние классы всегда static.

Более поздним условием является то, о чем компилятор жалуется:

Невозможно сделать статическую ссылку на нестатическое поле someField

Вы пытаетесь получить доступ к полю не static из внутреннего класса static. Это невозможно, даже если поле будет технически видимым из-за внутренней семантики класса. Вы можете дать указание компилятору явно получить доступ к значению, прочитав его из супер-класса, например:

public String doIt() {
  MyEnum thiz = this;
  return thiz.someField;
}

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

Ответ 2

Если вы сделаете someField protected вместо private или используете super.someField, вы сможете получить к нему доступ.

Ответ 3

someField является приватным, удаляет частный модификатор или переносит его в ваши абстрактные классы.

Ответ 4

Частные поля недоступны из подклассов, что именно вы делаете при реализации абстрактного метода MyEnum.doIt() для каждого экземпляра. Измените его на protected, и он будет работать.

Ответ 5

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

Ответ 6

По-видимому, проблема в том, что, когда вы говорите:

public enum MyEnum {
    ...
    public abstract String doIt();
} 

Он неявно нуждается в перечислении как abstract "class", так как вы должны предоставить ему реализацию. Поэтому, когда вы говорите

FIRST {
    @Override
    public String doIt() {
        return "1: " + this.someField; //error
    }
}

Он дает ошибку, потому что вы пытаетесь получить доступ к частному полю "базовый класс" MyEnum, а поскольку он является закрытым, он не отображается из неявно созданного анонимного подкласса. Таким образом, protected видно из подкласса, поэтому он исправляет проблему.

Есть несколько вопросов о переполнении стека, которые говорят об этой проблеме, такие как Синглотоны, перечисления и анонимные внутренние классы или Почему я могу анонимно подклассифицировать перечисление, но не последний класс?.

EDIT: По-видимому, не все в этом утверждении верно, потому что, пока this.someField не работает, поскольку поле не видно из подкласса, оно видимо доступно как super.someField. Это феномен, который я раньше не видел, и попытаюсь изучить его сейчас.

Ответ 7

Что вы можете сделать, это следующее:

public enum MyEnum {
    FIRST,SECOND;

    private String someField;

    public String doIt(){
        switch(this){
            case FIRST:  return "1: " + someField; break;
            case SECOND: return "2: " + someField; break;
        }
    }

}

Таким образом, вы по-прежнему наследуете Enum, и вы можете использовать MyEnum.values() и другие льготы, которые возникают при выводе Enum.

Ответ 8

Я бы не использовал шаблон стратегии, используя перечисление. Весь код заканчивается в том же uni (файл).

Идея состоит в том, чтобы отделить этот код. Используйте интерфейс как базовый класс, а затем реализуйте каждую Стратегию как отдельный подкласс. Приятный чистый.