Комментарии 182
Простите братья джависты, вырвалось.
серьезным языком программирования.
В контексте математики?
Абстрактный пример могу привести. Например, есть класс Date, и для него переопределён оператор +, где второй аргумент — int, чтобы прибавлять дни.
В какой-то момент понадобилось добавлять к дате не дни, а секунды. Придется писать метод Date.addSeconds, потому что + int уже переопределен. Тогда чтобы добавить один день, нужно будет писать date += 1, а чтобы добавить секунду date.addSeconds(1), и постоянно помнить об этом.
Вообще, для повседневных задач, типа получения/вывода данных, рисования формочек и всякой бизнес-логики, перегрузка операторов не особо применима. А вот для математики это круто! Далеко за примером ходить не надо: работа с BigDecimal в Java сейчас это боль. С перегрузкой операторов это было бы на порядок проще и наглядней.
Оператор умножения в общем-то вообще можно на чём угодно использовать, вопрос в семантике.
Как мне кажется, здесь проблема не в перегрузке операторов, а в том что вы пытаетесь прибавить тёплое к зелёному и получить что-то осмысленное.
все так, проблема именно в том что слишком просто допустить подобные ляпы в неумелых руках. А потому язык расчитанный на промышленную разработку (где неумелых рук предостаточно) не должны предоставлять такие фичи.
p.s. не считаю перегрузку операторов чем-то обязательным и необходимым, что по поводу абстракций — опять же не вижу принципиальной разницы с сообщениями (obj1.plus(obj1)
вместо obj1 + obj2
). Хотя то как это реализовано скажем в python мне лично нравится (где оператор это просто функция).
Увы primitive obsession настолько распространенный код смэл, что опасность остается. Но в целом в таком варианте я допускаю пользу.
Если есть удобные фабричные методы для создания интервалов в разных единицах, зачем отдельные типы?
Конечно, я не предлагаю заводить фабричный метод для создания временного интервала из граммов. Важно не путать разные величины, а вот в каких единицах задано значение — неважно. Что-то типа TimeDelta::fromSeconds
, TimeDelta::fromMonths
и так далее, возвращающие один и тот же тип (с типом для массы не совместимый). Нетрудно придумать единообразное внутреннее представление, с которым перегрузка будет работать единообразно. Иначе говоря, 24 часа и сутки — это ровно одно и то же. А вот какая польза от отдельных типов для интервала в секундах и интервала в минутах — неясно.
Ага, договорились. Замечу, что вы выше предлагали "отдельные типы для секунд, дней, месяцев, и так далее".
я по возможности не использую потенциально опасные практики.
Да программировать вообще опасно, если разобраться. Шаг влево, шаг вправо — exception.
кстати, это универсальная фраза!
можно так
В конексте практического программирования,перегрузка операторовнаследование методов часто может оказаться «бомбой замедленного действия», срабатывающей как только нужно добавить альтернативную реализациютого же операторатого же метода.
С этими новыми keywords другая проблема — в их освоении и понимании, пока в Java терпимое количество keywords, но если так пойдет дальше, то точно эволюционирует в JavaScript.
Исходя из этой таблицы, в JS (ECMAScript 5/6) всего 50 зарезервированных ключевых слов, которые нельзя применять в качестве переменных.
А здесь представлены ключевые слова в Java. Их тоже 50. Но! в JS количество слов сокращают, а в Java увеличивают.
:)
Кстати, вот ссылка на контекстно-зависимые ключевые слова в C#: docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/contextual-keywords
Список интересен тем, что все новые ключевые слова, добавленные в язык после версии 2.0, являются контекстно-зависимыми. (Не совсем ясно, почему в списке нет select, from и where.)
Java идёт тем же путём: чем меньше ключевых слов, тем лучше. Следующий этап — добавление "@" к лексемам для обозначения того, что это не ключевое слово (как это сделано в C#). Надеюсь, до этого не дойдёт, но извечный джавовый мем — называть переменные «clazz», из-за того, что слово «class» зарезервировано — тоже не круто.
Хотя var можно использовать для ухудшения кодаЧто? (:
var item = GetItem();
не даёт нам точно узнать какой именно тип скрывается под var. Это не страшно при работе в IDE, но при просмотре, к примеру, через браузер в гите уже не узнаешь простым наведением.
Это может быть не сильно болезненно только лишь разработчику, который недавно написал этот код и его мозг закэшировал типы данных переменных. Читателю кода со стороны это никак не может быть удобно, ибо требует существенно больших усилий со стороны мозга.
Есть всего несколько случае, когда var имеет смысл:
1. Анонимные типы
2. Очень длинное название типа/здоровенный дженерик в результате LINQ
3. Длинное название типа/здоровенный дженерик и его очевидная инициализация в правой части выражения с помощью конструктора
Все остальное вида:
1. for (var i = 0; ...) { };
2. var documents = repo.GetDocuments()
3. var number = «superNumber»;
4. var mySuperNumber = a / b + c;
порождает лично у меня адскую попоболь и ручки так и тянутся к решарперу, дабы массово разыменовать эти долбаные varы.
Я кончил и закурил.
P.S. Тут я про C#, но вас тоже это ждет.
1. for (var i = 0; ...) { };
Настолько частый паттерн, что я не представляю, как на нем можно спотыкаться при чтении.
2. var documents = repo.GetDocuments()
Знание типа не имеет никакой ценности без знания контракта. Тут, возвращаясь к вашим словам немного выше, для получения какого-то эффекта от знания типа, потребуется закэшировать контракт.
В случае, если дело происходит в IDE — вообще без разницы. А с var читается легче.
3. var number = «superNumber»;
Проблема не в var, а в именовании переменных. var в этом плане даже дисциплинирует, я бы сказал. Приходится переменные называть понятно.
var mySuperNumber = a / b + c;
decimal mySuperNumber = a / b + c; — намного хуже, по-моему. Есть вероятность ошибиться получить неявное приведение типов, int к decimal, например, и дальше быть уверенным, что все ок.
Читателю кода со стороны это никак не может быть удобно, ибо требует существенно больших усилий со стороны мозга.
Мне код с var читать удобнее. Если речь про то, чтоб вникнуть в проект, то код читается все равно в IDE, и код, который короче, читается лучше.
Я бы сказал, что есть всего несколько случае, когда var не имеет смысла:
1. На stackoverflow, когда тип невозможно вывести из приведенного кода.
2. В документации при том же условии.
Настолько частый паттерн, что я не представляю, как на нем можно спотыкаться при чтении.
Частый паттерн для меня for(int i = 0; ..). Чем тут var лучше int? Компактнее, наверное?
Знание типа не имеет никакой ценности без знания контракта. Тут, возвращаясь к вашим словам немного выше, для получения какого-то эффекта от знания типа, потребуется закэшировать контракт.
В случае, если дело происходит в IDE — вообще без разницы. А с var читается легче.
Если вам без разницы Document[], List или вообще IEnumarable, то это печально. Мне лично до определенного момент все равно как получили коллекцию, но мне всегда важно какая именно это коллекция.
Прочее вообще комментировать смысла не вижу.
Частый паттерн для меня for(int i = 0; ..). Чем тут var лучше int? Компактнее, наверное?
Привычнее на уровне мышечной памяти, только и всего. var я набираю намного чаще, чем int. А о том, чтобы вывести int, пусть позаботится компилятор.
Если вам без разницы Document[], List или вообще IEnumarable, то это печально. Мне лично до определенного момент все равно как получили коллекцию, но мне всегда важно какая именно это коллекция.
Все это было бы здорово, если бы это было на самом деле так. Ниже вы упоминали типы и структуры данных. Вот смотрите:
IEnumerable numbers = new[] {1, 2, 3};
Создание массива можно спрятать за вызов функции для большего драматизма. Какая именно это коллекция в вашем понимании? Какая сложность применимых к ней операций? Никакой информации об этом мы не получили, а написать и прочитать объявление пришлось. О том, что это коллекция, и так понятно из имени переменной. Понятно, вы так не пишете. Наверняка, никто так не пишет, но код меняется со временем, и расставлять себе ловушки на будущее я считаю плохой идеей. Пусть за вывод типов беспокоится компилятор, за подсказки — IDE. Они с этим хорошо справляются, лучше меня.
Знание типа не имеет никакой ценности без знания контракта.
Кстати, рассказать как увидеть контракт, когда не используется var?
Document[] documents = repo.GetDocuments();
внеееезапно контракт:
Document[] IDocumentRepository.GetDocuments();
а вот c var вы вообще не поймете что там за контракт. Пока у IDE не спросите, конечно.
Просто так слово Document
перед именем переменной не так уж много информации добавляет.
В проекте может быть несколько разных классов с именем Document, лежащие в разных package. Давайте тогда писать org.mycompany.documents.Document
, чтобы уж точно все знать, не пользуясь подсказками IDE.
Точно так же, вы или доверяете транслятору и способностям кодера оценить тип выражения, или не доверяете. Если не доверяете, вы не будете использовать автовывод (var в Java, C#, auto в C++...) Если доверяете — вам автовывод не помешает. Вы, как видно, не доверяете. Ну, тоже вариант. Но хотелось бы понять причины такого недоверия.
Я в общем-то тоже в основном не доверяю :), но auto (C++) в итераторах — очевидно удобная возможность, не мешающая правильному использованию. Здесь будет что-то подобное.
А заабьюзить можно что угодно. Самый дурной вариант, мне кажется, это автосужение множества значений — например, при var автовывод дал int, а затем ему пытаются присвоить float и не предполагают возможность усечения до целого. Вот такое должно быть явно прописано в списке подводных камней.
Да неужели! К 10ой версии-то! Глядишь станет приятно читать эти полотна.
Оракл решил не тратить время на тестирование под 32bit платформы.
При желании можно собрать себе OpenJDK под 32bit и получить почти то же самое. Изменений, блокирующих работу на 32bit в Java нет. Только отсутствие официальной поддержки.
PS: Когда Java 9 только релизнулась, на офф. сайте была 32bit версия, но её быстро выпилили и сказали, что выложили по ошибке :)
А еще, возможно, это будет не «Java 10». А что-то типа «Java 18.3» (год и месяц).
В Java же сознательно отказались от type inference. Ещё в версии 1.3 обсуждалось bugs.java.com/bugdatabase/view_bug.do?bug_id=4459053
Humans benefit from the redundancy of the type declaration in two ways.
First, the redundant type serves as valuable documentation — readers do
not have to search for the declaration of getMap() to find out what type
it returns.
Second, the redundancy allows the programmer to declare the intended type,
and thereby benefit from a cross check performed by the compiler.
Персонально, он не нужен. Пусть программист потратит лишние 3 секунды и напишет тип один раз, а я (и другие) зато смогу потом легко понять читая код 100500 раз после этого, что же вернула функция doFoo().
Но, к сожалению, приходится идти за модой, чтобы не отставать в конкурентной борьбе. Чтобы новые проекты начинали именно на яве, чтобы в нее инвестировали деньги, нужно привлекать побольше новичков. А новички хотят, чтобы было меньше букв.
IEnumerable<string> result = null;
if (someCondition)
{
result = new List<string>();
// логика по добавлению элементов.
}
else
{
result = Enumerable.Empty<string>();
}
Или вот ещё проблема с тернарной записью:
int? result = someCondition ? 42 : null;
Теоретически, компилятор способен сам вывести тип переменной result, но сейчас этого нет. Думаю, когда-нибудь добавят и это (на сколько знаю, в C#8 проблему с Nullable побороли).
Вообще, вывод типов — это ключевая фича всех функциональных языков, а C# и Java к ним постепенно приближаются. В последнем Шарпе уже можно использовать кортежи в качестве возвращаемых значений методов, а в перспективе, думаю, в некоторых случаях можно будет вообще не указывать тип возвращаемых значений или писать «var».
Есть ещё такой трюк: var result = default(IEnumerable<string>);
У нас в команде принято использовать var везде, где это возможно, даже для примитивных типов.
А, к примеру,
var number = 1L;
всегда ли удобнее и читаемее, чем?:
long number = 1;
И как быть, когда некоторая функция возвращает экземпляр некоторого типа, но мы сознательно хотим редуцировать тип возвращенного значения до базового типа или интерфейса, реализованного в типе возвращаемого значения?
Здесь тип придется указать явно, т.к. var сразу выведет точный тип.
var result = SomeFunc(); // var == SomeResult
или?:
ISomeResult result = SomeFunc();
А такой код
IEnumerable<string> result = null;
if (someCondition)
{
result = new List<string>();
// логика по добавлению элементов.
}
else
{
result = Enumerable.Empty<string>();
}
плох не тем, что в нем приходится явно указывать тип переменной.
кстати, инициализировать null его не нужно, т.к. есть else;
и инициализация null'ом выдает перестраховку на случай отсутствия else, вытекающую их незнания базовых свойств языка,
либо неаккуратность, когда else не было, потом добавили, а лишнее ненужное присвоение null не убрали.
Этот код сразу напрашивается на вынесение в метод (или в локальную функцию, если методу при наполнении нужен контекст не класса, а основного метода):
IEnumerable<string> GetSomeItems(bool someCondition)
{
if (!someCondition)
return null;
var result = new List<string>();
// Adding items
return result;
}
Вынесли из основного метода простыни с if/else, сразу видна обработка "плохого" случая, и т.д.
Плюс, код легче дорабатывается — проще заменить дефолтное значение результат по сравнению с вариантом без отдельного метода:
IEnumerable<string> GetSomeItems(bool someCondition)
{
if (!someCondition)
return Enumerable.Empty<string>();
var result = new List<string>();
// Adding items
return result;
}
Вызвали метод.
Сохранили данные в переменной.
Если зависать над каждой запятой, то это будет не чтением того, что код делает, а проверкой правописания.
… Вы про использование var или простыни if-then-else (неужто последние легко читаются)?
А что до запятых — программный код формален, и выполняется ровно так, как написано, поэтому, проверять "запятые" таки требуется.
На примере с var:
Допустим, вам нужна была именно long-переменная, но, не обратив внимания на "запятые", вы написали "var number = 1;" вместо "var number = 1L;".
Такой код через ревью, скорее всего пройдет на ура, и хорошо, если мы поймаете исключение во время разработки и проверки переполнение целочисленной переменной.
Но, скорее всего, ошибка проявится уже в продакшене и будет плавающей и труднодиагностируемой, и не будет приводить к непосредственному исключению:
- проверка переполнения в проде отключена, в переменной будут неверные данные, они пойдут куда то дальше, еще не так плохо, если где-то дальше приведут к программному исключению до окончания основного алгоритма;
- а ведь может все без исключений отработать и привести к неверным результатам, когда принимается решение в офлайне на основе полученных от программы данных (а если это медицинская или АСУ ТП система?).
11
versus 1l
). Да и в целом числовые суффиксные литералы, на мой взгляд, довольно кривое решение. Для каких-то типов они есть (m, f, d, l, ul), а для каких-то забыли придумать (byte, short, ushort). Странно. Но если речь идёт о bool, string и char, то я всё же предпочитаю использовать var. Зачем писать «bool b = true;», если и так очевидно, что это bool?
Что касается моего примера с if-else — да, result не надо инициализировать null-ом. Писал без IDE, пропустил этот момент. Но выносить такое в отдельный метод тоже не всегда правильно, так как это засоряет private scope класса. Вложенные функции решают эту проблему, но их пока не везде можно использовать.
Но тогда платформо-независимость имеет ограничения, т.к. язык предназначен для железа с определенным кругом возможностей.
кстати, по аналогии с выше предложенной конструкцией:
var result = default(IEnumerable<string>);
можно писать так:
var number = (long)1;
и
var number = (byte)1;
Так и единообразно var везде пишем, и явно видим приведение к long,
а в случае byte так и вовсе это единственный способ использовать var за неимением суффикса.
Вот только здесь мы сталкиваемся с code style: сложившейся практики именно так писать не просматривается.
Дальше больше: если мы захотим написать «var y = (double)1000000000000000000000000000;», то будет синтаксическая ошибка. Но если добавить литеру «d» и убрать каст (var y = 1000000000000000000000000000d;), то всё скомпилируется, и типом ожидаемо будет double.
Так что, отказаться от использования суффиксов не получится пока.
Дальше начитаются варианты других типов в зависимости от диапазон, точки, знака и тд.
Получается не очень то и удобно — как мы обсудили в этой ветке, не получается выработать какой-то единый способ присвоения числовых переменных.
И как быть, когда некоторая функция возвращает экземпляр некоторого типа, но мы сознательно хотим редуцировать тип возвращенного значения до базового типа или интерфейса, реализованного в типе возвращаемого значения?
В принципе, это не такая уж и сложная задача для компилятора — вывести наиболее конкретный обобщённый тип, необходимый в данном контексте. TypeScript успешно с этим справляется. Почему это до сих пор не реализовано в C# — не понятно. Тем более не понятно, почему это не добавили в Java, учитывая, что var ещё только в проекте и можно учесть все минусы существующих языков.
Например, вместо
string[] names = GetNames();
я пишу
var names = GetNames();
Сейчас компилятор сразу «знает», что var это string[], IntelliSense может предлагать мне методы этого типа при обращении к names, и т.д.
Фактически, это даже не вывод типа, и даже не сахар — тип переменной уже определен в правой части выражения, сразу в момент написания этого выражения.
Если же исходить из того, что компилятор должен автоматически максимально редуцировать тип к базовому типу или интефвейсу при выводе, то получается, в зависимости от того, к каким методам names я обратился, компилятор должен выводить string[], IList<string>, ICollection<string>, IReadOnlyList<string>, IReadOnlyCollection<string>, IEnumerable<string>?
На мой взгляд, такой вывод типа будет неочевиден (без всплывающей подсказки при наведении на переменную мы вообще не будем знать тип, если только мысленно не сделаем тот же проход по коду, что и компилятор),
да и как IntelliSense сможет предлагать методы names, если тип переменной еще не определен — всегда предлагать, что есть в «максимальном» типе?
Также здесь возможны дополнительные сложности, связанные с explicit-реализацией интерфейсов и со множественным наследованием интерфейсов.
var names = GetNames(); // GetNames возвращает string[]
if (dropBob)
{
names = names.Where(name => name != "Bob");
}
то компилятор может понять, что names должен быть общим типом IEnumerable<string>. Не надо анализировать вызываемые методы. Достаточно посмотреть, значения каких типов присваиваются переменной.
Естественно, в этом случае свойство Length будет уже недоступно после if-а, т.к. тип может поменяться.
Да, идея понятна.
В общем случае это объявить var даже без первичной инициализации, главное, чтобы потом во всех ветках переменная была проинициализирована, а далее компилятор выведет тип — ближайшего общего предка (с выводом интерфейса вместо типа будут трудности — неясно, чему отдавать предпочтение — интерфейсу или типу, плюс может быть несколько общих интерфейсов).
Идея хорошая, только тогда нужно быть готовым и к таким образчикам:
var value;
if (someCondition)
{
value = 0;
}
else
{
value = "123";
}
Где var будет выведен как object.
И еще будут появляться проверки и приведение value к типу.
Такие и сейчас встречается, но object нужно написать осознанно, а var будет дополнительно провоцировать.
Впрочем, кажется, этот случай уже описан в статье.
P.S. Прошу прощения за долгий ответ. Читал ветку с имейлов, не мог ответить сразу, забыл ответить потом.
Так такие вещи как
var url = new URL("...");
как раз таки и не рекомендуется писать, смысла же нет никакого.
А вот в случае
Dictionary<string, Dictionary<string, Func<int, float, string>>>
я лучше напишу var
double Gizmo(double arg)
{
double arg2 = arg * arg;
return Math.Sign(arg2) * Math.Truncate(arg2);
}
И вдруг мы решили вместо double использовать decimal. Если бы мы сразу написали var arg2, то рефакторинг был бы минимальным — надо было бы только переписать сигнатуру метода. А теперь придётся лезть в тело метода и изменять все double на decimal. В этом примере всё не так страшно — всего одно изменение, но если это был бы какой-то сложный расчёт, пришлось бы помучиться.
С другой стороны, возможно неудобства будет меньше, если тщательно именовать переменные, что в принцыпе в обоих случаях отличная практика.
Осторожно! Вы в одном шаге от венгерской нотации.
На мой взгляд, пример хороший — показывает, как можно писать "обобщенный" код, а конкретные типы менять при необходимости.
С другой стороны, неоднозначный — с учетом специфики типов с фиксированной и плавающей точкой в реальном проекте все равно было бы много кода, завязанного на именно специфику типов (диапазон, точность, особенности сравнения — через эпсилон или непосредственное, и т.д.).
И, возможно, при смене типа, может оказаться, что как раз хорошо, если в случае отсутствия var компилятор будет ругаться на неправильно указанный тип переменной для запоминания возвращенного функцией значения, чтобы в каждом месте проверить, а что именно делается с полученным результатом.
Наоборот хорошо, везде сходите, посмотрите, не отломалось ли чего. В разных местах нужно по-разному поступить, где-то заменить на decimal, где-то сразу в double сконвертировать.
Я переучивался на С# после 8 лет Java и по началу конечно был возмущен var'ами, мол, не видно же какой тип! Все это вопрос привычки, тем более сейчас, когда многие языки имеют аналогичный функционал, даже JS/TS.
Осталось завезти свойства, перегрузку операторов, делегаты c событиями… Делагаты очень хорошая вещь, иначе все превращается в явный Observer с ISomethingListener. С лямбдами лучше, но не идеально.
… async/await, кортежи, Pattern matching (хотя мне не нравится его реализация в C# 7)...
Да C# до Kotlin будет путь побольше, чем Java 8/9 до C#.
В Kotlin есть Null Safety, им/мутабельность переменных (var/val), полноценные свойства (back-field, видимый только в геттере/сеттере, + механизм делегированных свойств), when (нормально юзабельный и читабельный, в отличие от switch/case/break), более выразительный механизм расширения методов через лямбда-стратегии (см. пример, как вызывается extension use для AutoClosable), более адекватные умолчания для областей видимости, и много другого.
Многое из этого не может быть реализовано в C# с сохранением обратной совместимости (а без нее это будет уже не C#, а аналог Kotlin для .NET).
А чтобы подтянуть Java до текущего состояния C#, нужно не так много — как тут уже писали — свойства как таковые, делегаты+события, async/await, и немного по мелочи.
Если ставить задачу дотянуть Java до C#, а не до Kotlin, и не ломать обратную совместимость, то это все реально — языки и платформы очень похожи, нужно доделать недостающее.
Компилятор отображает все задействованные типы и помещает их в файлы классов, как если бы вы их вводили сами.
Например, вот результат декомпиляции IntelliJ (фактически Fernflower) файла класса с примером URL:
…
Это байт в байт тот же результат, как если бы я сам объявил типы.
Результат работы декомпилятора не может быть доказательством «помещения» чего-либо куда бы то ни было.
Для исполнения программы информация о типе локальных переменных в class-файле не нужа и потому если и присутствует, то только в отладочной информации. Которой может и не быть.
Fernflower выводит типы локальных переменных сам, не полагаясь на наличие отладочной информации. Хотя и использует её в ряде случаев.
Простейший пример:
public class Test {
public static void main(String... args) {
Object greeting = "Habrahabr, world!";
System.out.println(greeting);
}
}
Декомпилятор в IDEA будет уверен, что декларировали мы переменную с типом String
:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Test {
public Test() {
}
public static void main(String... var0) {
String var1 = "Habrahabr, world!";
System.out.println(var1);
}
}
Помимо локальных переменных, например в полях и сигнатурах методов, var не может применяться.
В предложении слов порядок, Йода магистр одобрил бы.
Ваш код компилятор не соберет.
Вас не смущает, что чуть ниже идёт листинг декомпилированного class-файла, который по вашему мнению даже и не скомпилируется?
String в декомпилированном примере, скорее всего явно выводит javac. Fernflower тут вообще ни при чём.
Постойте, но ведь в предыдущем пункте вы утверждали, что «компилятор не соберет», а теперь утверждаете, что не только соберёт, но ещё и «выведет».
Вот что показывает javap:
Classfile /C:/Temp/Test.class
Last modified 08.01.2018; size 425 bytes
MD5 checksum d5aeccb9b28d3f897d76c7174e7e18cf
Compiled from "Test.java"
public class Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = String #16 // Habrahabr, world!
#3 = Fieldref #17.#18 // java/lang/System.out:Ljava/io/PrintStream;
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/Object;)V
#5 = Class #21 // Test
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 Test.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Utf8 Habrahabr, world!
#17 = Class #23 // java/lang/System
#18 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/Object;)V
#21 = Utf8 Test
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/Object;)V
{
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String...);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
Code:
stack=2, locals=2, args_size=1
0: ldc #2 // String Habrahabr, world!
2: astore_1
3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_1
7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
10: return
LineNumberTable:
line 5: 0
line 6: 3
line 8: 10
}
SourceFile: "Test.java"
Покажите мне, пожалуйста, где здесь «явный вывод» или хотя бы хоть какое-нибудь упоминание типов локальных переменных.
var в JS — это как dynamic в C# — тип определяется в рантайме
var в C# и Java тип определяется при компиляции.
Так что я повторю еще раз: ничего общего нет между var в C#/Java и var в JS
Это моя любимая тема: на интервью заявляю матёрому джависту, что не вижу разницы между Java и JavaScript и смотрю как он медленно закипает.
Лет через пять, в 2023 году, javascript заменит всё и вся.
Насчёт определения типов — определять типы переменных должен компилятор и компьютер, а не человек.
Только теперь и тут будут споры, нужно ли/можно ли его применять.
Применять, видимо, имеет смысл и придется, но аккуратно, находя баланс между краткостью и читаемостью.
Java по своему синтаксису, объектной/функциональной модели, и модели виртуальной машины ближе всего к C#, и поэтому было бы интересно сравнить ситуацию с C#.
И тут возникает интересное.
По одной из версий, в C# var появился в первую очередь для поддержки анонимных типов, экземпляры которых возвращаются LINQ-запросами (а также экземпляры анонимных типов можно конструировать вручную), чтобы можно было обращаться с полям этих типов в статике.
А если var позволяет вывести в статике анонимный тип, то тем более позволяет вывести и известный тип.
Вот только использование анонимных типов в C# вроде так и не стало каким то уж очень массовым, а сам var стал применяться очень часто, к месту и не к месту.
Возникают интересные вопросы.
Что делать в целом с проблемой var. Если в том же C# за много лет и череду новых версий .NET не устоялся единый взгляд на методологию применения var и код пестрит разнобоем, то, видимо, это же ждет и Java.
UPDATE:
Анонимные типы (классы) в Java есть.
Тогда тем более интересно, применим ли к ним var, появившийся в Java.
var foo = new ArrayList<Long>() {
public int tenSize() {
return 10 * size();
}
};
С возможностью обратиться потом к методу foo.tenSize(). Я пролистал JEP 286, там написано что переменной var могут быть присвоены значения и анонимного класса, и пересечения типов. Но про доступ к полям и методам там ничего не сказано. Может кто-то в курсе, как с этим обстоят дела?Декларируете именованный внутренний класс-наследник ArrayList<Long>
и можете делать всё то же самое. Синтаксический сахар в чистом виде.
InternationalCustomerOrderProcessor<AnonymousCustomer, SimpleOrder<Book>> orderProcessor = createInternationalOrderProcessor(customer, order);
Вместо этого можно было бы использовать
MyProcessor orderProcessor = createInternationalOrderProcessor(customer, order);
Было бы неплохое дополнение к var в тех случаях, когда тип важен при чтении кода и его некак быстро вывести из правой части глазами.
using OrderProcessor = InternationalCustomerOrderProcessor<AnonymousCustomer, SimpleOrder<Book>>;
как замена typedef. Потом использовать
OrderProcessor orderProcessor = createInternationalOrderProcessor(customer, order);
Но. Действует только в одном файле.
URL codefx = new URL("http://codefx.org");
реализовать вот такой синтаксис
URL codefx("http://codefx.org");
И избыточность устранена, и новых ключевых слов не надо добавлять.
А вот в таком коде заменять тип на var — это, на мой взгляд, преступление.
URLConnection connection = codefx.openConnection();
Тот, кто в этом коде потом будет разбираться, спасибо не скажет.
var connection = codefx.openConnection();
Мне постоянно приходится разбираться с тоннами говнокода на всех языках кроме может быть фортрана и имя типа это последнее, что мне поможет, потому что трудно помнить все эти типа в джаве, шарпе, питоне, objective-c, js и т.п. В случае "openConnection" я просто делаю "go to definition" и смотрю что оно там возвращает. И да, я двумя руками за "var" в джаве — вся эта многословная лапша нисколько не помогает, а вот читать становится сложнее.
Т.е. это эквивалент auto
из C++11?
Ну java это не про краткость.
Я еще не нашел ни одного человека который мог бы мне объяснить чем
parent.getChildren().get(2);
Лучше чем
parent.Children[2];
Очевидно, чем лучше. В джаве вторая конструкция — это дереференс поля и чтение элемента массива. Всегда, в любом контексте, вне зависимости от предыдущих объявлений. Это точно не обращение к базе и не 20 хттп-запросов под капотом. Понимание того, что делает программа, дорого стоит. А в языках с перегрузкой операторов вам придётся изучать контекст и разбираться, что там происходит на самом деле.
Как по мне — это ничего не меняет. Просто в Java будет почти всегда использоваться 2 вызова метода — и вам таки придётся изучать контекст.
По похожей причине вместо parent.Children нужно использовать функцию getChildren() — ее реализацию впоследствии можно изменить, в отличие от поля Children.
То есть в целом, эта многословность — из-за отсутствия перегрузки операторов и геттеров-сеттеров свойств в Java.
Пример кода:

