Тип java программы название которой звучит как фрукт
Перечисления появились в пятой версии Java и с тех пор крепко обосновались в наших приложениях. Работа с перечислениями почти не отличается от работы с любыми другими классами в Java. Но есть несколько особенностей, которые вызывают удивление. Каждый раз сталкиваясь с ними, хочется спросить: "Почему так?".
Давайте попробуем разобраться.
Порядок инициализации
В отличие от некоторых других языков программирования в Java перечисления являются полноценными классами. Конечно, есть некоторые особенности, например:
- нельзя наследоваться от классов (но можно реализовывать интерфейсы);
- нельзя объявлять класс финальным или абстрактным;
- нельзя создавать конструкторы с модификаторами public или protected ;
- множество других ограничений (полный список можно найти в документации).
Хорошо, смирились с запретами. Но можем ли мы ожидать, что остальные языковые конструкции работают так же, как в остальной Java? Например, порядок инициализации объектов.
Давайте проверим. Для этого напишем такое перечисление:
Остановитесь на секунду и попробуйте предположить что выведется в консоль при обращении к любому из значений перечисления.
В обычных классах при инициализации первого объекта кодовые блоки выполняются в следующем порядке:
Для перечисления же мы увидим в консоли следующее:
Как же так? Почему статический блок был вызван последним?
Для ответа на этот вопрос давайте прогоним скомпилированный класс через Java Class File Disassembler и вручную переведем дизассемблированный код в java код. Дизассемблинг выполняется командой:
Для самых любопытных привожу результат исполнения команды.
После ручного перевода в Java код получим следующее (не имеющий отношения к рассматриваемой теме код опущен):
Что же мы видим? Значения перечисления превратились в статические финальные поля. Выражения из кодового блока и конструктора переехали в конструктор. Выражения из статического блока остались в статическом блоке, но до их вызова добавился код создания экземпляров.
Получается, что при первом обращении к классу перечисления первым делом исполняется статический блок. Все как в обычных классах. Первое, что делается в статическом блоке — инициализируются финальные поля класса. Это влечет за собой вызов кода, объявленного в конструкторе, и в консоль дважды выводится
И только после этого исполняется код из статического блока оригинального класса.
Теперь мы знаем в какой класс преобразуется enum при компиляции, и порядок исполнения кодовых блоков становится понятен.
Для того чтобы не держать в памяти последовательность преобразований, приводящую к странному поведению, предлагаю запомнить следующее:
Значения, объявленные в перечислении — это статические финальные поля того же типа, что и класс. Инициализация этих полей происходит в статическом блоке до всех остальных статических выражений.
Отсутствующие методы
Все перечисления неявно унаследованы от абстрактного класса Enum . Если заглянуть в javadoc на этот класс, то можно увидеть следующие методы:
Чего-то не хватает.
Если попробовать в IDE написать любое перечисление, поставить точку и вызвать автодополнение, то он предложит еще два метода:
В исходниках класса Enum таких методов нет, но они как-то появляются в каждом перечислении.
Чтобы разобраться, обратимся к документации. Из нее мы узнаем, что два этих метода объявлены неявно. Почему неявно? Дело в том, что в отличие от других методов класса Enum эти методы не получается реализовать в абстрактном классе. Метод values() возвращает массив со всеми значениями перечисления, а класс Enum о них ничего не знает. Метод valueOf(String) возвращает конкретное значение перечисления по его названию. Можно было бы в нем вызвать метод valueOf(Class, String) :
Но ничего не выходит из-за того, что класс E невозможно извлечь в статическом контексте.
Почему же нельзя было объявить эти методы абстрактными в Enum , чтобы разработчики могли хотя бы ознакомиться с их контрактом в javadoc? Это невозможно из-за того, что методы не могут быть одновременно статическими и абстрактными. Компилятор не поймет. А методы valueOf(String) и values() по своей природе статические.
Теперь мы понимаем, что данные методы генерируются компилятором. Но какая же у них реализация? В JLS она не приведена, и в исходниках JDK ее тоже не найти.
Здесь нам поможет тот же трюк с дизассемблированием. В первой части статьи я сознательно не стал транслировать дизассемблированный код в Java-код полностью, чтобы не отвлекать внимание от инициализации. Если же пристальнее взглянуть на фрагмент под спойлером, то можно увидеть в дополнение к константам, описывающим значения перечисления, еще одну — VALUES . Она содержит в себе все значения перечисления в виде массива. Массив заполняется сразу после инициализации значений. Этот же массив возвращается при вызове метода values() :
Метод valueOf(String) реализуется с помощью вызова тезки:
Обобщая знания о неявных методах и порядке инициализации, давайте запишем как может быть представлено перечисление Pine из начала статьи в виде обычного класса:
Заключение
Странности в перечислениях вызваны архитектурными решениями и ограничениями, выбранными разработчиками Java. С помощью дизассемблирования нам удалось узнать, как перечисления инициализируются, и как в них реализованы неявные методы.
Надеюсь, что теперь, столкнувшись с необычным поведением перечислений, вы сможете мысленно преобразовать перечисление в обычный класс и разобраться.
Приватный конструктор. Если конструктор помечен модификатором private , объект класса нельзя создать с помощью этого конструктора. А поскольку в этом классе конструктор всего один, объект DayOfWeek нельзя создать вообще.
При этом в классе содержалось нужное количество public static объектов, которые были инициализированы нужным нам образом (названия дней правильные).
Это позволяло использовать объекты в других классах.
С выходом Java 1.5 в языке появилось готовое решение для таких ситуаций — перечисление Enum. Enum — тоже класс. Но он специально «заточен» на решение задач, похожих на нашу: создание некоторого ограниченного круга значений. Поскольку у создателей Java уже были готовые примеры (скажем, язык С, в котором Enum уже существовал), они смогли создать оптимальный вариант.
Что такое enum?
Итак, что же из себя представляет Enum в Java? Давай посмотрим на примере того же DayOfWeek : Выглядит уже намного проще :) Внутри нашего Enum находятся 7 констант со статическим доступом. Мы уже можем его использовать для реализации логики в программе. Например, напишем программу, которая будет определять, нужно ли школьнику сегодня идти на учебу. У нашего школьника будет свой режим дня, обозначенный классом ScholarSchedule : Переменная dayOfWeek в режиме дня определяет, какой сегодня день. А вот класс нашего школьника: В методе wakeUp() при помощи Enum определяем дальнейшие действия школьника. Мы даже не описывали подробно, что значит каждая переменная в DayOfWeek , да это и не нужно: механизм дней недели и так очевиден, и если мы будем его использовать в текущем виде, любому разработчику будет понятно, что происходит в твоем коде. Еще один пример удобства Enum : его константы можно использовать с оператором switch. Например, мы пишем программу для строгой диеты, в которой блюда расписаны по дням: Это одно из преимуществ Enum перед старым решением, которое применялось до Java 1.5: старое решение нельзя было использовать со switch .
Что еще нужно знать об Enum?
values() : возвращает массив из всех хранящихся в Enum значений:
ordinal() : возвращает порядковый номер константы. Отсчет начинается с нуля:
Привет! Сегодня мы поговорим об особом типе данных в Java — Enum (сокращенно от английского enumeration — «перечисление»). В чем же заключается их особенность? Давай представим, что нам нужно реализовать в программе месяцы. Казалось бы, в чем проблема? Надо просто определить, какие свойства есть у любого месяца. Пожалуй, нам нужны прежде всего название месяца и число дней в нем. Решение задачи выглядит довольно простым: Если такое появится в программе, будет непросто найти виновного! С одной стороны, программист, создавший объекты, мог бы и понять, что класс Month подразумевает «месяц в году» и не писать подобную дичь. С другой стороны, он всего лишь пользовался теми возможностями, которые ему предоставил проектировщик класса. Можно назначать любые имена и количество дней? Он и назначил. Что же в такой ситуации делать? До выхода версии языка Java 1.5 программистам приходилось, откровенно говоря, выкручиваться :) В те времена они создавали вот такие конструкции: Программисты, использующие класс, не могли просто создавать объекты Month . Они вынуждены были пользоваться теми финальными статическими объектами, которые предоставлял разработчик класса. Это выглядело примерно вот так: Однако разработчики Java обратили внимание на существующую проблему. Конечно, здорово, что программисты смогли придумать ее решение с помощью имеющихся в языке средств, но оно выглядит не таким уж простым! Необходимо было очевидное решение, доступное даже новичкам. Так в Java и появился Enum . По сути, Enum — это Java-класс, который предоставляет ограниченный набор значений-объектов. Вот как он выглядит: В определении мы указали что Enum — это Java-класс, но действительно ли это так? Да, и мы даже можем это проверить. Попробуй, например, унаследовать наш enum Month от какого-то другого класса: Почему так происходит? Когда мы пишем программе: компилятор преобразует эту команду в такой код: Как ты уже знаешь, множественное наследование в Java запрещено. Поэтому и наследоваться от AbstractMonth мы не смогли. Как эту новую конструкцию, Enum , можно использовать? И в чем ее отличие от старой конструкции со static final полями? Ну, например, старая конструкция не позволяла нам применять свой набор значений в switch -выражениях. Представь, что мы хотим создать программу, которая будет напоминать нам о том, какие праздники отмечаются в этом месяце: Здесь, как видишь, компилятор выбрасывает ошибку. Но после того, как в Java 1.5 появились enum , все стало гораздо проще: Вывод в консоль: Обрати внимание: доступ к объектам Enum остался статическим, как это было до Java 1.5. Нам не нужно создавать объект Month для доступа к месяцам. При работе с перечислениями очень важно не забывать, что Enum — это полноценный класс. Это значит, что при необходимости ты можешь определить в нем конструкторы и методы. К примеру, в предыдущем куске кода мы просто указали значения JANUARY, FEBRUARY, MARCH. Однако мы можем расширить наш enum Month вот таким образом: Вывод в консоль: Напоследок хочу порекомендовать тебе одну крайне полезную книгу по Java, а именно — “Effective Java” Джошуа Блоха. Автор — один из создателей Java, так что его советам по правильному и грамотному использованию средств языка точно можно доверять :) Применительно к нашей лекции, советую тебе обратить особое внимание на главу книги, посвященную enum . Продуктивного тебе чтения! :)
An enum type is a special data type that enables for a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it. Common examples include compass directions (values of NORTH, SOUTH, EAST, and WEST) and the days of the week.
Because they are constants, the names of an enum type's fields are in uppercase letters.
In the Java programming language, you define an enum type by using the enum keyword. For example, you would specify a days-of-the-week enum type as:
You should use enum types any time you need to represent a fixed set of constants. That includes natural enum types such as the planets in our solar system and data sets where you know all possible values at compile timefor example, the choices on a menu, command line flags, and so on.
Here is some code that shows you how to use the Day enum defined above:
Java programming language enum types are much more powerful than their counterparts in other languages. The enum declaration defines a class (called an enum type). The enum class body can include methods and other fields. The compiler automatically adds some special methods when it creates an enum. For example, they have a static values method that returns an array containing all of the values of the enum in the order they are declared. This method is commonly used in combination with the for-each construct to iterate over the values of an enum type. For example, this code from the Planet class example below iterates over all the planets in the solar system.
Note: All enums implicitly extend java.lang.Enum . Because a class can only extend one parent (see Declaring Classes), the Java language does not support multiple inheritance of state (see Multiple Inheritance of State, Implementation, and Type), and therefore an enum cannot extend anything else.
In the following example, Planet is an enum type that represents the planets in the solar system. They are defined with constant mass and radius properties.
Each enum constant is declared with values for the mass and radius parameters. These values are passed to the constructor when the constant is created. Java requires that the constants be defined first, prior to any fields or methods. Also, when there are fields and methods, the list of enum constants must end with a semicolon.
Note: The constructor for an enum type must be package-private or private access. It automatically creates the constants that are defined at the beginning of the enum body. You cannot invoke an enum constructor yourself.
In addition to its properties and constructor, Planet has methods that allow you to retrieve the surface gravity and weight of an object on each planet. Here is a sample program that takes your weight on earth (in any unit) and calculates and prints your weight on all of the planets (in the same unit):
If you run Planet.class from the command line with an argument of 175, you get this output:
Будущих студентов курса "Java QA Automation Engineer" приглашаем принять участие в открытом уроке на тему "HTTP. Postman, newman, fiddler (charles), curl, SOAP. SOAPUI".
А сейчас предлагаем ознакомиться с переводом полезного материала.
Для хранения тестовых данных обычно требуется такой тип данных, который:
допускает объявление нескольких свойств;
имеет минимальное поведение или вообще его не имеет;
позволяет легко создавать несколько одинаковых сущностей.
Объекты почти соответствуют этим требованиям. Но тогда для создания нескольких сущностей потребовалось бы создавать несколько объектов с небольшим количеством свойств и минимальным поведением (либо вообще без оного). Под минимальным поведением я имею в виду небольшое количество методов. По сути, для каждой нужной вам сущности пришлось бы создавать новый объект, а это бесполезная трата ресурсов. Вместо этого можно использовать Enum , объект особого типа.
С помощью Enum можно представлять такие понятия, как дни недели, месяцы года, браузеры или языки. В этой статье я хочу привести примеры использования объектов Enum с несколькими свойствами в рамках конструктора, обрабатывающего выбор страны. В конце статьи приведена ссылка на GitHub, где размещены все использованные в статье примеры кода. Информация об объектах Enum доступна в официальной документации.
Использование enum-перечислений Java в тестировании: пример со странами
Для этого примера давайте предположим, что тестирование подразумевает заполнение регистрационной формы для конкретной страны. В этой форме вам среди прочего нужно указать: страну, город этой страны, а также номер телефона из этой страны — все эти сведения относятся к клиенту, от имени которого заполняется регистрационная форма. Сайт с этой формой будет доступен во многих странах по всему миру.
Для выполнения тестирования давайте сузим список используемых стран до Австрии, Эстонии и Испании. У каждой из этих стран будет три свойства, которые необходимо вывести в регистрационную форму: название страны, список городов и код страны в номере телефона. Используя этот код, мы можем сформировать тестовый номер телефона. Все страны у нас будут перечислены в объекте Enum . Постоянные значения (константы), используемые для обозначения стран: AT, EE и ES.
Объявим объект Enum следующим образом:
Как видите, сначала мы указываем набор допустимых значений для констант стран, а также допустимые значения для всех их свойств. Мы определяем типы этих свойств, объявив label , cities и phoneNumberPrefix . Этими типами являются: String , List и int .
Конструктор используется внутри кода для формирования значений Enum . Итак, например, AT имеет свойства, порядок которых соответствует порядку параметров из конструктора: свойство label имеет значение «Австрия», список cities (городов), относящихся к этой стране: «Вена», «Зальцбург» и «Инсбрук», а свойство phoneNumberPrefix имеет значение «43».
Мы можем получить свойство, соответствующее константе Enum , следующим образом: Country.КОНСТАНТА.имяСвойства . Пример: Country.AT.label даст нам свойство «Австрия». Как видите, свойства Country являются статическими.
В регистрационной форме страна выбирается из раскрывающегося списка, город указывается в другом раскрывающемся списке, а номер телефона вводится в поле.
Класс Page содержит информацию по конкретной стране и включает следующие записи:
Метод countrySelect() возвращает функцию Select со ссылкой на раскрывающийся список стран. Аналогичным образом метод citySelect() возвращает функцию Select со ссылкой на раскрывающийся список городов. Для ввода номера телефона будет применяться элемент WebElement phoneNumberField .
В этой статье я пропущу те части кода, в которых открывается браузер, открывается веб-страница и закрывается браузер. Все эти части можно посмотреть в репозитории GitHub, ссылку на который я укажу в конце статьи.
Сценарий 1. Заполнение регистрационной формы от имени испанского клиента
Как только откроется браузер со страницей регистрации, наш тест должен будет заполнить поля данными для клиента из Испании. Мы выберем страну, затем город Валенсию, после чего введем произвольный номер телефона из 10 цифр. Часть регистрационной формы со сведениями о стране будет выглядеть вот так:
Итак, создадим новый тест для этого действия:
Сначала выберем страну. Для этого в раскрывающемся списке стран мы выберем значение, соответствующее свойству label записи ES в Enum . Делается это просто: Country.ES.label . Выбор страны выполняется следующим образом:
Далее, нам нужно выбрать город из соответствующего раскрывающегося списка. Доступ к списку городов производится так: Country.ES.cities . Доступ к элементу списка, соответствующему Валенсии (третьему элементу), производится так: Country.ES.cities.get(2) . Выберем это значение из списка городов:
И на последнем шаге нам нужно сформировать номер телефона, первые две цифры которого соответствуют коду страны. Этот код можно получить из объекта Enum следующим образом: Country.ES.phoneNumberPrefix . Формирование произвольного номера, состоящего из 10 символов: Country.ES.phoneNumberPrefix + randomNumeric(8) .
Чтобы использовать метод randomNumeric , сначала нужно импортировать соответствующий класс из библиотеки Apache Commons следующим образом:
Если у вас нет этой библиотеки в проекте, нужно ее импортировать. Для проекта Maven нужно просто добавить следующую запись в файл pom.xml (обязательно используйте последнюю версию):
Вот как выглядит полный тест:
Сценарий 2. Проверка имеющихся стран
Во втором и третьем сценариях нам нужно будет проверить, что раскрывающиеся списки стран и городов отображают только ожидаемые значения. Раскрывающиеся списки из этого примера работают следующим образом: когда они не открыты (пользователь не щелкнул их), в раскрывающемся списке стран не выбрано ничего, а раскрывающийся список городов полностью пуст. В этот момент раскрывающийся список городов отключен, и из него нельзя выбрать какой-либо вариант.
После нажатия раскрывающегося списка стран открывается список имеющихся вариантов. В этом списке должны отражаться значения, указанные в объекте Enum как свойства label . Стоит отметить, что для корректного отображения в раскрывающемся списке стран также имеется пункт без текста. Согласно изначальным требованиям, в раскрывающихся списках не должно быть никакого заранее выбранного значения.
После выбора страны активируется раскрывающийся список городов. В соответствии с выбранной страной в открытом раскрывающемся списке городов должны отображаться только города, соответствующие этой стране. Не забывайте, что для корректного отображения в этом раскрывающемся списке также есть один пункт без текста.
Составляемые нами тесты должны проверять наличие в соответствующих раскрывающихся списках всех нужных нам стран и городов, которые мы сохранили в объекте Enum . Опять же, не забывайте, что в обоих списках у нас есть пустые пункты.
Начнем со сценария номер 2, а именно с проверки наличия в раскрывающемся списке стран правильных значений. Сначала сформируем наш ожидаемый (expected) контент. Нам известно, что ожидаемые значения у нас хранятся в параметрах label в нашем объекте Enum , но нам также нужно обработать пункт без текста, который отображается в раскрывающемся списке. Учитывая, что мы будем считывать значения с веб-страницы с помощью Selenium, а также, что они возвращаются как строки (String), мы можем создать список ожидаемых строковых значений стран. Во-первых, я создам список и добавлю в него первый элемент, которым будет пустая строка. Этот элемент будет соответствовать пустой строке из раскрывающегося списка стран.
Теперь нужно добавить все значения свойств label , имеющиеся в нашем объекте Enum . Для этого нам нужно пройти по всем элементам Enum и добавить строковое значение каждого соответствующего свойства label в список ожидаемых значений. Чтобы прочесать каждую запись объекта Enum , воспользуемся методом Country.values() .
Теперь в нашем списке ожидаемых стран содержатся четыре пункта: «», «Австрия», «Эстония», «Испания».
Далее можно переходить к считыванию значений стран с веб-страницы и сохранению этих значений в список фактических (actual) значений. Поскольку мы имеем дело с Select , нам нужно будет пройти по всем вариантам WebElements , относящимся к Select . Нам нужно применить getText() к каждому варианту (option) и добавить итоговые строки в список фактических значений.
Перед сравнением ожидаемого и фактического списков нам нужно учесть, что, возможно, наши параметры в объекте Enum и значения вариантов в раскрывающемся списке идут не в одном порядке. Поэтому их нужно отсортировать, а затем произвести сравнение.
Весь тест выглядит следующим образом:
Сценарий 3. Проверка имеющихся городов
В нашем предыдущем сценарии нам не нужно было взаимодействовать с самими раскрывающимися списками. Однако в этом сценарии нам необходимо проверить, что для каждой выбранной страны в раскрывающемся списке городов отображаются только правильные города. Поскольку информацию в список городов после выбора страны в соответствующем раскрывающемся списке загружает JavaScript, тест должен выбрать поочередно каждую страну и проверить для каждой из них содержимое раскрывающегося списка городов.
Начнем писать тест с прохода по имеющимся записям объекта Enum :
Затем, давайте внутри петли for в раскрывающимся списке стран выберем ту страну, которая соответствует свойству label текущего пункта Enum :
В этот момент раскрывающийся список городов, как мы надеемся, будет заполнен значениями, соответствующими выбранной стране. Чтобы удостовериться, что пункты раскрывающегося списка правильные, мы сначала создаем список фактических (actual) названий городов. Мы считаем эти значения с веб-страницы:
Мы также должны создать список ожидаемых названий городов. В объекте Enum они хранятся в свойствах List cities . Мы создадим список ожидаемых значений, первым делом добавив в него пустую строку. Затем мы воспользуемся методом addAll() и сразу добавим все пункты из списка cities .
Теперь остается только сравнить списки фактических и ожидаемых значений. Разумеется, предварительно отсортировав эти списки.
Итак, в этом сценарии, выполняя проход по каждой стране, мы проверяем соответствующий ей список городов. Для каждой страны с нуля создаются списки ожидаемых и фактических городов. Они содержат только информацию, соответствующую этой стране. Теперь весь тест выглядит следующим образом:
Заключение
Надеюсь, эти примеры будут вам полезны и вдохновят вас использовать объекты Enum для тестирования подходящих конструкций. Весь код можно посмотреть в моем репозитории проекта на GitHub:
Читайте также: