Рубрики
Без рубрики

Синтаксические головоломки Java

Примерно 12 лет назад я начал вносить свой вклад в экосистему Eclipse с помощью различных функций. Один из т… С тегами java, программирование, водяное охлаждение, безопасность.

Примерно 12 лет назад я начал вносить свой вклад в экосистему Eclipse с помощью различных функций. Одним из самых интересных опытов на сегодняшний день была работа над инструментами разработчика и обработка крайних случаев, чтобы другим не приходилось бороться. Хотя я тем временем ушел с поста коммиттера Eclipse, в настоящее время я все еще продолжаю работать над инструментами повышения производительности в качестве участника Gradle Build Tool .

Работая над Eclipse, я с теплотой вспоминаю, как работал над различными частями Java Tooling (JDT), а также над рефакторингом и быстрыми исправлениями. И неудивительно, что при работе над фрагментами с интенсивным использованием языка возникли те же проблемы, что и с другими нетривиальными алгоритмами – переход от “это будет легко” к “почему я встаю в 3 часа ночи, читая спецификацию языка Java”.

Открытие отчета об ошибке с фрагментом для воспроизведения: 10 минут. Работа над исправлением: 2 часа. Получение пинга о том, сможете ли вы закончить этот патч 11 лет спустя: бесценно. Я действительно скучаю по работе над #eclipse ##jdt хотя ##jdt

Работа с инструментами, специфичными для конкретного языка, открывает вам все виды крайних случаев и деликатных деталей, которые может предложить язык. Некоторые из них хорошо известны и обычно кажутся “непрофессиональными” (hello goto ). Другие на самом деле вообще не известны. И при всем моем уважении, мне очень нравится открывать крайние случаи синтаксиса языка – много раз, чтобы сбить с толку моих коллег, которые думают, что знают синтаксис языка Java 😉 И учитывая, что я люблю хорошие головоломки (особенно головоломки Java), давайте попробуем головоломку, но с использованием только синтаксиса Java, без какого-либо поведения во время выполнения.

Использование Java для фишинга

Давайте начнем с широко известного факта об исходных файлах Java. Вам разрешено использовать Юникод в большинстве мест вашего кода. Хотя мы не можем использовать полный диапазон unicode в именах ваших классов (Я все еще хочу написать throw 🎂 () ), вы можете добавить достаточно Юникода, чтобы подшутить над своими коллегами.

В качестве закуски, в вашем следующем (удаленном) сеансе сопряжения, просто вставьте “Греческий вопросительный знак” ( U + 037E ) в код и наблюдайте, как ваш коллега пытается выяснить, что не так с этой простой точкой с запятой. Этот метод чаще всего используется фишинговыми электронными письмами, чтобы сделать URL похожим на реальный, но на самом деле указывает на совсем другой домен .

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

Угадайте, что печатает следующая программа?

public static void main(String[] args) {
    if (1 == 2) { // one down, one to go: \u000a\u007d\u007b
        System.out.println("1 is 2");
    }
}

Да, в контексте поста вы правильно догадались, что он печатает “1 равно 2”. Просто… КАК? Как можно обмануть Java, заставив ее думать 1, даже с помощью магии юникода? ВНУТРИ КОММЕНТАРИЯ. Есть какие-нибудь предположения? На самом деле это не меняет выражения лица. В процессе были повреждены следующие символы юникода:

  • \u000a – символ новой строки \n
  • \u007d – закрывающая фигурная скобка }
  • \u007b – открывающая фигурная скобка {

Итак, код, который мы на самом деле рассматриваем, таков:

public static void main(String[] args) {
   if (1 == 2) {
   }
   {
       System.out.println("1 is 2");
   }
}

Как ни странно, большинство программистов заподозрили бы что-то подозрительное в этом комментарии, когда увидели бы его. Но как насчет отступа в нем значит, это больше не отображается в вашем редакторе? 😉

Блоки блоков

Давайте перейдем к Спецификации языка Java и посмотрим, какие интересные фрагменты синтаксиса мы можем там найти.

Рассматривая имеющиеся у нас возможности для реализации методов, Java определяет тело метода как содержащее Блок элементы:

MethodBody:
  Block

Block:
  { [BlockStatements] }

BlockStatement:
  LocalVariableDeclarationStatement
  ClassDeclaration
  Statement

Присмотревшись повнимательнее к определению Block , мы узнаем, что они могут содержать операторы (пока все хорошо), но также… Объявление класса с. Теперь это становится интересным. Давайте посмотрим, насколько глубока кроличья нора.

public void howDeepCanWeGo() {
    class Foo {
        public void hello() {
            class Bar {
                public void helloFromBar() {
                    // You musn't be afraid to dream a little bigger, darling.
                } 
            }
            new Bar().helloFromBar();
        }
    }
    final Foo instance = new Foo();
    instance.hello();
}

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

Это и То

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

public class KeywordParameter {

    public static void main(String[] args) {
        KeywordParameter someObject = new KeywordParameter();
        someObject.callMe(3);
    }
    public void callMe(KeywordParameter this, int foo) {
        // ...
    }

}

Итак, мы создаем новый экземпляр Ключевого параметра и вызываем для него метод CallMe . Передача параметра int . Но подождите, у метода есть два параметра. И один из них даже назван в честь ключевого слова. Это даже не должно компилироваться, верно? Это действительно так. Просматривая объявления методов JLS 8.4, мы можем найти определение для объявлений методов.

MethodDeclarator:
  Identifier ( [ReceiverParameter ,] [FormalParameterList] ) [Dims]

Мы видим, что первый параметр является специальным необязательным параметром, не входящим в список формальных параметров. И на самом деле он определен так, чтобы всегда иметь имя “this:

ReceiverParameter:
  {Annotation} UnannType [Identifier .] this

Так называемый “параметр получателя” – это “необязательное синтаксическое устройство”, представляющее объект, для которого он вызывается (так что это действительно то же самое, что вы ожидаете от “this”). Его единственная цель – быть доступным в исходном коде для аннотирования в случае необходимости. Давайте предположим, что у нас есть аннотация @Immutable в нашем проекте, и по какой-то причине мы хотим убедиться, что ваша IDE (или другие анализаторы кода) понимают, что this в нашем текущем контексте представляет неизменяемую структуру данных. С помощью явного параметра receiver мы можем соответствующим образом аннотировать его:

    public void callMe(@Immutable KeywordParameter this, int foo) { ... }

@Везде

Говоря о аннотировании вещей в целях анализа кода. Чтобы приведенные выше фрагменты работали, аннотация должна быть целевой для ПАРАМЕТРА . Вы когда-нибудь смотрели, какие еще цели может иметь аннотация? Проходя через самые распространенные из них, нет никаких сюрпризов: ТИП , поле , СПОСОБ , ПАРАМЕТР , КОНСТРУКТОР , LOCAL_VARIABLE , АННОТАЦИЯ_ТИП , ПАКЕТ , ТИП_ПАРАМЕТРА , МОДУЛЬ (начиная с Java 9) и RECORD_COMPONENT (начиная с Java 14). Но есть один, который не так очевиден, куда его девать: ТИП_ИСПОЛЬЗОВАНИЯ . Судя по названию, его можно использовать везде, где используется тип. Давайте попробуем использовать его в некоторых местах:

@TypeAnnotationsEverywhere.Immutable // ok, easy, similar to TYPE 
public class TypeAnnotationsEverywhere {

  public void giveMeMoreTypes() throws @Immutable RuntimeException {  // errr what?
     Object foo = new @Immutable Object(); // WHAT??????
  }

  class Foo implements @Immutable Function { ... }

}

Действительно, использование TYPE_USE позволяет нам размещать аннотации в самых необычных местах. JLS 4.11 определяет все области, на которые распространяется “использование типа”.

О каком из этих синтаксисов вы знали? Получил их все? Код для поста также можно найти на GitHub . Тем временем я все еще работаю над своим музеем интересных примеров языковых конструкций, поэтому, пожалуйста, поделитесь всем, с чем вы столкнулись сами. Вы можете связаться со мной в Твиттере через @bmuskalla .

Оригинал: “https://dev.to/bmuskalla/java-syntax-puzzlers-55lp”