Статьи

20 лет Явы

Двадцать лет назад в квартире в Цюрихе произошли две важные вещи.

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

То, что сначала привлекло меня к языку, было фактически апплетами; идея создания 3D слюноотделения молекулярных структур в реальном времени, встроенного в одну из этих «новых и ярых» веб-страниц, казалась довольно опьяняющей. Хотя одновременно программистам на Фортране и Си, Java казался невообразимо неуклюжим и нелегким языком.

В течение следующих 20 лет я никогда не проводил больше чем несколько месяцев вдали от Java. Он изменил мир вычислительной техники и отчасти ответственен за разрушение монополистического влияния на ИТ, которое Microsoft так остро переживала в период своего расцвета. Java стала намного более мощной, невероятно быстрой, бесконечно более масштабируемой и значительно более неуклюжей, одновременно ужасающе менее и значительно более элегантной (varhandles, autoboxing — Инь и Ян).

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

Насколько важна Java?

Давайте не будем брезгливы по этому поводу; Java — один из 4 действительно меняющихся парадигм коммерчески важных языков программирования общего назначения — когда-либо. Фортран, Кобол, Си и Ява. У всех нас могут быть наши любимые языки, и мы можем сказать, что Python важнее, чем COBOL в истории вычислений, или что C # лучше, чем Java, и, следовательно, важнее. Тем не менее, ни Python, ни C # не изменили ни одной парадигмы (C # есть и всегда был просто постепенным переосмыслением Java, а Python на самом деле является дальним потомком awk). SQL не является языком общего назначения, и Lisp никогда не был коммерчески релевантным (бросайте вызов ненавистникам — но это так).

В дополнение к C ++, чтобы объяснить, почему его нет в моем списке: проще говоря, C ++ не был достаточно большим фактором достаточно быстро, прежде чем Java появилась на сцене. Люди не перешли это орды с COBOL на C ++. Несмотря на то, что это важный язык, его влияние на изменение мировоззрения значительно меньше, чем на языке Java.

Сходство Java с доктором Кто

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