// ничто из этого не работает
var ints = {0, 1, 2};
var appendSpace = a -> a + " ";
var compareString = String::compareTo
То есть, ничто из самого интересного не работает… И фактически, все на что годен этот новый var — немного сократить запись в некоторых случаях. И в правильной / чуть более сложной системе оно и вовсе не нужно:
var getInstance() { // var не заработает
...
}
ClassName getInstance() {
// никаких ClassName result = new ClassName();
return ClassName.builder()
.setter1(value1)
.setter2(value2)
.build();
}
var x = new ChildClass(); // x не выведется в базовый тип или интерфейс, посему:
@Autowired
private final var x; // заработает?..
private final var y; // пригодится ли?
@Autowired
public Constructor(ClassName y) {
this.y = y; // и толку было объявлять его как var?..
}
Естественно, не работает. Было бы глупо, если бы работало. Разве это не очевидно?
Любят вот люди потребовать фичу, показав простой пример. Хотя даже тут неочевидно — это int[], long[], double[], Integer[], Number[], Serializable[] или Object[]. Все эти типы подходят. И между ними нет линейного порядка по операции widening как в других случаях с var. А {1, null} — это что? А {1, 1L}? long[] или Number[]? Как насчёт {null, null, null}? Напишите-ка главу спецификации, по которой можно будет однозначно установить тип массива по инициализатору. Желательно, чтобы во всех правилах была какая-то логика.
www.jcp.org/en/jsr/overview
Я пишу на котлине и там var & val показали себя очень хорошо. Настолько уже привык) Правда там можно и не инициализировать переменную или использовать ключевое слово lateinit что тоже на практике оказалось очень удобным. Вообще после джавы котлин вызывает одни положительные эмоции)
Вывод: хорошо когда есть большой и хороший выбор языков, где каждый может найти свой любимый язык))
P.S На скале привык к val/var, что не представляю, как эти километровые полотна писать — адская мешанина.
Все чаще замечаю, что при правке многострочных stream-операций с лямбдами периодически приходится чесать репу: стоит немного что-то поправить и IDEA радостно подсвечивает все 10 строчек красным — поскольку информации о типах нигде нет и все выводится автоматически, то при потере малейшей части информации вывести тип не представляется возможным, а в явном виде он не указан.
Но лямбды реально позволяют избавится от лишних строчек при введении полноразмерных анонимных классов. А вот с var выгода что-то мне не кажется столь очевидной, поэтому никакого праздника не ожидаю.
Наверное, не ради этого удовольствия выпустили 10-ю версию.
var вместо типа ссылки. работает также как и описано в статье.
stackoverflow.com/questions/36859312/lombok-not-working-in-a-netbeans-project
Например:
var qdr = x -> x*x;
Но для типизированных переменных делать такое крайне неправильно (ИМХО).
Когда переменная var a;
А что она на самом деле является, крайне не удобно искать.
начиная с Java 8 у нас есть концепция эффективного final
Тут по подробнее пожалуйста...
После выпуска новой редакции Java 8 вместе с lambda-выражениями в Java также было добавлено новое понятие effectively final.
…
Любая переменная, находящаяся снаружи lambda-выражения и проинициализированная единожды в локальной области видимости(в методе), является effectively final переменной и может быть использована в lambda-выражениях. Effectively final переменная очень похожа на final переменную, с единственным отличием: нет необходимости использовать модификатор final при объявлении переменной.
Например, при использовании LINQ на языке C#:
int[] arr = new int[] { 1, 2, 3, 4, 5 };
var result = arr.Select(x => new { x, mod2 = x % 2 }).ToArray();
Так как при проецирование используется анонимный класс, то на момент написания кода нет возможности определить какого типа вернется объект. Это будет массив классов, но имена этим классам будут присвоены лишь на момент компиляции.
Не ужели так много людей пишет в блокноте? Ведь IDE, все проблемы «многословности» легко нивелирует, делая их мнимыми проблемами.
В связи с этим вопрос господа профессионалы, почему вы так не любите очевидность?
Справедливости ради стоит сказать, что в строке var order = new Order() как есть 100%-я уверенность, что order это Order.
А вот в других случаях это м.б. уже неочевидно, отчего и идут дискуссии насчет var.
Кстати, есть случаи, когда явное указание типа как раз может ввести в заблуждение.
Сорри, но я приведу пример из C#.
Вот в таком коде:
foreach (SomeType item in items) // где items - коллекция элементов SomeTypes
item "не совсем" — SomeType.
Дело в том, что если коллекция имеет только нетипизированный (non generic) инумератор, то на каждой итерации foreach мы получаем object, который с помощью объявления SomeType item приводится в рантайме к SomeType.
Это дает как потери производительности, так и в общем случае может привести к InvalidCastException.
В любом случае, когда смотришь на этот код, думаешь одно, а может происходить совсем другое. А казалось бы, куда проще — никакого var — один только SomeType item.
А если написать
foreach (var item in items)
то хотя бы в IDE всплывающая подсказка сразу покажет, items это object или SomeType.
А ведь в C# можно даже такой компилирующийся код написать, дающий исключение в рантайме:
var items = new object[] { null, 0, "123" };
foreach (string s in items)
{
}
Где items — изначально нетипизированная коллекция.
Т.е., явное указание типа в общем случае не 100%-я гарантия.
Такой код более безопасно писать так:
foreach (var s in items.Cast<SomeType>())
здесь сразу "читаемо", что происходит приведение типа (и возможное исключение ожидаемо), и var уместен, т.к. тип уже явно виден.
Или так, если мы хотим пропустить элементы неподходящего типа:
foreach (var s in items.OfType<SomeType>())
Полагаю, что в Java тоже достаточно неоднозначных при чтении моментов (были здесь уже статьи на тему, что то вроде "10 кейсов, знаешь ли ты, как поведет себя Java"), так что сам по себе var — не проблема, тем более, что это и выводом типа не назовешь, скорее, сокращение записи, т.к. тип не выводится, а известен из правой части выражения.
Проблему я вижу именно в том что бы использовать для такого:
var codefx = new URL("http://codefx.org");
var connection = codefx.openConnection();
var reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
В данном примере это часто встречаемая конструкция, и тот кто её уже использовал легко поймут, что тут проходит.
Но а если это будет, что-то более редкое. Что-то, что будет казаться автору понятным и легким, а тому кто поддерживает код (в виду обстоятельств) не очевидным, т.к. он не использовал такой конструкции. А потом ещё на это мы наложим проблему правильного именования переменных.
В итоге мы будем иметь красиво форматированный код, но с не очевидным описанием команд.
Другой пример:
Вот пришёл новичок в мир Java уже после релиза Java 10. Разумеется более быстро находимые статьи будут про новые фичи. Почитав он думает — «Ага, зачем мне писать по старому, по новому меньше печатать». (хотя я опять повторюсь, с IDE проблема надумана)
И вот реализует компонент который использует множественное наследование. И по привычке он пишет:
var вася = new Вася();
вместо,
Человек вася = new Вася();
а потом допустим положил в массив.
Какой будет тип у вася, Человек или Вася?
P.S. пост не нацелен оскорбить Василиев, это просто пример.
P.P.S. Может мне не хватает знаний, рассуждать объективно на эту тему, но мне не нравиться, что в угоду не самой объективной проблеме, придумали двухстороний меч.
P.P.P.S. Если проблема кроется у большинства в:
Таким образом, var сразу приносит нам недостаток читаемости и должен компенсировать это. Один из способов — выравнивание имен переменных:
// с явными типами
No no = new No();
AmountIncrease<BigDecimal> more = new BigDecimalAmountIncrease();
HorizontalConnection<LinePosition, LinePosition> jumping =
new HorizontalLinePositionConnection();
Variable variable = new Constant(5);
List<String> names = List.of("Max", "Maria");
// с выведенными типами
var no = new No();
var more = new BigDecimalAmountIncrease();
var jumping = new HorizontalLinePositionConnection();
var variable = new Constant(5);
var names = List.of("Max", "Maria");
Имена типов важны, но имена переменных могут быть важнее. Типы описывают общую концепцию в контексте всей экосистемы Java (для классов JDK), общий вариант использования (библиотека или фреймворк) или бизнес-домен (приложение) и, следовательно, всегда будут иметь общие имена. Переменные, с другой стороны, определены в конкретном и очень малом контексте, в котором их имя может быть очень точным.
С var имена переменных выходят на первый план и выделяются так, как раньше этого не делали, особенно если подсветка кода отмечает ключевое слово и, таким образом, позволяет инстинктивно игнорировать его. Я какое-то время проводил час или два в день, читая Kotlin, и я тут же привык к этому. Это может значительно улучшить читаемость.
Как говорилось выше, другое улучшение читаемости может происходить из-за того, что объявлено больше промежуточных переменных, поскольку это связано со снижением издержек при записи и чтении.
То почему бы не придумать иное форматирование стиля например, \n\t после объявления типа переменной. Тогда всё имена переменных будут тоже в колонку.
Object
obj = new Object();
SuperMegaGigaCoolType
cupCoffee = new SuperMegaGigaCoolType();

Вар позволяет выведение типов, а значит и миксины в анонимных классах… а значит надо поковырять. Статья бредовая и кроме сахарка ничего не описала.
Первый контакт с «var» в Java 10