Pull to refresh

Comments 57

Не хватает заголовка как на N+1.

Сложность - 9/10

А ещё есть пример Comparator.comparing(String::toLowerCase), который почему-то не компилируется в Java

Comparator.comparing требует либо ToIntFunction (что происходит при String::length), либо Function с возвратом чего-то сравнимого: <T, U extends Comparable<U>>comparing(Function<? super T, ? extends U> keyExtractor)

Как понятно из String::toLowerCase, он возвращает String, который, кажется, не является Comparable

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

То что вы хотите можно получить либо так
Comparator.comparing(s->s.toString().toLowerCase()) //из Object
либо так
Comparator<String> comparator=Comparator.comparing(String::toLowerCase); //из String

В лоб (без контекста, т.е. в смысле без самого объекта) из Comparator.comparing(String::toLowerCase) компилятору ничего вывести нельзя, т.к. самих этих toLowerCase - много, и непонятно на какой именно вы ссылаетесь. А если ::length или ::hashcode или ::toString - то тут такой неоднозначности не возникает, у этих методов нет перегрузки и компилятор понимает объект какого типа имелся ввиду, и какой из методов этого объекта нужен для извлечения признака (keyExtractor).

Но вообще представить себе где может понадобиться такой код Comparator.comparing(что-угодно) без присваивания или передачи куда-либо довольно трудно. Даже наверное невозможно.

Перегрузка тут никак не мешает, потому что к Function<T, U> применима только String.toLowerCase(), но не String.toLowerCase(Locale).

А кто сказал, что я никуда не присваиваю? Если я хочу написать Comparator<String> cmp = Comparator.comparing(String::toLowerCase).reversed(), то работать не будет аналогично.

А вы с какой jdk пробуете скомпилировать этот пример?

Я думаю, что именно без присваивания компилироваться не будет. C .reversed() тоже не будет.

Без .reversed(), но с контекстом - будет. С .reversed(), но в следующем операторе тоже будет (т.к. тип уже выведен).

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

  1. Comparator.comparing(String::toLowerCase) - не компилируется "Reference to 'toLowerCase' is ambiguous, both 'toLowerCase(Locale)' and 'toLowerCase()' match";

  2. Comparator<String> cmp = Comparator.comparing(String::toLowerCase) - компилируется;

  3. Comparator<String> cmp = Comparator.comparing(String::toLowerCase).reversed() - не компилируется, причина как в 1 пункте

Да, спасибо. И так тоже должно скомпилироваться (хоть это и банально):
Comparator<String> comparing = Comparator.comparing(String::toLowerCase);
comparing = comparing.reversed();

Специально же попросил не трогать перегруженные методы, чтобы не открывать портал в ад.

Тьфутынуты. Я уж было подумал человек реально не понимает, что происходит, а вы тут рофлите в междусобойчиках. Нехорошо :D

Поясните, пожалуйста, что имеется в виду под "квалификатором" ?

Мда, про перегруженные методы ещё Мейер писал, что это несовместимо с ООП.

Но создатели java не читатели были :(

Ваш комментарий очень фанатично звучит. Как будто есть единственное тру-ООП, а всё остальное - ересь. На самом-то деле нет никакого правильного ООП, есть удобные и неудобные инструменты в программировании. И не надо думать, что вот деды знали как правильно, а мы все знания растеряли. Прогресс не стоит на месте, мир программирования меняется. Раньше вон визиторы были вполне себе тру-ООП, а теперь этот паттерн практически изжил себя.

Конечно нет никакого тру-ООП.

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

Зато создаёт проблемы- как в выводе типов, так и с null'ами.

А если сделать два метода с параметрами-интерфейсами, а потом сделать единый интерфейс-наследник - то тут сообщения об ошибках вместе с руганью разработчика могут вызвать Ктулху (я сам натыкался на это).

Да, это синтаксический сахар! Вы так говорите будто это что-то плохое. Половина Джавы - синтаксический сахар. Кому не нужен сахар, тот на Лиспе пишет :-) Это весьма полезная возможность, особенно в отсутствии дефолт-параметров. А для конструирования объектов так вообще необходимая, так как у конструкторов нет имени.

Вы так говорите будто это что-то плохое. 

Всё имеет свою цену. Сахар тоже. Когда он приводит к запутанности вывода типов (и это не только компилятор/анализатор путается, люди тоже) - это дорогая цена.

Это весьма полезная возможность, особенно в отсутствии дефолт-параметров

Вот, кстати, лучше б дефолт-параметры были.

А для конструирования объектов так вообще необходимая, так как у конструкторов нет имени.

А кто их запрещал иметь? Ну кроме зацикленности авторов на С++

Когда он приводит к запутанности вывода типов (и это не только компилятор/анализатор путается, люди тоже) - это дорогая цена.