Каждый раз, когда Java и JVM казались на грани уничтожения каким-то заклятым врагом (C #, Go, Ruby и т. Д.), Происходила регенерация, приводящая к очередной серии захватывающих эпизодов. Даже отвратительных ран, таких как интерфейс JNI или травмирующий ужасный параллельный исполнитель потокового беспорядка, не хватило, чтобы убить нашего героя. Аналогичным образом, замечательные улучшения производительности, такие как виртуальная точка доступа и огромный набор приемов оптимизации компилятора, представленных в Java 7, 8 и 9, постоянно поддерживали актуальность Java в мире, где тактовые частоты процессора застопорились, а ИТ-бюджеты после сбоя жаждут экономии средств. ,

Анализ побега помогал анализу затрат на спасение Java? (Ладно, это слишком много, Алекс, отступи с духом.)

Хотя естественной тенденцией ретроспективы является следование стрелке времени, я обнаружил замечательные проблемы в этом для Java. Наряду с другими наиболее важными с коммерческой точки зрения языками C, Fortran и COBOL, история Java столь же многопоточна, как и время выполнения, и рекурсивна, поскольку внешние силы согнули Java, а Java аналогичным образом изменила мир ИТ.

Чтобы проиллюстрировать это, мы можем взглянуть на JEE и Hadoop.

Слон И Рыба

На рубеже веков программирование сошло с ума. Что-то, что должно было быть действительно простым, например, обслуживание веб-страницы, внезапно потребовало (что казалось) страниц XML и стрижек кода Java только для определения «сервлета». Этот сервлет также будет поддерживаться внутри «сервера приложений», который имеет еще больше XML, определяющих Java-бины, которые плыли в море конфигураций и сервисов.

Некоторые читатели могут посчитать мой личный взгляд неприятным и почувствовать, что J2EE (теперь переименованный в JEE) был / просто удивительно блестящим. Это было в некотором смысле, потому что это показало, как новый современный язык программирования может наконец сломить мертвую хватку Мэйнфрейма в коммерческих вычислениях коммерческого масштаба. Хорошо определенные фрагменты J2EE (или используемые им фрагменты), такие как JDBC и JMS, были действительно удивительными. Внезапно у нас появились хорошие инструменты для бизнес-обработки, такие как подключение к базе данных и обмен сообщениями между системами. Java выглядела так, как будто она действительно может изменить все: от банковского дела до управления складом в распределенной вычислительной среде.

Загвоздка в том, что реализация Java Enterprise Edition была ужасна практически во всех отношениях. Я говорю это из личного опыта, а не с теоретической точки зрения. В самом начале 2000-х я был разработчиком J2EE.

История была примерно такой: «Все слишком медленно. Конец.».

Чтобы быть более любезным, я приведу немного больше деталей. Я работал в компании, которая создавала программное обеспечение для розничной торговли. Все их решения изначально были на C и работали с реляционными базами данных Oracle. Переход на J2EE был огромной ставкой с их стороны и требовал значительных инвестиций в переподготовку и другие ресурсы (они обанкротились). Одним из заказчиков этой новой линейки программного обеспечения на базе Java был зарождающийся (и все еще работающий много лет спустя) интернет-бакалейщик. Их система состояла из больших (по меркам времени) 16-ти процессорных серверов Sun.

Затраты на систему J2EE с ее неуклюжим управлением состоянием, когда некоторые компоненты должны были сохранять данные в базе данных через JDBC, а другие управляли логикой и т. Д., Снижали производительность. Даже с идеями «локального» и «удаленного» интерфейса, появившимися в более поздних версиях J2EE, сильная зависимость от JNDI для поиска бинов, а затем сериализации для связи между ними была крайне вредной.

Кроме того, система опиралась на JMS, которая в то время была катастрофической для Weblogic (версия 5, если я правильно помню). Действительно, реализация Weblogic JMS, которую мы начали с сериализации сообщений в Oracle с использованием типов BLOB-объектов, которыми Oracle 8i не мог управлять внутри транзакций. Да, действительно, постоянство сообщений JMS не было транзакционным, но они все еще просили денег за этот мусор.

Итак, я потратил 6 месяцев своей жизни, вырывая код бизнес-логики из J2EE и внедряя их в то, что мы бы сейчас назвали POJOS (обычный объект Java). Я пошел дальше и заменил JMS системой обмена сообщениями на основе PL / SQL, доступ к которой осуществлялся из Java с использованием привязок PL / SQL к Java. Все это работало хорошо и во много, много раз быстрее, чем система J2EE.

Затем мой друг и коллега переписали все это на PL / SQL, и это было еще быстрее.

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

Как раз в тот момент, когда сокрушительный вес JEE, казалось, привел к долгой медленной смерти для крупномасштабной бизнес-Java, Google взорвал мир своими известными статьями о GFS, Map-Reduce и BigTable. Файловая система Google и работающие над ней системы открыли новый подход к обработке. «Реализованная» модель программирования компьютера, на котором запущен сервер, который затем запускал процессы, исчезла. Кроме того, весь подход был несколько низким понятием; запускать простые вещи в больших избыточных «облаках» вычислительных ресурсов. Однако то, чем были эти «вещи», было гораздо менее предписывающим, чем тесно взаимосвязанный и абстрагированный мир JEE.

Вместо того, чтобы поддаться этому новому заклятому врагу, наши «голоса» позволили Java переродиться в совершенно нового зверя. Hadoop родился и вместо того, чтобы облако было смертью Java на предприятии, он внедрил Java в это предприятие в обозримом будущем.

Телефоны — новые холодильники

Внедрение независимости платформы в сознание разработчиков — это одна вещь, за которую, я считаю, мы все в огромном долгу перед Java. Рассматривая разработку программного обеспечения как в значительной степени независимую от обмана поставщика ОС, произвела революцию системное мышление более высокого уровня. То, что кто-то мог написать что-то для Windows и запустить это на Linux (или на Solaris, или на Irix, или на чем-то еще), просто растаяло в конце 90-х.

Я лично считаю, что сочетание независимости платформы Java и жесткой простоты Hadoop — две силы, которые несут наибольшую ответственность за предотвращение «захвата мира» Microsoft с помощью .Net.

Откуда эта независимость платформы? Какова была основная цель этого назад в тот день? Ну, мы можем переписать историю и сказать разные вещи пост-хок. Тем не менее, я отчетливо помню, как Сун говорил, что все это связано с холодильниками и тостерами. Каким-то образом они были полностью убеждены, что будущее за автоматизированными устройствами (верно), и что Java будет способом написать одну программу управления устройствами и запускать ее везде (неправильно).

Неправильно понять эту вторую часть — это не большая ошибка; Sun никак не могла предсказать, что сверхнизкие процессоры, работающие на стабильной операционной системе с открытым исходным кодом, окажутся абстракцией выбора над виртуальной машиной. Linux полностью перевернул мир, предоставив платформу независимости на уровне ОС и будучи свободным. Однако это другая история, а не история Java; вместо этого появился Android.

Многие разработчики бизнес-Java на самом деле не думают о влиянии Android, потому что он не запускает JVM. Тем не менее, он работает на Java. Сейчас все меняется немного (насколько я могу судить), но еще 5 или 6 лет назад стандартным способом разработки приложения для Android было написать его на Java на ПК с помощью эмулятора Android, скомпилировать его так, чтобы байт-код, а затем перекрестный перевод кода прикуса JVM в байт-код Dalvik .

Действительно, этот процесс был настолько удивительным, что когда я работал с Microfocus, мы скомпилировали COBOL в байт-код JVM, а затем перевели его в Dalvik, а затем запустили приложение COBOL на телефоне Android. Я не говорю, что это было хорошо, но это было весело.

Я хочу сказать, что Android (и в меньшей степени Java-телефоны до этого момента) сделали Java актуальной для огромного сообщества начинающих разработчиков. Я подозреваю, что университеты учат Java, а не C # из-за Android. Еще раз, «Голоса выключены» спасли Java и позволили ей переродиться в нового Доктора, чтобы принять новые вызовы в великолепной и захватывающей новой серии (на самом деле — я не смотрю доктора Кто — я делал это еще в 70-х и 80-х годах) Я как бы потерял интерес, когда Лалла Уорд и Том Бейкер покинули сериал) .

Я с некоторым странным удивлением оглядываюсь назад на дискуссии о том, «Android — это правильная Java» и некоторые чувства враждебности между Google и Oracle ; это бесспорный факт, что Google взятие на Dalvik и Java в качестве платформы для Android массово повысить ценность Java активов Oracle пришел к своим собственным.

Простота и элегантность — JMM

Java редко рассматривается как невероятная простота и элегантность, но в одном отношении она действительно показала другим основным языкам путь вперед. Представление новой модели памяти Java как части стандарта Java 5 стало триумфом простоты и эффективности.

Давайте серьезно отнестись к тому, насколько большим это было; впервые один из крупных коммерческих языков программирования четко изложил все отношения языка «происходит до» в многопоточной среде. Ушли все опасения по поводу крайних случаев; все отсутствующие оптимизации из-за попыток сохранить сходство между поведением, которое никогда не указывалось изначально. Внезапно Java стала «переходом к языку» для разработки алгоритмов без блокировок и ожидания. Академические статьи на такие темы, как реализация списка пропусков, могут быть основаны на Java. Далее, модель затем проникла на любой другой язык, который был основан на JVM.

Другие языки JVM не являются пределом его влияния; процитировать Википедию:

«Модель памяти Java была первой попыткой предоставить всеобъемлющую модель памяти для популярного языка программирования. [5] Это было оправдано растущей распространенностью параллельных и параллельных систем, а также необходимостью предоставления инструментов и технологий с четкой семантикой для таких систем. С тех пор потребность в модели памяти стала более широко распространенной, и подобная семантика предоставляется для таких языков, как C ++ . [6] «

Итак, да, Java научил C ++, как выполнять моделирование памяти, и я почувствовал влияние как с Java 5, так и с C ++ 11.

Небезопасно, но требуется для любой скорости

Фатальный недостаток Java, с тех пор как горячая точка наконец уложила компиляцию / интерпретацию в постель, всегда была и может всегда быть ее моделью распределения ресурсов. Java (как и многие другие языки — например, Python) рассматривает память как совершенно иной ресурс, чем что-либо еще. Рассмотрим C, в котором память выделяется через malloc, который возвращает указатель на эту память; этот ресурс освобождается путем звонка бесплатно. Файлы в C обычно открываются с помощью fopen и закрываются с помощью fclose. Другими словами, использование памяти и файловых ресурсов в C симметрично. C ++ идет дальше, имея управление ресурсами на основе области действия (RAII — даже Stroustrup признает, что это ужасное имя), которое позволяет симметрично обрабатывать ресурс памяти (new / delete) и другие ресурсы (файлы, сокеты, соединения с базой данных и т. Д.) Таким же образом и часто полностью автоматически.

По какой-то неясной для меня причине в 90-х годах стало хорошей идеей разрабатывать языки программирования, которые по-разному относятся к ресурсу памяти по сравнению со всеми остальными ресурсами. С точки зрения процессора это не имеет особого смысла. Основная память подключается через чипсет к ЦП, как и жесткий диск и сетевые карты. Почему память как-то сильно отличается от этих двух других?

Действительно, за последние 20 лет мы увидели, что основная память становится все более похожей на все другие ресурсы, поскольку задержка памяти по сравнению со скоростью процессора становится все более и более серьезной проблемой. В современных архитектурах NUMA подключение материнской платы к отдельному банку памяти может занять десятки тактов. Кроме того, нехватка памяти намного более фатальна, чем другие проблемы с ресурсами. Например, память дороже сетевых подключений. Если сокет потерян, программа может попытаться восстановить его в цикле; если возникает ошибка нехватки памяти, программа обречена. На самом деле, он может даже не записывать, что произошла ошибка.

Наряду с асимметрией управления ресурсами Java также очень плохо работает с IPC и внутренним межпотоковым взаимодействием (теперь меньше — см. Позже). Возможно, вы прямо сейчас кричите на экран, говоря: «Но в Java есть отличная поддержка библиотек для межпотокового взаимодействия и обработки сокетов для IPC». Хотя это правда, мир двигался дальше; перенесение переключения контекста для передачи данных из одного потока в другой или из одного процесса в другой больше не приемлемо. Широкое внедрение организации очереди на основе памяти и совместной памяти сделало Java неуклюжим и медленным по сравнению с C и C ++. Особенно с принятием C ++ 11, возможности Java выглядели ужасно.

Но, как это часто бывает, сообщество нашло способы обойти это. Скрытность в кишечнике JDK была (еще предстоит выяснить), этот класс называется sun.misc.unsafe . В Java 8 он даже был существенно улучшен и израсходован. Оказывается, что разработчикам JDK требовался более низкий уровень доступа к компьютерному оборудованию, чем предоставляли публичные классы JDK, поэтому они продолжали добавлять вещи к этому мрачному секрету.

Когда я работал в Morgan Stanley, я участвовал в проекте, направленном на то, чтобы системы C ++ с низкой задержкой могли «общаться» с Java через общую память. Чтобы убедиться, что подход к атомарности в Intel x86 был одинаковым для стандартов C ++ 11 и sun.misc.unsafe, я прошел открытый код JDK. Действительно, хотя некоторые из операций sun.misc.unsafe были немного неоптимальными (например, циклическое выполнение CAS для атомарной записи, а не использование перемещения с префиксом блокировки), подход «забор на запись и использование упорядоченных операций чтения» соответствовал 1: 1 с C ++ 11.

Поскольку методы sun.misc.unsafe присущи им, их производительность просто фантастическая, особенно с более поздними версиями JVM. Вызовы JNI — это безопасная точка, которая не позволяет оптимизатору включать их или разворачивать содержащие их циклы (в большей или меньшей степени). С помощью встроенных функций оптимизатор может рассуждать о них так, как если бы они были любыми другими методами Java. Я видел, как optmiser удалял несколько слоев вызовов методов с помощью встраивания и разворачивал внешний цикл так, чтобы sun.misc.unnsafe.setLong () достигал той же скорости, которую мы видели в программе оптимизации с профилем C. Честно говоря, так как оптимизация профилированного руководства используется так редко в C и C ++, Java и sun.misc.unsafe могут в действительности оказаться быстрее, чем эквивалентный C. Я всегда чувствую, что высовываю язык после того, как говорю это — не знаю почему.

Пуристы иногда могут ненавидеть sun.misc.unsafe, как показывает этот довольно печально известный пост.

«Позвольте мне быть тупым — sun.misc. Небезопасные должны умереть в огне. Это — ждать
за это — небезопасно. Это должно идти. Игнорировать любые теоретические веревки и
начать путь к праведности / сейчас / . До того, как
конец публичных обновлений JDK 8, так что у нас есть / * лет * /, чтобы решить это
должным образом. Но торчали наши головы в коллективных песках и надеялись на
Тривиальные обходные пути для Unsafe не сработают. Если вы используете
Небезопасно, это год, чтобы объяснить, где API сломан и получить его
Прямо….

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

Ну, как мы говорим в Англии, «этого не происходит, приятель». Как иллюстрирует этот пост , это везде и везде, это важно. Моя личная программа синтеза звука oss Sonic Field использует sun.misc.unsafe для прямого доступа к файлам, отображенным в памяти, внутри отображенных напрямую буферами. Кроме того, он сохраняет адреса каждого сегмента, отображенного в памяти, в файле большего размера в память вне кучи (malloc’ed). Весь этот код может звучать так, как будто он будет медленным, но из-за встроенных функций, позволяющих встроить его, он завершается гораздо быстрее, чем при непосредственном использовании напрямую сопоставленных байтовых буферов. Кроме того, поскольку эта память не является сборщиком мусора, она не перемещается в виртуальном адресном пространстве, что помогает оптимизировать использование кэша данных ЦП.

Как и в случае с моим приложением, существует множество программ, использующих sun.misc.unsafe, чтобы позволить Java конкурировать и иногда побеждать в C, C ++ и т. Д. По крайней мере, разработчики JDK / JVM уже поняли это. Напомним, что их частичное исправление — переменные дескрипторы — невероятно неуклюже (как я предположил в начале поста — Java, кажется, движется именно так). Однако, если он действительно (или становится) настолько быстрым, как sun.misc.unsafe для управления ограждениями и атомами памяти, тогда неуклюжесть может быть скрыта внутри библиотек. Хорошей новостью является то, что разработчики осознали реальную потребность сообщества и перестали пить абстрактную / функциональную классную помощь (немного). Остается надежда на лучшую, более быструю Java. Хотя я разочарован, увидев пока мало доказательств правильной поддержки от кучи в varhandles. Надеюсь, это придет или есть, но как-то скрыто (не стесняйтесь комментировать свои мысли).

Дженерики для универсальных программистов

Я вроде понимаю, что за тип стирается однородная структурно-параметрическая типизация сейчас — на это ушло много лет.

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

Если вы никогда не программировали пре-дженерики Java, то, вероятно, трудно представить, какой болью была задняя часть старой системы типов. Например, контейнер типа Vector был нетипизирован; он содержал проиндексированные объекты. Все ссылочные типы в Java являются подтипами Object, и, следовательно, Vector может содержать все, что является ссылочным типом; действительно любая смесь чего-либо. Бедному программисту-чмо перед тем, как его использовать, пришлось преобразовать то, что было извлечено из вектора, в соответствующий тип. Что еще хуже, программист должен был обеспечить, чтобы в Вектор входили только соответствующие типы; этот последний шаг является проблемой в сложных системах с разнородными командами программирования.

Само собой разумеется, ClassCastException был постоянным упадком программ Java. В настоящее время интегрированные среды разработки отлично предупреждают или даже предотвращают использование, склонное к случайным (преимущественно) случайным исключениям NullPointerException, а непатентованные средства избавляются от исключений ClassCastExceptions (в основном). Еще в начале 2000-х и до программирования на Java было четыре этапа:

  1. Напиши код.
  2. Скомпилируйте код
  3. Потратьте много-много часов / недель / дней на исправление ClassCastExceptions и NullPointerExceptions.
  4. Получите его, чтобы пройти модульные тесты — вернитесь к 4 много раз.

Все эти общие вещи (просто великолепны, кроме того, что, черт возьми, я дикие карты? Пока мы в этом, что такое стирание типа?

Я чувствовал, что должен знать, и, естественно, мне пришлось использовать обе концепции, чтобы доказать, что я метал как программист на Java. За исключением того, что они немного хитрые. Теперь у меня есть 2 JVM-компилятора, и я также много работал над коммерческим программированием на C ++, думаю, у меня есть довольно хорошее представление о том, что такое стирание типов. Кроме того, Java на самом деле не использует стирание типов (не кричите). На самом деле тип стирается в исполняемом байт-коде; у аннотированного байтового кода все еще есть типы. Другими словами, мы полагаемся на то, что компилятор корректирует типы, а не время выполнения, и компилятор не стирает тип на уровне AST / Type-System. Это также относится, например, к C ++, когда он содержит методы. Тип встроенного метода полностью стирается во время компиляции, но останется в отладочной информации (по крайней мере, для современных версий C ++). Однако мы не называем этот тип стиранием. Забавно, как реальность и дискуссии типа башен из слоновой кости так далеки так часто (я думаю, по высоте титульной башни).

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

В качестве примера давайте рассмотрим документацию Oracle по этому вопросу:

1
2
3
List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35)); // compile-time error

