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

Отсутствуют материализованные дженерики (reified generics). Об этом не писал только ленивый, причём большинство комментариев свидетельствуют о непонимании сути затирания типов. Если Java-разработчик говорит: «Я не люблю затирание типов», то в большинстве случаев это означает «Мне нужен List int». Вопрос примитивной специализации дженериков лишь косвенно связан с затиранием, а польза от дженериков, видимых в ходе исполнения, сильно преувеличена молвой.

Беззнаковые вычисления (unsigned arithmetic) на уровне виртуальной машины. Отсутствие в Java поддержки беззнаковых арифметических типов вызывает недовольство разработчиков уже многие годы. Но это является обдуманным решением создателей языка. Наличие лишь знаковых вычислений существенно упрощает язык. Если сегодня начать внедрять беззнаковые типы, то это повлечёт за собой очень серьёзную переработку Java, что чревато массой больших и маленьких багов, которые будет трудно вылавливать. Заодно сильно возрастает риск дестабилизации всей платформы.

Длинные указатели для массивов. Опять же, внедрение этой функциональности потребует слишком глубокой переработки JVM с возможными неприятными последствиями, причём далеко не только с точки зрения поведения и семантики сборщиков мусора. Хотя нужно отметить, что Oracle ищет пути внедрения подобной функциональности с помощью проекта VarHandles.

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

Более выразительный синтаксис импорта


Синтаксис импорта в Java предоставляет нам не так много возможностей, доступны лишь две опции: импорт одного класса или всего пакета. И если нужно импортировать лишь часть пакета, то приходится громоздить кучу строк. Также бедность синтаксиса заставляет нуждаться в таких возможностях IDE, как свёртка импорта для самых больших Java-файлов.

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

import java.util.{List, Map};

Читабельность кода повысилась бы за счёт возможности локального переименования типа (или создания алиаса). Заодно было бы меньше путаницы с типами, имеющими одинаковое короткое имя класса:

import java.util.{Date : UDate};
import java.sql.{Date : SDate};
import java.util.concurrent.Future;
import scala.concurrent.{Future : SFuture};

Пошло бы на пользу и внедрение расширенных подстановочных символов:

import java.util.{*Map};

Это небольшое, но полезное изменение, которое можно целиком реализовать с помощью javac.

Литералы коллекций


В Java присутствует синтаксис (хотя и ограниченный) для объявления литералов массива. Например:

int[] i = {1, 2, 3};

У этого синтаксиса есть ряд недостатков. Например, литералы должны использоваться только в инициализаторах.

Массивы в Java не являются коллекциями. «Мостовые методы», представленные во вспомогательном классе Arrays, также имеют изъяны. Скажем, метод Arrays.asList() возвращает ArrayList, который при ближайшем рассмотрении оказывается Arrays.ArrayList. Этот внутренний класс не содержит методов, альтернативных List, а похожие методы выбрасывают исключение OperationNotSupportedException. В результате в API возникает уродливый шов, затрудняющий переход между массивами и коллекциями.

Для отказа от синтаксиса объявления литералов массива нет причин, в той или иной форме он присутствует во многих языках. Например, на Perl можно написать так:

my $primes = [2, 3, 5, 7, 11, 13];
my $capitals = {'UK' => 'London', 'France' => 'Paris'};
На Scala — так:
val primes = Array(2, 3, 5, 7, 11, 13);
val m = Map('UK' -> 'London', 'France' -> 'Paris');

К сожалению, в Java нет полезных литералов коллекций. Этот вопрос поднимался неоднократно, но ни в Java 7, ни в Java 8 эта функциональность не появилась. Также вызывают интерес литералы объектов, но в Java их гораздо труднее реализовать.

Структурная типизация


Именование играет очень большую роль в системе типов в Java. Все переменные должны относится к именованным типам, и невозможно выразить тип только через определение его структуры. В других языках, например, в Scala, можно выражать тип, не объявляя его при реализации интерфейса (или трейта Scala), а просто подтвердив, что он содержит определённый метод:

def whoLetTheDucksOut(d: {def quack(): String}) {
 println(d.quack());
}

В данном случае будет принят любой тип, содержащий метод quack(), вне зависимости от наличия наследования или использования типами общего интерфейса.

