Новые ключевые слова в Java +8


В ближайшем будущем в языке Java появятся новые фичи, над которыми сейчас идет работа в рамках проектов Valhalla, Panama и Loom. Расширение языка — дело непростое, тем более — языка, в котором акцент делается на обратную совместимость; поэтому для того, чтобы их интеграция в Java прошла органично, архитекторам языка приходится решать накопившиеся фундаментальные вопросы.

Вчера (8 января) Брайан Гетц, работающий в Oracle на должности Java Language Architect, опубликовал в рассылке Project Amber письмо «Нам нужно больше ключевых слов, капитан!», в котором предложил способ решения проблемы добавления в язык новых ключевых слов. Вследствие чего в языке могут появиться такие ключевые слова, как non-null, non-final, eventually-final и this-return (полный список ждет вас под катом в конце поста).

Поскольку в прошлом эта проблема возникала в языке нечасто, обычно над ней особо не задумывались и «старались как можно быстрее свалить с шеи»; из-за недостатков уже существующих подходов в будущем их применение будет проблематичным, и в связи с этим было решено сработать на упреждение. Предлагаемое решение: попытаться расширить набор лексических форм, которые можно использовать в качестве ключевых слов: разрешить ключевые слова, разделенные дефисом, в составе которых будет использоваться одно (или более) уже существующее ключевое слово или зарезервированный идентификатор.

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

«Старые» методы


Как известно, на сегодняшний день в языке Java существует 50 ключевых слов (keywords), которые запрещено использовать в качестве идентификаторов переменных. Их полный список приведен в спецификации языка JLS в пункте 3.9. Список этот не сильно изменился с первой версии языка — добавились разве что assert в 4 версии, enum в 5 и _ в 9. Помимо них, есть еще «зарезервированные идентификаторы» — это true, false и null — которые ведут себя схожим с ключевыми словами образом.

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

  • Принудительное отчуждение собственности: берем слова, которые ранее были идентификаторами, и превращаем их в ключевые слова (пример — assert).
  • Утилизация: существующее ключевое слова начинает использоваться таким образом, которым его никогда не планировали использовать (пример — использование default для значений аннотации или методов по умолчанию).
  • Обойтись без него: найти способ использовать синтаксис, который не требует нового ключевого слова — например, использовать interface для аннотаций вместо annotation — или вовсе отказаться от фичи.
  • Создание видимости: создать иллюзию ключевых слов, зависящих от контекста, с помощью героических лингвистических достижений (restricted keywords, reserved type names).

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

Добавление новых ключевых слов


Против того, чтобы «просто» взять и добавить новые слова, есть следующие аргументы.

  • Чем удобнее и популярнее будет выбранное ключевое слово, тем чаще оно будет попадаться в исходном коде программ, что добавит в язык несовместимость (например, когда в Java SE 1.4 появилось слово assert, перестали работать все тестовые фреймворки).
  • Стоимость устранения подобной несовместимости кода разработчиком будет сильно варьироваться от небольшой (переименование локальной переменной) до фатальной (когда произойдет инвалидация метода интерфейса или публичного типа).
  • Слова, которые скорее всего захочется использовать разработчикам языка, являются популярными идентификаторами (например, value, var, или method);
  • Если выбирать слова, которые редко используются в исходном коде и с которыми будет меньше коллизий, то придется использовать конструкции вроде usually_but_not_always_final, которых в языке естественно хотелось бы избежать.
  • Если все-таки прибегнуть к выбору редко используемых слов, то пользоваться этим методом слишком часто не выйдет — ломать совместимость нехорошо, а более-менее удачных сочетаний не так уж и много.

Повторное использование «старых» ключевых слов


Насчет того, чтобы «просто» продолжать жить с теми словами, есть свои соображения.

  • Прецеденты повторного использования ключевых слов в разных контекстах встречаются во многих языках программирования (пример из Java — это использование ((ab)use) final для обозначений «не изменяемый», «не переопределяемый» и «не расширяемый»).
  • Иногда такой подход имеет смысл и приходит сам по себе, но обычно он не в приоритете.
  • Со временем набор требований к набору ключевых слов расширяется, и дело может дойти до смешного — никто не захочет использовать в своем коде null final.
  • Если последнее показалось вам преувеличением — то учтите, что во время работы над JEP 325 на полном серьезе предлагали использовать конструкцию new switch для того, чтобы описать switch с отличной от принятой семантикой — если продолжать в таком же духе, лет через десять можем дойти и до new new switch.