Однако следующее намного проще:

1
2
List<NaturalNumber> ln = new List<>();
ln.add(new NaturalNumber(35)); // This is fine.

Когда мне действительно понадобится подстановочный знак в реальной программе? Даже если мне это нужно, работает следующее:

1
2
3
4
5
6
class ConcreateNaturalNumber() extends NaturalNumber{}
class EvenNumber extends NaturalNumber{
  // Stuff
}
List<ConcreateNaturalNumber> ln = new List<>();
ln.add(new NaturalNumber(42)); // Compile time error.

Один из способов взглянуть на это — это List <? extends NaturalNumber> неявно определяет новый тип; этот тип «Любой потомок NaturalNumber». Хотя это кажется хорошим способом сделать систему типов завершенной и может быть полезной для разработчиков библиотек, для простых смертных, таких как я, если я хочу новый тип, почему бы не создать его явно?

Итак, дженерики кажутся чрезвычайно сложными из-за встроенных концепций стирания типов и групповых символов. Однако со временем сообщество Java научилось в основном концентрироваться на подмножестве обобщенных типов, которое использует явные типы и в значительной степени игнорирует стирание (просто дайте компилятору и среде выполнения сделать это под прикрытием). Следовательно, в настоящее время универсальные программисты, такие как я, могут использовать универсальные шаблоны, не заботясь о угловых случаях и правилах сложных типов.