Неслучайно в качестве примера был выбран quack() — структурная типизация имеет много общего с «утиной типизацией» в том же Python. Однако в Scala типизация осуществляется при компиляции, что говорит о гибкости языка с точки зрения выражения типов, которые было бы трудно или невозможно выразить в Java. К сожалению, здесь система типов имеет очень небольшие возможности по структурной типизации. Можно задать локальный анонимный тип с дополнительными методами, и если один из них будет сразу же вызван, то Java позволит скомпилировать код.

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

Алгебраические типы данных


Благодаря дженерикам Java является языком с параметризованными типами (parameterized types), представляющими собой ссылочные типы (reference types) с параметрами типа. При их подстановке в одни типы образуются другие. То есть получившиеся типы состоят из «контейнеров» (обобщённых типов) и «полезной нагрузки» (значения параметров типа).

В некоторых языках поддерживаемые составные типы сильно отличаются от дженериков Java. В качестве примера сразу напрашиваются кортежи (tuples), хотя куда больший интерес вызывают тип-суммы (sum type), иногда называемые также «несвязными объединениями типов» (disjoint union of types) или «размещенными объединениями» (tagged union).

Тип-сумма — это однозначный тип, то есть в каждый момент времени переменные могут иметь лишь одно значение. Но при этом оно может быть любым валидным значением, относящимся к указанному диапазону различных типов. Это справедливо даже в том случае, если являющиеся значениями несвязные типы никак не связаны друг с другом с точки зрения наследования. К примеру, в языке F# можно задать тип Shape, экземплярами которого могут быть прямоугольники или круги:

type Shape =
| Circle of int
| Rectangle of int * int

F# сильно отличается от Java, но и в Scala эти типы реализованы с ограничениями: запечатанные типы (sealed types) применяются с case-классами. Запечатанный класс нельзя расширять за пределами текущей единицы компиляции (compilation unit). Это практически аналог терминального класса в Java, но в Scala базовой единицей компиляции является файл, и многочисленные высокоуровневые открытые классы (public classes) могут объявляться в единственном файле.

Это приводит нас к паттерну, при котором запечатанный абстрактный базовый класс объявляется вместе с несколькими подклассами, которые соответствуют возможным несвязным типам из тип-суммы. В стандартной библиотеке Scala содержится много примеров использования этого паттерна, включая Option[A], который аналогичен типу Optional T из Java 8.

В Scala к несвязным объединениям двух возможностей относятся Option и Some, а также None и Option type.

Если бы мы реализовали в Java такой же механизм, то столкнулись бы с ограничением, когда единица компиляции, по сути, является классом. Получается не так удобно, как в Scala, но всё же можно придумать способы решения. Например, можно было бы использовать javac для обработки нового синтаксиса применительно к классам, которые мы хотим запечатать:

final package example.algebraic;

Подобный синтаксис означал бы, что компилятор должен допускать расширение класса с учётом конечной упаковки в рамках текущей папки, отклоняя все иные попытки расширения. Это изменение тоже можно было бы реализовать с помощью javac, но без проверок в ходе исполнения его нельзя полностью защитить от циклического кода (reflective code). Кроме того, Java-реализация была бы менее полезной, чем в Scala, поскольку в Java не хватает развитых выражений сопоставления (match expressions).

Точки динамического вызова (Dynamic call sites)


Начиная с версии 7 в Java появился на удивление полезный инструмент: байткод invokedynamic, призванный выполнять роль основного механизма вызова. Это позволяет исполнять динамические языки поверх JVM, а также расширять систему типов в Java путём добавления встроенных методов и изменения интерфейса, в то время как раньше это было невозможно. Расплачиваться за это приходится несколько возросшей сложностью. Но при умелом обращении invokedynamic является мощным инструментом.

Правда, у него есть одно странное ограничение. Несмотря на объявленную поддержку в Java 7, почему-то до сих пор не обеспечивается прямой доступ к методам динамического вызова. Хотя весь смысл динамической диспетчеризации заключается в том, чтобы позволить разработчикам самим принимать решение, какой метод вызывать из конкретной точки вызова, причём принятие решения можно отложить до момента исполнения кода.