Как прожить без новых ключевых слов? Можно разве что полностью перестать заниматься эволюцией языка, как и предлагают некоторые. Но это несерьезно и не вяжется с мнением остальных, поскольку к новым возможностям языка имеется здоровый интерес со стороны разработчиков.

Контекстные ключевые слова


Контекстные ключевые слова, которые используются для предоставления конкретного значения в коде, но при этом не являются зарезервированными словами (используются в C#) на первый взгляд кажутся той самой «волшебной палочкой», но здесь Брайан излагает собственный взгляд на их использование, основанный на практике (например, реализации var в Java 10, которое является не ключевым словом, а reserved type name). В обмен на иллюзию добавления новых ключевых слов без необходимости «ломать» существующие программы, мы получаем возросшую сложность и искажения в языке.

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

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

Использовать это средство можно, но делать это следует с осмотрительностью.

Искажение языка


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

Для разработчиков на Java написание interface вместо annotation сегодня является привычным делом, но все согласятся, что использование понятного термина annotation вместо комбинации @ и «старого» ключевого слова было бы куда логичнее.

Другой пример: набор доступных модификаторов (public, private, static, final, и т.д.) нельзя назвать полным — мы никак не можем сказать not final или not static. В свою очередь, это означает, что нельзя создать фичи, в которых переменные или классы являются по умолчанию final, или члены являются по умолчанию static, поскольку не существует способа указать на то, что мы хотели бы отказаться от этого модификатора.

Данная проблема не так очевидна для тех, кто пользуется языком — но авторы самого языка из-за своего стремления развивать его дальше постоянно с ней сталкиваются, и расплачиваться за подобные решения (когда явно, когда неявно) приходится в итоге уже нам всем.

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

Предложенное решение


В экспериментальных возможностях языка для предварительного обозначения новых ключевых слов используется синтаксис, при котором сами новые ключевые слова предваряются двумя символами подчеркивания (например, в прототипе Project Valhalla это __ByValue). Причина такого решения понятна — нужно указать на то, что это временная замена, для которой в дальнейшем нужно будет принять решение насчет финального синтаксиса, и при этом можно с легкостью избежать коллизий с существующим кодом. Можно было бы предложить использовать для новых ключевых слов подобный формат — начинать их с одного или двух подчеркиваний — но это решение нельзя назвать красивым, ведь в таком случае на выходе мы получим путаницу из обычных и новых ключевых слов.

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

В отличие от restricted keywords, такой подход создаст гораздо меньше проблем для парсинга, поскольку (далее — пример) not-null нельзя спутать с выражением вычитания, а лексер всегда сможет определить, представляет ли собой a-b три токена или один. Благодаря этому нам открываются новые широкие возможности для создания ключевых слов, которые с гораздо меньшей вероятностью будут конфликтовать с уже существующим исходным кодом или между собой. Вдобавок ко всему, у них с куда большей вероятностью будут осмысленные имена, поскольку многое из того, что создателям языка хочется добавить в Java, базируется на уже существующих в ней языковых конструкциях — например, non-null.

В качестве примеров новых ключевых слов приводятся вероятные кандидаты на место новых ключевых слов (напоминаю, что по утверждению автора, на данный момент этот список носит чисто иллюстративный характер):

non-null;
non-final;
package-private (модификатор уровня доступа к членам класса по умолчанию, который в данный момент никак не обозначается);
public-read (публично читаемый, приватно записываемый);
null-checked;
type-static (концепт, необходимый для Valhalla; обозначает статичность по отношению к конкретной специализации класса, а не самого класса);
default-value;
eventually-final (то, что сейчас предполагается делать при помощи аннотации Stable),
semi-final (в качестве альтернативы sealed);
exhaustive-switch;
enum-class, annotation-class, record-class (авторы языка могли бы использовать данные ключевые слова как альтернативу enum и interface, если бы у них была такая возможность);
this-class (для описания литерала класса для текущего класса);
this-return (часто просят добавить способ пометить сеттер/метод-билдер в качестве возвращающего свой получатель).

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

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




К сожалению, не доступен сервер mySQL