Это то, что мне действительно нравится в сообществе Java; он любит идти на то, что работает. Это противоречит тому, что я вижу в мире C ++, где люди ищут каждый странный крайний случай, который можно использовать, а затем делают это, чтобы доказать, что они достаточно умны.

В то время как я печатаю о типе Какие еще типы имеют типы Java, которые нужно понимать при печати?

Мы легко можем упасть в иллюзию, что объектная иерархическая и номинативная параметрическая типизация — это все, что делает Java; но нет, это очень далеко от дела.

Java отошла от объектной ориентации в 1997 году (да, действительно) с введением API отражения. Чтобы получить представление о том, как это ощущалось в то время, эта статья была современной для выпуска (в ней говорится о Java-бинах — вы помните это?). Внезапно на Java появилась полная утка. Другими словами, мы могли бы посмотреть на метод в классе и вызвать его без необходимости знать что-либо о типе класса, кроме его имени. Скажем, есть метод:

1
2
3
void wagTail(){
   // some stuff.
}

В двух не связанных между собой классах говорят «CustomerService» и «Dog». С помощью объектов отражения, как CustomerService, так и Dog, можно вилять хвостом (что бы это ни значило — концепция контракта даже не подразумевается) без необходимости использования общего базового класса.

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

Конечно, полная динамическая диспетчеризация с проверкой типов во время выполнения. Например, Python отлично справляется с этой задачей, поскольку программисты на Python привыкли добавлять дополнительный код управления типом утки для обеспечения стабильности. Для Java последствия могли быть катастрофическими, но на самом деле (предупреждение о личном представлении на 100%) я подозреваю, что на самом деле это вынудило разработку Junit и других методологий модульного тестирования Java на очень сложном уровне, которого они сейчас достигли. Если вы выберете окно проверки времени компиляции, вам непременно придется протестировать экскременты кода, и Java является мировым лидером в этой области.