Имхо, современная среда разработки + компилятор сильно облегчают жизнь. Вы хоть раз сталкивались с реальными багами, которые вызваны выводом типов?

Вот, кстати, лучше б дефолт-параметры были.

Мне кажется, вы тут себе же противоречите, ведь параметры по умолчанию по сути делают любой метод с параметром перегруженным, ведь вы можете вызвать его с аргументом или без (тогда подставится параметр по умолчанию). И представьте, насколько это усложнит чтение кода: допустим, что вы видите обращение к некоему методу впервые, и в этот метод не передаётся аргумент. Теперь как понять, объявлен ли этот метод без аргументов, или с аргументом (без его явного указания берётся значение по умолчанию).

Теперь как понять, объявлен ли этот метод без аргументов, или с аргументом (без его явного указания берётся значение по умолчанию).

Среда разработки тут поможет.

А вот с перегрузкой будут большие проблемы.

Среда разработки тут поможет.

А что делать в процессе вычитки кода на том же Гитхабе?

А вот с перегрузкой будут большие проблемы.

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

Дописать Гитхаб. Встроенный веб VsCode это движение в эту сторону.

Если бы всего две. N^2 сигнатур там. И это проблема почти полностью запрещающая обычную перегрузку.

Дописать Гитхаб.

Язык должен быть самодостаточным. Если для понимания написанного нужно пользоваться дополнительными инструментами, то это стрёмный язык, ИМХО.

Любимые всеми var, auto и подобное уже часто сложно читаемы без помогающей среды разработки.

Про более сложные случаи многоуровневых лямбд я и не говорю.

Возможность удобно читать код без помогающей среды разработки уже утрачена и все довольны. Можно продолжать.

Если вы используете var и делаете ваш код нечитаемым -- не делайте так. var нужно использовать там, где тип очевиден.

Запрет var на уровне линтера в pre-commit hook значительно улучшает читаемость.

HashMap<String, List<String>> temporaryMap = new HashMap<>();
var temporaryMap1 = new HashMap<String, List<String>>();

MyObject object = new MyObject();
var object1 = new MyObject();

MethodHandle methodHandle = parseMethodHandle(sourceOfMethodHandle);
var methodHandle1 = parseMethodHandle(sourceOfMethodHandle);

Ну и чем здесь var хуже явного указания типа? Это особенно удобно при очевидном длинном типе.

  • Даже видя место декларации переменной вы в общем случае не можете предсказать тип переменной без помощи IDE или похода к месту декларации метода из правой части выражения.
  • Фактический тип будет совпадать с типом правой части присвоения, что не всегда нужно.
  • Поведение может преподнести сюрпризы: Заменить Object на var: что может пойти не так?

Думаю, достаточно.

Зря вы так. Есть куча очевидных мест где var лучше. Типы бывают очень длинные и при этом очевидные. Зачем читать больше? Линтер глупый и такие места не обнаружит.

Длину идентификаторов можно ограничить тем же линтером и корпоративными style guide.


Зачем читать больше?

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

Длину идентификаторов можно ограничить тем же линтером и корпоративными style guide.

Ой. Вы точно на джаве код пишите? Так такие монстрики иногда бывают...

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

Не надо в уме. Пользуйтесь IDE. Особенно в любом неочевидном случае.

Запретить можно все. Но зачем? В плюсах я могу понять запрет части конструкций. Но в джаве это выглядит очень странно.

Не надо в уме. Пользуйтесь IDE. Особенно в любом неочевидном случае.

Вот пришёл нам pull request в GitLab-e, чем тут IDE поможет?

git checkout ...

Есть pr достаточно большой чтобы было непонятно смотреть его в IDE в любом случае удобнее.

Использование var способствует увеличению числа случаев, когда сделать checkout действительно удобнее. О чём и речь.


Если, к примеру, в одной из последущих версий в язык вкорячат string interpolation, то картина станет ещё хуже. Зато ХХВП можно быстрее делать, да.

var увеличивает, лямбды увеличивают, стримы увеличивают, пропуск дженерик типов увеличивает, да даже рекорды увеличивают.

И что теперь писать на синтаксисе jdk6? Язык развивается, игнорировать развитие и заставлять всех писать как 20 лет назад странно. И нанимать вам с таким подходом сложно будет.

Var не нужно запрещать, как выше написал @MediaNik5

var нужно использовать там, где тип очевиден

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

Возьмем самый что ни на есть учебный перегруженный метод.

Прямо с вики взял и косметически поправил:

double volume(int h, double r) {     return(3.14*r*r*h); }   // volume of a cuboid
long volume(int l, int b, int h) {     return(l*b*h); }

И сразу ой. С параметрами по умолчанию так не сделать.

Авторы любого языка всегда выбирают между дефолтными параметрами и перегрузкой. Перегрузка в общем полезнее. Я с ними согласен.

