10.01.19. В ближайшем будущем в языке Java появятся новые фичи, над которыми сейчас идет работа в рамках проектов Valhalla, Panama и Loom. Расширение языка — дело непростое, тем более — языка, в котором акцент делается на обратную совместимость; поэтому для того, чтобы их интеграция в Java прошла органично, архитекторам языка приходится решать накопившиеся фундаментальные вопросы.
Поскольку в прошлом эта проблема возникала в языке нечасто, обычно над ней особо не задумывались и «старались как можно быстрее свалить с шеи»; из-за недостатков уже существующих подходов в будущем их применение будет проблематичным, и в связи с этим было решено сработать на упреждение. Предлагаемое решение: попытаться расширить набор лексических форм, которые можно использовать в качестве ключевых слов: разрешить ключевые слова, разделенные дефисом, в составе которых будет использоваться одно (или более) уже существующее ключевое слово или зарезервированный идентификатор.
Важное замечание: Брайн отмечает, что новые ключевые слова приведены исключительно в качестве иллюстративного примера, и что заострять на них свое внимание не стоит. Однако, очевидно, что перед нами продуманная демонстрация того, как синтаксис языка может измениться в будущем — в письме автор упоминает, что эта идея поможет добавить в 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 (часто просят добавить способ пометить сеттер/метод-билдер в качестве возвращающего свой получатель).
Наверняка существуют и другие варианты лексических схем, по которым можно было бы составить ключевые слова так, чтобы они минимально пересекались с уже написанных исходным кодом. Предложенный вариант с дефисом является достаточно читаемым как для машин, так и для человека.
Подразумевается, что данный подход ни в коем случае не исключает возможности использования совместно с теми, что применялись ранее, а будет использоваться наряду с ними.
Источник: https://habr.com/post/435540/?utm_source=habrahabr&utm_medium=rss&utm_campaign=435540
Смотри также популярное:
Зачем нужна Java. http://fetisovvs.blogspot.com/2014/07/java.html
Когда Java наконец помрёт, что с этим делать и что будет с JPoint. https://fetisovvs.blogspot.com/2018/11/java-jpoint-java.html
Разбор основных концепций параллелизма. http://fetisovvs.blogspot.com/2018/04/java.html
Первый контакт с «var» в Java 10. http://fetisovvs.blogspot.com/2018/01/var-java-10-java.html
JAVA 9. Что нового? http://fetisovvs.blogspot.com/2017/10/java-9-java.html
Руководство по Java 9 для тех, кому приходится работать с legacy-кодом. http://fetisovvs.blogspot.com/2018/08/java-9-legacy-java.html
Концепции объектно-ориентированного программирования — ООП в Java. http://fetisovvs.blogspot.com/2017/01/java-java.html
Анимации в Android по полочкам (Часть 1. Базовые анимации). http://fetisovvs.blogspot.com/2018/02/android-1-java.html
Двести пятьдесят русскоязычных обучающих видео докладов и лекций о Java. http://fetisovvs.blogspot.com/2015/12/java-5-java-java.html
Абстрактные классы и методы. http://fetisovvs.blogspot.com/2017/02/java.html
Полное руководство по Java Reflection API. Рефлексия на примерах. http://fetisovvs.blogspot.com/2017/02/java-reflection-api-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. http://fetisovvs.blogspot.com/2017/10/java-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. (Часть 3). http://fetisovvs.blogspot.com/2017/10/java-3-java.html
ТОП-3 способа конвертировать массив в ArrayList. Пример на Java. http://fetisovvs.blogspot.com/2016/09/3-arraylist-java-java.html
Ввод–вывод в Java. http://fetisovvs.blogspot.com/2016/05/java-java_28.html
Java Challengers #2: Сравнение строк. https://fetisovvs.blogspot.com/2018/11/java-challengers-2-java.html
Enum-Всемогущий. http://fetisovvs.blogspot.com/2017/02/enum-java.html
Массивы в Java. Создание и обработка. http://fetisovvs.blogspot.com/2017/10/java-java_18.html
Arrays, Collections: Алгоритмический минимум. http://fetisovvs.blogspot.com/2017/12/arrays-collections.html
Популярные методы для работы с Java массивами. http://fetisovvs.blogspot.com/2016/09/java-java_29.html
Пример использования метода replace в Java. Как заменить символ в строке? http://fetisovvs.blogspot.com/2017/01/replace-java-java.html
Класс Scanner в Java — описание и пример использования. http://fetisovvs.blogspot.com/2017/01/scanner-java-java.html
Пример использования метода trim в Java: как удалить пробелы в начале и конце строки? http://fetisovvs.blogspot.com/2017/01/trim-java-java.html
Spark — Потрясающий веб-микрофреймворк для Java. http://fetisovvs.blogspot.com/2017/10/spark-java-java.html
Чтение и запись CSV файла с помощью SuperCSV. http://fetisovvs.blogspot.com/2017/01/csv-supercsv-java-java.html
Конструкция try/catch/finally (исключения). http://fetisovvs.blogspot.com/2017/01/trycatchfinally-java.html
1000+ часов видео по Java на русском. http://fetisovvs.blogspot.nl/2017/06/1000-java-java.html
Раздача халявы: нетормозящие треды в Java. Project Loom. http://fetisovvs.blogspot.com/2018/09/java-project-loom-java.html
Шпаргалка Java программиста 7.1 Типовые задачи: Оптимальный путь преобразования InputStream в строку. http://fetisovvs.blogspot.com/2016/04/java-71-inputstream-java.html
Шпаргалки Java программиста 10: Lombok. http://fetisovvs.blogspot.nl/2017/12/java-10-lombok-java.html
Шпаргалки Java программиста 9: Java SE — Шпаргалка для собеседований и повторений. http://fetisovvs.blogspot.com/2017/12/java-9-java-se-java.html
Шпаргалка Java программиста 8. Библиотеки для работы с Json (Gson, Fastjson,
LoganSquare, Jackson, JsonPath и другие). http://fetisovvs.blogspot.com/2016/04/java-8-json-gson-fastjson-logansquare.html
Java 8 и паттерн Стратегия. http://fetisovvs.blogspot.com/2018/03/java-8-java.html
Java EE Concurency API. http://fetisovvs.blogspot.com/2018/08/java-ee-concurency-api-java.html
Реализация ООП-наследования в классах, работающих с SQL и MS Entity Framework. http://fetisovvs.blogspot.com/2017/02/sql-ms-entity-framework.html
Как установить соединение с СУБД MySQL в IntelliJ IDEA в редакции Community. http://fetisovvs.blogspot.com/2016/04/mysql-intellij-idea-community-java.html
TDD приложений на Spring Boot: работа с базой данных. https://fetisovvs.blogspot.com/2018/12/tdd-spring-boot-java.html
Руководство: Thymeleaf + Spring. Часть 1. https://fetisovvs.blogspot.com/2019/01/thymeleaf-spring-1-java.html
Введение в Spring Boot: создание простого REST API на Java. https://fetisovvs.blogspot.com/2019/01/spring-boot-rest-api-java-java.html
Максимально простой в поддержке способ интеграции java-клиента с java-сервером. http://fetisovvs.blogspot.com/2018/09/java-java-java.html
AOP vs Функции. https://fetisovvs.blogspot.com/2019/01/aop-vs-java.html
Когда Java наконец помрёт, что с этим делать и что будет с JPoint. https://fetisovvs.blogspot.com/2018/11/java-jpoint-java.html
Разбор основных концепций параллелизма. http://fetisovvs.blogspot.com/2018/04/java.html
Первый контакт с «var» в Java 10. http://fetisovvs.blogspot.com/2018/01/var-java-10-java.html
JAVA 9. Что нового? http://fetisovvs.blogspot.com/2017/10/java-9-java.html
Руководство по Java 9 для тех, кому приходится работать с legacy-кодом. http://fetisovvs.blogspot.com/2018/08/java-9-legacy-java.html
Концепции объектно-ориентированного программирования — ООП в Java. http://fetisovvs.blogspot.com/2017/01/java-java.html
Анимации в Android по полочкам (Часть 1. Базовые анимации). http://fetisovvs.blogspot.com/2018/02/android-1-java.html
Двести пятьдесят русскоязычных обучающих видео докладов и лекций о Java. http://fetisovvs.blogspot.com/2015/12/java-5-java-java.html
Абстрактные классы и методы. http://fetisovvs.blogspot.com/2017/02/java.html
Полное руководство по Java Reflection API. Рефлексия на примерах. http://fetisovvs.blogspot.com/2017/02/java-reflection-api-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. http://fetisovvs.blogspot.com/2017/10/java-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. (Часть 3). http://fetisovvs.blogspot.com/2017/10/java-3-java.html
ТОП-3 способа конвертировать массив в ArrayList. Пример на Java. http://fetisovvs.blogspot.com/2016/09/3-arraylist-java-java.html
Ввод–вывод в Java. http://fetisovvs.blogspot.com/2016/05/java-java_28.html
Java Challengers #2: Сравнение строк. https://fetisovvs.blogspot.com/2018/11/java-challengers-2-java.html
Enum-Всемогущий. http://fetisovvs.blogspot.com/2017/02/enum-java.html
Массивы в Java. Создание и обработка. http://fetisovvs.blogspot.com/2017/10/java-java_18.html
Arrays, Collections: Алгоритмический минимум. http://fetisovvs.blogspot.com/2017/12/arrays-collections.html
Популярные методы для работы с Java массивами. http://fetisovvs.blogspot.com/2016/09/java-java_29.html
Пример использования метода replace в Java. Как заменить символ в строке? http://fetisovvs.blogspot.com/2017/01/replace-java-java.html
Класс Scanner в Java — описание и пример использования. http://fetisovvs.blogspot.com/2017/01/scanner-java-java.html
Пример использования метода trim в Java: как удалить пробелы в начале и конце строки? http://fetisovvs.blogspot.com/2017/01/trim-java-java.html
Spark — Потрясающий веб-микрофреймворк для Java. http://fetisovvs.blogspot.com/2017/10/spark-java-java.html
Чтение и запись CSV файла с помощью SuperCSV. http://fetisovvs.blogspot.com/2017/01/csv-supercsv-java-java.html
Конструкция try/catch/finally (исключения). http://fetisovvs.blogspot.com/2017/01/trycatchfinally-java.html
1000+ часов видео по Java на русском. http://fetisovvs.blogspot.nl/2017/06/1000-java-java.html
Раздача халявы: нетормозящие треды в Java. Project Loom. http://fetisovvs.blogspot.com/2018/09/java-project-loom-java.html
Шпаргалка Java программиста 7.1 Типовые задачи: Оптимальный путь преобразования InputStream в строку. http://fetisovvs.blogspot.com/2016/04/java-71-inputstream-java.html
Шпаргалки Java программиста 10: Lombok. http://fetisovvs.blogspot.nl/2017/12/java-10-lombok-java.html
Шпаргалки Java программиста 9: Java SE — Шпаргалка для собеседований и повторений. http://fetisovvs.blogspot.com/2017/12/java-9-java-se-java.html
Шпаргалка Java программиста 8. Библиотеки для работы с Json (Gson, Fastjson,
LoganSquare, Jackson, JsonPath и другие). http://fetisovvs.blogspot.com/2016/04/java-8-json-gson-fastjson-logansquare.html
Java 8 и паттерн Стратегия. http://fetisovvs.blogspot.com/2018/03/java-8-java.html
Java EE Concurency API. http://fetisovvs.blogspot.com/2018/08/java-ee-concurency-api-java.html
Реализация ООП-наследования в классах, работающих с SQL и MS Entity Framework. http://fetisovvs.blogspot.com/2017/02/sql-ms-entity-framework.html
Как установить соединение с СУБД MySQL в IntelliJ IDEA в редакции Community. http://fetisovvs.blogspot.com/2016/04/mysql-intellij-idea-community-java.html
TDD приложений на Spring Boot: работа с базой данных. https://fetisovvs.blogspot.com/2018/12/tdd-spring-boot-java.html
Руководство: Thymeleaf + Spring. Часть 1. https://fetisovvs.blogspot.com/2019/01/thymeleaf-spring-1-java.html
Введение в Spring Boot: создание простого REST API на Java. https://fetisovvs.blogspot.com/2019/01/spring-boot-rest-api-java-java.html
Максимально простой в поддержке способ интеграции java-клиента с java-сервером. http://fetisovvs.blogspot.com/2018/09/java-java-java.html
AOP vs Функции. https://fetisovvs.blogspot.com/2019/01/aop-vs-java.html
Как с помощью maven работать с библиотеками, которых в maven нет. http://fetisovvs.blogspot.com/2017/03/maven-maven-java.html
Проекты по созданию компиляторов из Java в JavaScript и исполняемые файлы. http://fetisovvs.blogspot.com/2018/01/java-javascript-java.html
Реактивное программирование с JAX-RS. http://fetisovvs.blogspot.com/2018/09/jax-rs-java.html
Компактные строки в Java 9. https://fetisovvs.blogspot.com/2018/10/java-9-java.html
Динамический прокси Java: что это и как им пользоваться? https://fetisovvs.blogspot.com/2018/12/java-java.html
Абстрактный CRUD от репозитория до контроллера: что ещё можно сделать при помощи Spring + Generics. http://fetisovvs.blogspot.com/2018/09/crud-spring-generics-java.html
Диагностика утечек памяти в Java. http://fetisovvs.blogspot.com/2017/03/java-java_18.html
Spring AOP и JavaConfig в плагинах для Atlassian Jira. http://fetisovvs.blogspot.com/2018/04/spring-aop-javaconfig-atlassian-jira.html
Блеск и нищета Java для настольных систем. http://fetisovvs.blogspot.com/2018/04/java-haulmont-java.html
Разбор задачек от Одноклассников на JPoint 2018. http://fetisovvs.blogspot.com/2018/04/jpoint-2018-java.html
Программируем… выход из лабиринта. http://fetisovvs.blogspot.com/2015/10/java.html
Основы работы с IntelliJ IDEA. Интерфейс программы. http://fetisovvs.blogspot.com/2016/09/intellij-idea-java.html
Ускоряем время сборки и доставки java web приложения. http://fetisovvs.blogspot.com/2018/03/java-web-java.html
Открытый урок Java Enterprise «CDI in action». http://fetisovvs.blogspot.com/2018/09/java-enterprise-cdi-in-action-java.html
«Мы все стремимся к сложности, а потом с ней боремся»: интервью с Венкатом Субраманиамом. http://fetisovvs.blogspot.com/2018/09/java_16.html
Проекты по созданию компиляторов из Java в JavaScript и исполняемые файлы. http://fetisovvs.blogspot.com/2018/01/java-javascript-java.html
Реактивное программирование с JAX-RS. http://fetisovvs.blogspot.com/2018/09/jax-rs-java.html
Компактные строки в Java 9. https://fetisovvs.blogspot.com/2018/10/java-9-java.html
Динамический прокси Java: что это и как им пользоваться? https://fetisovvs.blogspot.com/2018/12/java-java.html
Абстрактный CRUD от репозитория до контроллера: что ещё можно сделать при помощи Spring + Generics. http://fetisovvs.blogspot.com/2018/09/crud-spring-generics-java.html
Диагностика утечек памяти в Java. http://fetisovvs.blogspot.com/2017/03/java-java_18.html
Spring AOP и JavaConfig в плагинах для Atlassian Jira. http://fetisovvs.blogspot.com/2018/04/spring-aop-javaconfig-atlassian-jira.html
Блеск и нищета Java для настольных систем. http://fetisovvs.blogspot.com/2018/04/java-haulmont-java.html
Разбор задачек от Одноклассников на JPoint 2018. http://fetisovvs.blogspot.com/2018/04/jpoint-2018-java.html
Программируем… выход из лабиринта. http://fetisovvs.blogspot.com/2015/10/java.html
Основы работы с IntelliJ IDEA. Интерфейс программы. http://fetisovvs.blogspot.com/2016/09/intellij-idea-java.html
Ускоряем время сборки и доставки java web приложения. http://fetisovvs.blogspot.com/2018/03/java-web-java.html
Открытый урок Java Enterprise «CDI in action». http://fetisovvs.blogspot.com/2018/09/java-enterprise-cdi-in-action-java.html
«Мы все стремимся к сложности, а потом с ней боремся»: интервью с Венкатом Субраманиамом. http://fetisovvs.blogspot.com/2018/09/java_16.html
Комментариев нет:
Отправить комментарий