Я нахожу текущее положение дел, когда Maven и внедрение зависимостей работают вместе, чтобы быть абсолютно уверенным, что никто не имеет представления, какой код будет фактически выполняться в любой момент, довольно удручающе. Сказав это, кажется, что это хорошо работает для сообщества Java, и нет необходимости писать код таким образом (по крайней мере, в Java). Видя, что многомиллионные базы кода в Python работают очень хорошо, мое беспокойство по поводу динамической диспетчеризации во время выполнения несколько рассеялось. Живи и дай жить другим, может быть хорошим подходом здесь.

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

Первым и безусловно самым злым из них было / ткачество кода. Возьмите невинно выглядящий класс и придерживайтесь аннотации. Затем во время выполнения этот класс перезаписывает код, чтобы он отправлялся другому коду и полностью изменял свое поведение (Think Universal Soldier ). С этим пришло аспектно-ориентированное программирование, которое было как сквозным, так и серьезным вопросом. Полагаю, я не должен быть слишком яростным, ведь переплетение кода помогло всему движению POJO и Spring.

Насколько я понимаю, Spring больше не требует переплетения кода. Он динамически компилирует прокси-классы, а не добавляет аспекты к поведению классов. Результат с точки зрения программиста во многом такой же. Сейчас очень тяжело хлопать на перерывах, потому что … Spring и POJO в целом выступали в качестве противовеса J2EE / JEE, и еще до того, как hadoop стал большим успехом, помог спасти Java от медленной серой смерти. Действительно, JEE узнал о полной нагрузке от Spring и аспектного сообщества, поэтому результат был хорошим.

Не удовлетворенные всем этим разработчики JDK хотят иметь некоторые новые концепции типов. Сначала пришел тип вывод . Теперь C # начал с этого, введя ключевое слово var. В безумном порыве «не изобретенного здесь синдрома» Java пошла с алмазными операторами. Это лучше, чем ничего, скажем, черствый хлеб лучше, чем голодать.

Получив уровни Гомера Симпсона « наполовину » с <>, они стали полноправными с Lambdas. Из этой статьи мы получаем следующий пример:

1
2
3
4
5
6
7
8
n -> n % 2 != 0;
 (char c) -> c == 'y';
 (x, y) -> x + y;
 (int a, int b) -> a * a + b * b;
 () -> 42
 () -> { return 3.14 };
 (String s) -> { System.out.println(s); };
 () -> { System.out.println("Hello World!"); };

Так что «(х, у) -> х + у;» вещь, но «var x = 1;» не является. Да, это имеет смысл. Хотя на самом деле очень приятно иметь вывод типов в лямбдах. Если бы только они были ссылочными закрытиями первого порядка, а не поддерживали только ссылочную семантику второго порядка (они эффективно закрывают конечное состояние, но могут изменять ссылки внутри этого состояния), они были бы действительно полезны. Как таковые, они не могут гарантировать отсутствие побочных эффектов, но они не являются полной реализацией замыкания.