А в чём проблема назвать их по-разному? У них разная суть - объём цилиндра и объём куба.

То, что второй использует целые числа - просто бред.

Потому что я пошел на википедию в статью о перегрузке функций и взял пример оттуда https://en.wikipedia.org/wiki/Function_overloading

Назвать по разному это и есть запрет перегрузки. Можно, но это будет фундаментальное свойство языка. Которое изменить потом уже нельзя. Никогда. Вы же не хотите очередной Питон3?

Не факт что с таким свойством язык станет лучше.

Авторы любого языка всегда выбирают между дефолтными параметрами и перегрузкой.

Технически, авторам ЯП не обязательно выбирать что-то одно. В Delphi были и параметры по умолчанию и перегрузка.

Технически разрешить можно. Но это порождает кучу неочевидных мест где нельзя. Так даже еще хуже.

 Вы хоть раз сталкивались с реальными багами, которые вызваны выводом типов?

Да. Много раз. А совместно с extension-методами в kotlin это создаёт ещё больше проблем.

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

Нет.

Потому что метод остаётся один. И в наследнике он будет ровно такой же.

Смесь множественного (по факту дефолтных методов) наследования и перегрузки - это плохо.

Да. Много раз.

Не троллинга ради, но можно простенький пример?

Потому что метод остаётся один.

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

Смесь множественного (по факту дефолтных методов) наследования и перегрузки - это плохо

С точки зрения идеологической чистоты ООП - да. В жизни же рядового разраба методы по умолчанию сильно помогают.

Не троллинга ради, но можно простенький пример?

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

И если код написан не в ИДЕ, то такие методы сильно усложняют понимание написанного

Ну не сильно - потому что, опять же, всегда есть ровно один метод и его проще найти.

В жизни же рядового разраба методы по умолчанию сильно помогают.

Вы про перегузку? Можно пример, когда они помогают (т.е. никак нельзя назвать два метода разными именами)?

Вы про перегузку?

Я про случай, когда у интерфейса есть много наследников, но всем им нужен одинаковый для всех метод, например, вычисляющий сложные проценты. Если у интерфейса нет методов по умолчанию, то остаётся либо написание абстрактного класса и наследование от него, что снижает гибкость, либо выносить функционал во внешней класс. Также см. https://stackoverflow.com/a/33721297/12473843

Ничего плохого про методы по-умолчанию не скажу.

Я ругаю только перегрузку методов.

А как без неё ООП сделать? Никак, ИМХО.

Тут путаница в терминах. Я против перегузки: https://ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%B3%D1%80%D1%83%D0%B7%D0%BA%D0%B0_%D0%BF%D1%80%D0%BE%D1%86%D0%B5%D0%B4%D1%83%D1%80_%D0%B8_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B9

Она не нужна для ОПП. Более того, это некоторая замена ООП для процедурных языков типа C (не C++).

Были бы именованные параметры - не было бы большинства с перегрузкой. И шаблон Builder бы не потребовался. И ещё дофига всего. Но нет, Java - она не такая. Хотя на момент её (Java) пелёнок это уже лет пятнадцать как было много где. А ещё я иногда хочу перегружать по возвращаемому результату. Однако хрен там. Там же, где и наследование enum. Зла не хватает. Язык, на котором пишется куча бизнес-логики... и который не очень-то для этого предназначался при разработке, судя по всему

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


Были бы именованные параметры

Все, кому это важно, пишут на питоне.


И шаблон Builder бы не потребовался.

Как бы вам помогло наличие именованных параметров в деле отказа от StringBuilder? :D


Там же, где и наследование enum.

В каком языке такое есть и для чего нужно?


Зла не хватает.

Не пора ли благородному дону в отпуск?

Как бы вам помогло наличие именованных параметров в деле отказа от StringBuilder?

Не этот. https://projectlombok.org/features/Builder + https://projectlombok.org/features/NonNull

Там же, где и наследование enum.

В каком языке такое есть и для чего нужно

Про языки не знаю. Случай примерно следующий - класс X умеет обрабатыать что-то из множества A (т.е. множество - тип параметра функции), его наследник Y - умеет всё из A и ещё пару случаев. Хорошо бы расширить множество A в наследник B добавивив ещё пару вариантов и обрабатывать в Y уже его, чтобы получился статически проверяемый код, но нет. Я знаю, как такое собрать из static final членов того же класса + приватный конструктор, но это как раз из разряда извращений. И не поддерживается языковыми конструкциями

Не пора ли благородному дону в отпуск?

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

PS: Видимо пора коммитить в lombok - не ждать так сказать милостей от природы

Что в этом сезоне модно использовать вместо посетителей?

Издатель-подписчик, например.

Sign up to leave a comment.

Articles