Перечисления в Java и подозрительный метод values()

Перечисление (Enumeration) в Java представляют собой класс, состоящий из констант. Появились в языке для удобной реализации перечисляемых типов данных. Например, светофор имеет три цвета: красный, желтый и зеленый. Вместо того, чтобы создавать отдельный статический класс с константами типа Color, у которых есть поле name в которое уже записано имя цвета.

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

Вся логика первого примера уместилась в 5 строчек второго. Что происходит в этом примере? Мы создаем перечисление с помощью ключевого слова  enum. По сути это обычный класс, который наследуется от java.lang.Enum, однако, для перечислений существует ряд ограничений. Во-первых, вы не можете создать экземпляр с перечисления с помощью конструктора. При компиляции будет создан приватный конструктор по умолчанию. Во-вторых, нельзя создать потомков перечисления. В остальном их можно использовать как обычный класс. Справедливости ради стоит заметить, что обычно структура перечислений остается простой.

Рассмотрим некоторые возможности перечислений.

  1. Для каждого перечисления можно добавить параметр, инициализировав его в конструкторе.  Как видно из примера IGOR(“Denis”) имеет параметр desc=”Denis”, который задается в конструкторе. Конструктор не может быть public, потому что нельзя создать новый экземпляр перечисления, помимо объявленных. Если посмотреть на байткод скомпилированного класса, то мы увидим, что коснструктор принудительно будет объявлен как private, даже если мы оставили его с default.

    В примере мы передали параметр String, но на деле можно передать любой объект и даже не один.
  2. Есть возможность переопределить метод базового класса toString(). Какого базового класса? – спросите вы. java.lang.enum – отвечу я. Но ведь мы явно не указывали родителя? Это так. За нас это сделал компилятор.

    Мы видим ключевое слово extends и класс java.lang.enum. Любое созданое перечисление будет всегда наследоваться от него. Переопределить можно только метод toString(), потому что остальные методы базового класса являются final.
  3. Можно создавать свои методы. Эта возможность открывает перед перечислениями целый спектр прикладных задач, в которых их можно использовать. Например, конечные автоматы – граф состояний, причем число состояний конечно. Яркий пример такого автомата – вендинговый автомат с ништяками. Его состояния: принять 100р, принять 50р, принять 10р, выдать товар, выдать сдачу, недостаточно средств, неверный код товара. Эти состояния можно заполнить в перечислении, добавив метод, который будет изменять состояния в зависимости от входных данных. Это лишь один из вариантов возможного использования перечислений.
  4. Метод ordinal() возвращает порядковый номер экземпляра перечисления (начиная с 0) в котором он был записан. Для IGOR – 0, для RENAT – 1, для AVTO – 2.

Подозрительный метод values()

В нашем примере есть вызов

Понятно, что метод values() возвращает массив из всех экземпляров перечисления. В нашем примере этот метод мы явно нигде не указали. Но для нас это не проблема, ведь есть базовый класс, из которого мы вызываем, например, метод ordinal(), значит в нем и описан метод values(). Но ЭТОГО МЕТОДА В КЛАССЕ JAVA.LANG.ENUM  НЕТ! Как такое может быть? Метод нигде явно не описан, но он есть. В очередной раз обратившись к байткоду мы увидим, что метод values() появится в нашем классе после компиляции.

Так же видно, что помимо наших экземпляров создалась ссылка $VALUES как поле класса. Ниже мы видим скомпилированный метод values(). Согласитесь, это странно. Метод нигде не описан, но при компиляции он появляется! Не верите? Проверьте сами.

Оставить комментарий:

Ваш email не будет опубликован.