Еще не убедившись в ссылках второго порядка, попробуйте это:

1
LongFunction<Long> broken = chunks -> {reportTicker.set(chunks); return chunks % 10;};

Я только что проверил эту компиляцию — и это делает. Конечный (или фактически окончательный) объект reportTicker видоизменяется из-за разбитой лямбды. Таким образом, эффективный финал не добавляет никаких гарантий лямбдам с государственной точки зрения. Лямбды — это обычные объекты в многопоточном контексте, и их проще рассуждать, чем анонимные классы. Все эти усилия по созданию лямбд, и в итоге они стали синтаксическим сахаром для анонимных классов (с более сложной реализацией, использующей invokedynamic). Все еще не убежден? Вот вышеупомянутая лямбда, написанная с использованием анонимного класса.

1
2
3
4
5
6
7
8
9
LongFunction<Long> broken = chunks -> new LongFunction<Long>()
{
    @Override
    public Long apply(long value)
    {
        reportTicker.set(chunks);
        return chunks % 10;
    }
}.apply(chunks);

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

Если вам не нравится то, что я здесь говорю, просто используйте лямбды C ++ 11 в качестве первоклассных ссылочных замыканий и посмотрите, насколько это очень, очень мощный способ программирования.

Итак, это действительно должен быть конец этого, конечно? Эти Java / JDK разработчики не стали бы вводить другую систему типов, не так ли? Это были бы помешанные…

Ну, они сделали — время параметризованного полиморфизма; сумасшедший как коробка лягушек, но в конечном итоге весьма полезный. Если бы система типов Java еще не была в значительной степени каноническим примером второго закона термодинамики — добавление новой системы типа / диспетчеризации было бы очень плохим ходом, но лошадь хорошо и действительно вышла из ворот и создала милое маленькое стадо. мустанга в горах далеко, так почему бы и нет?

VarHandles — что весело:

«Арктичность и типы аргументов для вызова метода режима доступа не проверяются статически. Вместо этого каждый метод режима доступа указывает тип режима доступа, представленный в виде экземпляра MethodType, который служит своего рода сигнатурой метода, с помощью которого динамически проверяются аргументы. Тип режима доступа предоставляет формальные типы параметров в терминах типов координат экземпляра VarHandle и типов для значений, важных для режима доступа. Тип режима доступа также дает тип возвращаемого значения, часто в терминах типа переменной экземпляра VarHandle. Когда метод режима доступа вызывается для экземпляра VarHandle, дескриптор символьного типа на сайте вызова, типы времени выполнения аргументов для вызова и тип времени выполнения возвращаемого значения должны соответствовать типам, указанным в режиме доступа. тип. Исключение времени выполнения будет выдано, если совпадение не удастся ».

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

Кафка, Искра И Невероятная Кассандра