Примечание: не путайте этот способ динамического связывания с ключевым словом dynamic из C#. В нашем случае вводится объект, в ходе выполнения динамически определяющий свои привязки; это не сработает, если объект не поддерживает запрашиваемые вызовы методов. Экземпляры подобных динамических объектов в ходе выполнения неотличимы от «обычных» объектов, а сам механизм получается небезопасным.

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

Можно достаточно просто добавить в Java эту функциональность. Например, с помощью какого-нибудь ключевого слова или аннотирования. Также потребуется дополнительная библиотека и поддержка на стадии сборки.

Проблески надежды?


Развитие архитектуры языка и его реализация — это искусство достижения возможного. Существует немало примеров, когда важные изменения очень долго пробивают себе дорогу. Например, в С++ лямбда-выражения появились только в 14 версии.

Многим не нравится неторопливость развития Java. Но Джеймс Гослинг придерживается позиции, что нельзя реализовывать функциональность, пока она не будет полностью понята и осознана. Хотя консервативность архитектуры Java является одной из причин успеха этого языка, в то же время она не нравится многим нетерпеливым молодым разработчикам, жаждущим быстрых перемен. Ведутся ли работы над внедрением каких-то из вышерассмотренных возможностей? Можно это осторожно предположить.

Некоторые из описанных идей можно реализовать с помощью того же invokedynamic. Как вы помните, он должен выполнять роль основного механизма вызова, отложенного до момента выполнения. Согласно предложению по улучшению языка JEP276, можно стандартизировать библиотеку Dynalink, которая изначально создавалась Аттилой Жегеди (Attila Szegedi) для реализации «протокола мета-объектов» в JVM. Позднее автор библиотеки перешёл работать в Oracle, который использовал Dynalink в Nashorn, реализации JavaScript на JVM. Описание библиотеки есть на Github, но сама она оттуда удалена.

По существу, Dynalink позволяет говорить об объектно-ориентированных операциях — «получить значение свойства», «присвоить свойству значение», «создать новый объект», «вызвать метод» — без необходимости воплощения их семантики с помощью соответствующих статически типизированных, низкоуровневых операций JVM.

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

Некоторыми ключевыми разработчиками Scala этот механизм рассматривался в роли возможной замены при реализации структурных типов в этом языке. Хотя в текущей версии ставка сделана на рефлексии, но появление Dynalink на сцене может всё изменить.

Комментарии (42)


  1. senia
    14.04.2016 10:13
    +4

    Запечатанный класс нельзя применять за пределами текущей единицы компиляции (compilation unit).
    В оригинале:
    A sealed type in Scala is not extensible outside of the current compilation unit.
    .
    Не «применять», а «расширять» (наследовать). Если бы sealed классы нельзя было применять вне файла, в котором они объявлены, то в них было бы мало смысла.


  1. grossws
    14.04.2016 11:54

    def whoLetTheDucksOut(d: {def quack(): String}) {
     println(d.quack());
    }

    Неслучайно в качестве примера был выбран quack() — структурная типизация имеет много общего с «утиной типизацией» в том же Python. Однако в Scala типизация осуществляется при компиляции, что говорит о гибкости языка с точки зрения выражения типов, которые было бы трудно или невозможно выразить в Java. К сожалению, здесь система типов имеет очень небольшие возможности по структурной типизации. Можно задать локальный анонимный тип с дополнительными методами, и если один из них будет сразу же вызван, то Java позволит скомпилировать код.

    В данном примере используются structural types, которые реализуются через reflection. Так что проверки компилятором нет.


    1. senia
      14.04.2016 13:29
      +2

      Проверка компилятором как раз есть.

      scala> whoLetTheDucksOut(new { def quack() = "quack!" })
      quack!
      
      scala> whoLetTheDucksOut(new { })
      <console>:13: error: type mismatch;
       found   : java.lang.AnyRef
       required: java.lang.AnyRef{def quack(): String}
             whoLetTheDucksOut(new { })
                               ^
      

      Но есть и рефлексия. Из-за нее проседает производительность, но и эту проблему, возможно, пофиксят. По крайней мере есть пути решения.


  1. guai
    14.04.2016 12:52
    +4

    «польза от дженериков, видимых в ходе исполнения, сильно преувеличена молвой» — прям стокгольмский синдром какой-то :)


    1. KeLsTaR
      14.04.2016 14:58
      +1

      А в чем огромная польза, на ваш взгляд?


      1. guai
        14.04.2016 15:35
        +2

        проблема явы как раз в том, что все фичи в нее добавлялись изолированно, каждая принесла «огромную пользу», насколько могла. Но они довольно хреново стыкуются между собой. Например, рефлекшен и оверлоадинг методов не стыкуются, надо каждый раз указывать все аргументы, чтоб найти метод — это неудобно.
        так же и с овеществленными дженериками, если их добавить сейчас, они действительно мало чего дадут, потому что хорошо бы заодно переделать коллекции, API — да практически всё. Кортежи на них можно делать, например. Делаем кортежи — хотим единую абстракцию кортежей и параметров методов. И так далее.
        Представьте себе аналог jdbc, который вам отдаёт итератор над по-человечески типированными кортежами.
        Но глубокого пересмотра фич без запиливания нового языка не получится всё-равно.
        А просто так говорить, что раз его нет, значит и не надо — сродни стокгольскому синдрому, оправдание неприятных обстоятельств, которые всё-равно не можешь поменять.
        Поэтому я считаю, ораклу стоило бы больше внимания уделать эффективной работе новых языков на JVM, чем тащить новые языковые фичи в яву, они уже большого прироста не дадут, лямды — это было последнее улучшение, дающее «огромную пользу».
        Как овеществленные дженерики влияют на дизайн языка, можно на примере цейлона глянуть, хорошо влияют. Разрабы утверждают, что совокупный эффект от состыкованных фич позволяет писать сразу корректные программы, практически без тестирования их в рантайме, и пока у меня нет оснований в этом тезисе усомниться.


        1. 23derevo
          14.04.2016 16:44
          +3

          оооо, как богато.

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

          а как бы вы сделали?

          Представьте себе аналог jdbc, который вам отдаёт итератор над по-человечески типированными кортежами

          а как типы данных определяются? Контежи типизированные? Чем вам Spring-JDBC не нравится?

          ораклу стоило бы больше внимания уделать эффективной работе новых языков на JVM

          вы можете поизучать эту тему на досуге и попытаться понять, что, во-первых, оракл этим уже занимается, а во-вторых, это сущая благотворительность, потому что доля этих самых «новых языков на JVM» меньше, чем погрешность изменения, которым она считается.

          Разрабы утверждают

          разрабы могут утвеждать всё, что угодно. Только вот цейлона в TIOBE нет даже в Top-50. (там доля в 0.2% является достаточной для попадания, если что).


          1. guai
            14.04.2016 17:02

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


            1. 23derevo
              14.04.2016 17:08
              +2

              слушайте, я вам задал вполне конкретные вопросы, а вы мне в ответ философию какую-то.

              Давайте я совсем просто переформулирую:
              1. Как сделать рефлекшен по куче перегруженных методов?
              2. как типизировать кортежи (ruple, row), прилетающие из БД?
              3. зачем вендору вкладываться в развитие языков, на которые никто не пишет?
              4. как можно сравнивать «клевый» язык, которым мало кто пользуется с языком, которым пользуются десятки миллионов людей?
              5. насчет языков и хабраопроса — вы знаете, что такое «survivor bias»?


              1. guai
                14.04.2016 18:05
                +2

                1. как в яве. а если не делать перегруженных методов, то можно еще как-то, попроще, только по имени.
                2. в рантайме. для этого и нужны овеществленные дженерики.
                3. понятия не имею, их спросите. но существование больше одного языка должно навевать мысль, что причины есть.
                4. я сравниваю по количеству фич, порогу входа, трудозатратам на перевод проекта, наличию интеропа со старыми наработками, наличию нужных мне либ — вот основные факторы.
                5. знаю.
                А теперь обрисуйте свою позицию.


                1. cs0ip
                  14.04.2016 20:39

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


                  1. guai
                    14.04.2016 21:30
                    +1

                    ну кроме нестыковки оверлоадинга с рефлекшенами в яве оно еще и с дженериками не стыкуется, а именно со стиранием их. если с точностью до дженерика параметры совпадают, то всё-равно придется метод называть по-другому, тк в рантайме были бы одинаковыми. Более понтовые системы типов позволяют от оверлоадинга избавиться, если есть дефолтные значения, передача параметров по имени, нормальные рефлекшены — то вроде оно не особо и надо. Вот как в цейлоне, в скале.
                    Еще есть трабла с эволюцией кода: я делаю менее специфичный метод и отовсюду он вызывается, а потом делаю более специфичный перегруженный вариант, и некоторые вызовы без никаких уведомлений начинают вызывать другой метод и тут всё может поломаться. Версию либы, например, обновили и код перестал работать — крайне сложная к отлавливанию ошибка, нужно быть в курсе, что там в либе поменяли, API даже не поменялось, а поведение поменялось.
                    Есть траблы, когда в языке взаимозаменяемость методов и первоклассных функций имеется. Из соображений оптимизации чаще всего делают так, что метод это функция, в которую мы первым параметром передаём конкретный экземпляр this, а функция — ну просто адрес в памяти. Вот это тоже сложно поженить с оверлоадингом, разве что можно всё зарулить на одну функцию и в ней уже разбираться динамически с типами, но это оверхэд.
                    Может еще где мешается — больше не вспомнил.


                  1. guai
                    14.04.2016 22:48

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


      1. Saffron
        18.04.2016 10:27

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

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

        Java-программистов, понятное дело, это не напрягает. Они вместо сложных отношений типов фигачат везде Object и приводят его по мере необходимости. Концепция статических типов и типобезопасного программирования прошла мимо них.


  1. aibar
    14.04.2016 12:57
    +7

    Не хватает только некоторым, большинству хватает


    1. NeoCode
      14.04.2016 14:50
      +1

      В течение многих лет большинству хватало (да и сейчас, поверьте, многим хватает) if, else и goto :)


      1. Prototik
        14.04.2016 15:51
        +1

        if, else

        … которые, в сущности, выражаются через goto, так что список можно сократить до одного элемента :)


      1. dougrinch
        14.04.2016 16:21
        +1

        else не нужен


  1. stepanp
    14.04.2016 13:27
    -14

    Тормоза бы лучше починили, все остальное терпимо


    1. gurinderu
      14.04.2016 17:01
      +2

      Мне кажется, или я вижу вас в каждом треде про JVM-based langs и вы везде говорите, что они тормозят?


      1. stepanp
        14.04.2016 19:56
        -4

        Ну я же правду говорю :) К сожалению приходится пользоваться тулами, написанными на JVM-based langs и их производительность меня категорически не устраивает.


        1. terryP
          15.04.2016 00:04
          +1

          Мыши кололись, но ели кактус. (с) Если вам придется пользоваться кривонаписанной криворукими разработчиками программой на C, вы будете в каждой статье тогда язык С обвинять? Найдите аналогичные тулы, которые не написаны на JVM, смените работу в конце концов, зачем же так мучится и тратить нервы.


          1. stepanp
            15.04.2016 00:23

            Не для всего есть альтернатива.

            >Если вам придется пользоваться кривонаписанной криворукими разработчиками программой на C
            Это вы сейчас про JVM?


            1. terryP
              15.04.2016 00:27
              +1

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


        1. terryP
          15.04.2016 00:14

          Кстати, попробуйте тут описаны тулзы по конвертации jar файлов в нативный код: такие как Launch4j и packr. Если я понял ваша главная проблема загрузка JVM, попробуйте перегнать все в натив и может быть вам будет счастье.


          1. r00tGER
            15.04.2016 09:47
            +6

            Если он решит проблему — он больше не сможет ныть.


          1. TonyLorencio
            15.04.2016 11:59

            Натив? Он же просто пакует выбранную JVM в бандл


            1. terryP
              15.04.2016 14:44

              Например, в описании packr указано что он перегоняет в найтив и jar и jre, при этом есть возможность ограничить только нужной частью jre. И что он наиболее подходит для GUI приложений. Не знаю, не пользовался, но описание на github'e выглядит интересно.


  1. intet
    14.04.2016 14:12

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


  1. icxon
    14.04.2016 14:58

    Автор, что Вы можете сказать о Kotlin?


  1. vadim_ig
    14.04.2016 15:00

    Лично мне не хватает typedef'ов для повышения читабельности параметризованных типов — страдаешь сначала записывая объявление, а потом еще и читая его


  1. Instand
    14.04.2016 16:18
    +1

    Например, в С++ лямбда-выражения появились только в 14 версии
    Лямбды появились в С++11


  1. MzMz
    14.04.2016 16:20
    +4

    Более выразительный синтаксис импорта

    не помню когда в последний раз писал импорт руками


  1. j_wayne
    14.04.2016 17:52
    +2

    Я пишу на Java и на Ruby, после Ruby больше всего не хватает именованных (непозиционных) аргументов и значений аргументов по умолчанию.
    def foo(arg1:, arg2: nil)

    end

    Куда больше не хватает unsigned Integers, но эта тема в статье раскрыта.


  1. prijutme4ty
    14.04.2016 19:26
    +1

    Литералов регулярных выражений не хватает. Экранирование регулярного выражения выраженного строкой распухает сильнее, чем могло бы, будь отдельный литерал для регэкспов.


  1. kidar2
    14.04.2016 19:45

    Вот чего мне реально не хватает:
    К примеру переделать сравнение примитивных типов Integer == int, чтобы не падало NPE, А выдавало false если null сравнивают с 666 к примеру.
    Ещё чтобы == работал для String, а не надо было писать «bla».equals(«bla»)


  1. Throwable
    14.04.2016 19:48

    Давайте разделим фичи на три типа:
    1. тяжелые, значительно затрагивающие работу VM
    2. средние, добавляющие в язык определенный функционал, но контролируемых на уровне компилятора
    3. легкие — всякий синтаксический сахар

    К первому типу будет относиться value types, если когда-нибудь вообще выйдут. За все время существования Java не было сделано столь серьезных изменений. Даже появление generics и лямбд фактически не затрагивало VM. Любую подобную фичу реализовать с гарантией обратной совместимости будет огромной проблемой. Придется решать сразу огромный круг задач из всевозможных областей, менять спецификации, JMM, валидатор кода, поддерживать на всех архитектурах, etc…

    Второй тип — это все, что до сих пор делалось в Java. Тем не менее всегда проблемно что-то встроить, не поломав совместимости. С одной стороны у нас есть крутая фича, с другой — API Java и куча легаси-кода, который нужно мигрировать. Основной затык в лямбдах был именно встроить их в коллекции. Разработчики реально задолбались, в итоге выкинули все нахрен и сделали отдельно Stream. Даже такая простая вещь как unsigned тип поломает все и сразу: сериализацию, все библиотеки, работающие с рефлекшном, абсолютно все фреймворки, etc…

    Сахар? Ну а как бы зачем? Семантически запись не меняется, лишь делается чуть короче, что не имеет большого смысла, т.к. сегодня IDE берет на себя всю нагрузку по печатанию кода. Посмотрите, какая полемика развернулась по поводу var. Так что импорты, литералы и прочее — это то, что меньше всего волнует.

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


  1. Alex_ME
    15.04.2016 01:24

    Мне, как C# программисту, но которому приходится программировать и на java (android) самым главным минусом является отсутствие делегатов, и, как следствие, нормальных событий.
    Написание интерфейсов OnXXXListener на каждый чих весьма утомляет. Делегаты делают все гибче и проще.

    Не хватает и свойств, геттеры-сеттеры функции не так удобны и красивы. Но это чисто сахар.

    Так же не хватает неких аналогов async, но C# они реализуются на уровне компилятора через корутины, которых тоже нет (?).


    1. gurinderu
      15.04.2016 17:19
      +1

      Пишите на scala под andoid.


    1. zirix
      15.04.2016 19:19

      Вместо делегатов можно использовать interface с нужной функцией. После реализовать его в нужном классе и отдать класс туда где его будут вызывать.


      1. Alex_ME
        15.04.2016 21:08

        Я так и делаю, что я и написал. Но это менее удобно и более утомительно.


  1. fly_style
    15.04.2016 19:15
    +1

    Мне, например, не хватает Case classes, как в Scala.