Облачных систем второго поколения сейчас предостаточно, и Java снова стала лидером. В то время как некоторые облачные разработки переходят на C ++ с такими известными игроками, как Impala, использующие Some, и Scylla, использующими только этот язык, все еще справедливо сказать, что большая часть облачной инфраструктуры OSS работает либо на Java, либо работает на JVM. Например, SPARK, которая за последние месяцы превратилась из искры в лесной пожар, написана на Scala. Я не уверен, почему кто-то хотел бы сделать такую ​​вещь, но там это есть, и это работает и набирает обороты все время.

С этими игроками приходит светлое будущее для Java. Темный плащ морального износа не где увидеть. Хотя я не рассматриваю следующее десятилетие как свободное испытание, о котором я расскажу в следующем разделе.

Монолит молотый в песок

У Java и JVM есть некоторые базовые концепции, воплощенные в них с самого первого дня. Как я уже говорил ранее, одним из них является асимметрия ресурсов. Еще одна закрытая песочница. Это действительно имело смысл, когда Java изначально была разработана для запуска в качестве апплета как защищенного процесса и не имела доступа к ОС из пользовательского исходного кода. В этой модели язык Java, тесно связанный с его комплектом для разработки, должен был обеспечивать все необходимое для выполнения желаемых задач. Абсолютный провал концепции Microsoft при проектировании Azure в чистом виде .Net без концепции машин и без Linux демонстрирует, как этот подход совершенно не подходит для облачных вычислений.

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

Задача будет сложной: «Имеет ли смысл большая многопоточная одноэлементная виртуальная машина, когда для всех серьезных вычислений требуется совместная работа многих компьютеров».

Учтите это, чтобы вычислить что-то серьезное с моим нынешним работодателем, требуются десятки тысяч вычислительных ядер. Другими словами, вычисления выполняются не на уровне сервера, а на уровне ядра и программы, распределенных по многим серверам. Тот факт, что есть даже серверы, не виден конечным программистом. Таким образом, JVM становится барьером, а не выгодой. Логично ли иметь одну огромную JVM на каждом из множества серверов? Возможно нет. Но тогда логично ли иметь на сервере 32 маленьких JVM? Учитывая, что JVM не предназначена для этого и не предназначена для запуска и остановки в коротких циклах, в этой области возникают огромные проблемы.

Сказав это — как всегда Java возрождается. Время запуска было сокращено с помощью вариатора разделения (ну, мне сказали, что я не очень уверен в реальности), а размеры JDK теперь лучше контролируются с помощью модулей. Как таковой запуск / выключение должно быть лучше сейчас. Тем не менее, поскольку невозможно создать JVM, он никогда не сможет конкурировать с другими системами (C ++, C, Rust, Python и т. Д.), Которые могут использовать модель fork и run в облаке.

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

PS

На всякий случай, если бы я никого не оскорбил где-то, вот несколько вещей, которые я должен был обсудить подробно, но чувствовал, что разглагольствование продолжалось достаточно долго:

  • Попробуй с ресурсами: Отлично.
  • Maven: мерзость
  • Gradle: Я не думал, что что-то может быть хуже, чем сделать, но это было достигнуто.
  • Качели: круто, но паутина съела свой обед.
  • nio: действительно хорошо, когда он вышел, но скоро нуждается в хорошей полировке.
  • Вальхалла: Возможно, это было бы здорово, но создание неизменных типов значений наносит ущерб этой концепции. Reified встроенные общие контейнеры будут хороши.
  • Вызывать динамику: слишком статично, но есть обещание.
  • JMH: Блестящий и о времени.
  • Муравей: Если бы это был не XML, это было бы 4 из 5 звезд.
  • Дразнящие фреймворки: Да, наверное, но в большинстве случаев они кажутся излишне используемыми.
  • Сборщик мусора G1: Поскольку я не уверен, что огромные JVM имеют смысл, таким образом, не ясно, что G1 был необходим, но это определенно не плохая вещь.
  • JVMTI: Отлично.
  • Внутренние Классы: Да, они были изобретены и не являются частью оригинальной Java, и они прекрасны.
  • ОСГИ: Жизнь слишком коротка.
  • Головоломка: больше нравится.
  • Scala: Очень похоже на Delorean, выглядит действительно круто, но смехотворно медленно, трудно начать и все время ломается.
  • Остальное: извините, я забыл о вас, Java настолько огромна, что вы обязательно должны забыть о ней.
Опубликовано на Java Code Geeks с разрешения Александра Тернера, партнера нашей программы JCG . Смотреть оригинальную статью здесь: 20 лет Java …

Мнения, высказанные участниками Java Code Geeks, являются их собственными.