Кто-нибудь может объяснить, почему в Идее отсутствуют кнопки разворачивания окон на весь экран? Неужели никого это не напрягает? 🤔
#IntelliJIDEA
#IntelliJIDEA
Универсальные дженерики уже попали в репозиторий Valhalla. И там стоит релиз Java 18! Естественно, preview. Значит по логике мы сможем писать
#Java18 #Java20
List<int> в Java 20. Поскорей бы.#Java18 #Java20
GitHub
universal type variables: initial prototype · openjdk/valhalla@3e896fa
Reviewed-by: mcimadamore
Где-то с год назад я делал разбор поста из одного популярного Telegram-канала по Java, где объяснил, почему автор неправ. Решил вот опять заглянуть туда. Ничего не изменилось: автор (точнее авторка) продолжает сбивать с толку своих читателей и вдалбливать в голову совершенно вредные и, в данном случае, неправильные вещи. А количество подписчиков с того момента увеличилось на две тысячи, так что стало всё только хуже.
Перед началом чтения моего разбора поста можете прочитать оригинальный пост, хотя это необязательно (и может быть даже опасно 🤡).
Итак, автор начинает с двух тестов. В первом приводится кусок кода и спрашивается, сколько элементов он выведет:
1. for выведет 2 элемента, forEach выведет 2
2. for выведет 2 элемента, forEach выведет 3
3. for выведет 3 элемента, forEach выведет 2
4. for выведет 3 элемента, forEach выведет 3
Комбинации 2 и 4, например, можно вполне себе легко воспроизвести: на Java 11.0.12 for выведет 20, 30, а forEach 20, 40, 30. Но если взять другие числа, например, 10, 20, 30 вместо 20, 30, 40, то оба выведут одно и то же: 20, 10, 30.
Таким образом, автор, пытаясь объяснить различия между устройством for и forEach, выбрала крайне неудачный (и некорректный пример).
Теперь давайте перейдём к самому посту. Пойдём прямо по тексту:
for использует для обхода итератор, а forEach - траверс.
Не очень понятно, что автор хочет нам этим сказать. И for и forEach оба делают обход. Правильнее было бы сказать: for использует pull-based обход, а forEach – push-based. Отсюда и возможные различия в поведении.
Сразу скажу: всё это имеет смысл только для обхода многопоточных структур данных
Не только. Различия между for и forEach порой существенны и для однопоточных структур. Например, те же стримы могут в некоторых случаях вести себя немного по-разному, если использовать iterator вместо forEach. Кроме того, forEach работает, как правило, быстрее, так как push-based реализация чаще намного проще и эффективнее. Посмотрите, например, как просто реализован ArrayList.forEach и насколько сложнее ArrayList.Itr.
Почему нельзя использовать траверс по умолчанию?
— Итератор проще и работает быстрее, а условия для пропуска элемента при обходе встречаются редко.
Не проще и не быстрее (см. выше). Итератор может быть либо медленнее и сложнее, либо как минимум одинаков по скорости и сложности с forEach (ну если говорить о разумных реализациях, можно конечно специально сделать forEach медленнее).
Перед началом чтения моего разбора поста можете прочитать оригинальный пост, хотя это необязательно (и может быть даже опасно 🤡).
Итак, автор начинает с двух тестов. В первом приводится кусок кода и спрашивается, сколько элементов он выведет:
Map<Integer, Integer> map = new ConcurrentHashMap<>();Во втором тесте цикл for заменён на метод forEach и задаётся тот же самый вопрос:
map.put(20, 0);
map.put(30, 0);
for (Integer key : map.keySet()) {
System.out.println(key);
map.put(40, 0);
}
map.keySet().forEach(key -> {
System.out.println(key);
map.put(40, 0);
});
Без чтения дальнейшего поста уже стоило бы напрячься. Во-первых, модифицировать мапу во время итерации в этом же самом потоке уже немного странно. Ну хорошо, бывает, что иногда приводится не очень практичный пример чисто ради педагогических целей. Есть ли какая-то педагогическая ценность в данном вопросе? Я бы сказал, да, если бы среди ответов был вариант: «чувак, поведение не определено и может произойти что угодно». Ведь для ConcurrentHashMap гарантируется только отсутствие вылета ConcurrentModificationException, а будет ли там подхватываться или не подхватываться элемент – это уже деталь реализации. Среди ответов же есть только два варианта «выведется 2 элемента» и «выведется 3 элемента». На самом деле контракт нам про 2 или 3 сказать ничего не может. Гипотетически может произойти любая комбинация:1. for выведет 2 элемента, forEach выведет 2
2. for выведет 2 элемента, forEach выведет 3
3. for выведет 3 элемента, forEach выведет 2
4. for выведет 3 элемента, forEach выведет 3
Комбинации 2 и 4, например, можно вполне себе легко воспроизвести: на Java 11.0.12 for выведет 20, 30, а forEach 20, 40, 30. Но если взять другие числа, например, 10, 20, 30 вместо 20, 30, 40, то оба выведут одно и то же: 20, 10, 30.
Таким образом, автор, пытаясь объяснить различия между устройством for и forEach, выбрала крайне неудачный (и некорректный пример).
Теперь давайте перейдём к самому посту. Пойдём прямо по тексту:
for использует для обхода итератор, а forEach - траверс.
Не очень понятно, что автор хочет нам этим сказать. И for и forEach оба делают обход. Правильнее было бы сказать: for использует pull-based обход, а forEach – push-based. Отсюда и возможные различия в поведении.
Сразу скажу: всё это имеет смысл только для обхода многопоточных структур данных
Не только. Различия между for и forEach порой существенны и для однопоточных структур. Например, те же стримы могут в некоторых случаях вести себя немного по-разному, если использовать iterator вместо forEach. Кроме того, forEach работает, как правило, быстрее, так как push-based реализация чаще намного проще и эффективнее. Посмотрите, например, как просто реализован ArrayList.forEach и насколько сложнее ArrayList.Itr.
Почему нельзя использовать траверс по умолчанию?
— Итератор проще и работает быстрее, а условия для пропуска элемента при обходе встречаются редко.
Не проще и не быстрее (см. выше). Итератор может быть либо медленнее и сложнее, либо как минимум одинаков по скорости и сложности с forEach (ну если говорить о разумных реализациях, можно конечно специально сделать forEach медленнее).
Итого: при выводе элементов ConcurrentHashMap через for и forEach используются разные алгоритмы обхода, поэтому результат вывода тоже разный.
Как раз-таки для ConcurrentHashMap используется один и тот же алгоритм обхода. Это тот случай, когда for и forEach совпадают. forEach использует вложенный класс Traverser, а for – класс BaseIterator, который наследуется от Traverser. А различия в поведении (for выводит 20, 30, а forEach 20, 40, 30) наблюдаются из-за того, что Traverser более ленив, чем BaseIterator. BaseIterator следующий элемент вычисляет раньше, чем он реально нужен.
Для однопоточных коллекций между for и forEach нет никакой разницы, в обоих случаях используется итератор.
Нет, разница между ними есть. И нет, в обоих случаях не используется итератор. Да, реализация forEach в Iterable по умолчанию делегирует обход к итератору. Но почти все коллекции эту реализацию переопределяют на более эффективную.
Как раз-таки для ConcurrentHashMap используется один и тот же алгоритм обхода. Это тот случай, когда for и forEach совпадают. forEach использует вложенный класс Traverser, а for – класс BaseIterator, который наследуется от Traverser. А различия в поведении (for выводит 20, 30, а forEach 20, 40, 30) наблюдаются из-за того, что Traverser более ленив, чем BaseIterator. BaseIterator следующий элемент вычисляет раньше, чем он реально нужен.
Для однопоточных коллекций между for и forEach нет никакой разницы, в обоих случаях используется итератор.
Нет, разница между ними есть. И нет, в обоих случаях не используется итератор. Да, реализация forEach в Iterable по умолчанию делегирует обход к итератору. Но почти все коллекции эту реализацию переопределяют на более эффективную.
До выхода следующей LTS-версии Java остаётся 3 дня. Планируете переходить?
Final Results
31%
Да, после выхода планируем перейти в ближайшее время
31%
Нам и на Java 11 хорошо
31%
Мы застряли на 8
3%
Зачем слезать с Java 7? Работает - не трогай
4%
Да мы вообще на 18 уже в продакшене
Forwarded from Java Memes → АйТи мемес
Пора сделать выбор будущего айти
Anonymous Poll
22%
КПРФ - Котлин программисты РФ
12%
ЗЕЛЁНЫЕ - Go
10%
ЛДПР - Либерально-демократический Питон России
3%
НОВЫЕ ЛЮДИ (нет) - PHP
32%
ЕДИНАЯ ДЖАВА (C# сюда же)
6%
СПРАВЕДЛИВЫЙ TYPE SCRIPT
4%
ЯБЛОКО - Swift
5%
ПАРТИЯ РАСТА
5%
ПАРТИЯ ПЕНСИОНЕРОВ - Cobol, Fortan, C
Классный вводный доклад про арифметику с плавающей точкой. Не поленитесь и потратьте всего полчаса на просмотр. Гарантирую, что время будет потрачено не зря. Я вот, например, узнал, как вообще на английском читается аббревиатура IEEE 754 😅. Оказывается, её произносят как "I triple-E seven fifty-four".
Ну это не самое полезное знание, конечно. Куда важнее понять, что в double числа дискретные и их множество конечно, в то время как вещественные числа непрерывны и их бесконечное количество. Это значит, double аппроксимирует вещественные числа, т.е. каждое вещественное число конвертируется в double путём нахождения ближайшего. Например, числа 0.1 в double нет, а ближайшее к нему это 0.1000000000000000055511151231257827021181583404541015625. Но почему тогда Double.toString(0.1) возвращает "0.1", а не "0.1000000000000000055511151231257827021181583404541015625"?
А ещё из-за такой аппроксимации нарушаются многие аксиомы. Не работают ассоциативность сложения, ассоциативность умножения, дистрибутивность и т.д. Например:
В общем, про всё это можно узнать из лекции.
#IEEE754 #double
Ну это не самое полезное знание, конечно. Куда важнее понять, что в double числа дискретные и их множество конечно, в то время как вещественные числа непрерывны и их бесконечное количество. Это значит, double аппроксимирует вещественные числа, т.е. каждое вещественное число конвертируется в double путём нахождения ближайшего. Например, числа 0.1 в double нет, а ближайшее к нему это 0.1000000000000000055511151231257827021181583404541015625. Но почему тогда Double.toString(0.1) возвращает "0.1", а не "0.1000000000000000055511151231257827021181583404541015625"?
А ещё из-за такой аппроксимации нарушаются многие аксиомы. Не работают ассоциативность сложения, ассоциативность умножения, дистрибутивность и т.д. Например:
> 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1Но:
$1 ==> 0.9999999999999999
> (0.1+0.1+0.1+0.1)+(0.1+0.1+0.1+0.1)+0.1+0.1(Это кстати отличный пример, демонстрирующий, почему double ни в коем случае нельзя использовать для хранения денег)
$2 ==> 1.0
В общем, про всё это можно узнать из лекции.
#IEEE754 #double
Три самых сложных проблемы в разработке ПО – это именование переменных, инвалидация кешей, и… порядок объявления полей, методов и параметров. Третье особенно болезненно в Java, потому что в ней нет никаких ограничений по порядку. Например, можно объявить поле в классе, потом десяток методов, а потом опять поле. Когда я вижу такое в коде, у меня сильно бомбит. Считаю, что Java не должна вообще такое позволять. Но с другой стороны, в далёких 90-х не было понятно, какой должен быть строгий порядок, поэтому решили не ограничивать вообще. Например, люди, пришедшие из C++ (а это основная целевая группа в то время), привыкли группировать по области видимости:
Далее я поделюсь моим личным предпочтением относительно порядка.
Порядок объявления членов класса должен быть таким, по моему мнению:
Порядок параметров конструкторов, очевидно, должен соответствовать порядку полей. Если конструкторов несколько, то они должны быть отсортированы по принципу «кто кого дёргает». Если конструктор A вызывает конструктор B, то конструктор A должен быть объявлен выше. Если конструкторы не связаны друг с другом, то выше лучше объявить тот конструктор, у которого поменьше параметров (он более простой и, скорее всего, будет вызываться чаще, поэтому лучше, чтобы он находился читателем кода пораньше).
С методами то же самое: тот, который вызывает, должен быть выше того, кого вызывают. То есть чем ниже мы скроллим класс, тем всё глубже в детали реализации мы погружаемся. Чем ниже, тем более низкоуровневые методы мы видим. Бывает, что методы вызывают друг друга, тогда непонятно, что должно быть выше. Но взаимная рекурсия довольно редкая штука, на самом деле. Сложнее, если методы не связаны друг с другом. В этом случае уже надо подключать здравый смысл и логику: я предпочитаю группировать по функциональности. Связанные вещи должны быть рядом:
Ещё принцип: стабильные параметры должны быть раньше нестабильных. Это не так уж и просто объяснить, что стабильно, а что нет, но попытаюсь опять привести пример:
Также я всегда объявляю лямбды последними. Например:
В общем, как-то так. Такой вот набор правил и принципов я применяю.
Вопросы? Замечания?
class C {
private:
// поля, методы
public:
// поля, методы
}
Я же считаю, что надо группировать по типу членов: поля отдельно, методы отдельно.Далее я поделюсь моим личным предпочтением относительно порядка.
Порядок объявления членов класса должен быть таким, по моему мнению:
class C {
// статические константы (static final)
// статические поля (static)
// final поля
// поля
// конструкторы
// геттеры/сеттеры
// методы
// вложенные классы
}
Порядок самих полей – вещь сложная, надо решать в каждом индивидуальном случае. Строгого правила нет, но можно исходить из здравого смысла: если, например, есть Text inputText и Button okButton, и поле ввода в окошке находится выше кнопки ОК, то и поле inputText должно быть объявлено выше okButton.Порядок параметров конструкторов, очевидно, должен соответствовать порядку полей. Если конструкторов несколько, то они должны быть отсортированы по принципу «кто кого дёргает». Если конструктор A вызывает конструктор B, то конструктор A должен быть объявлен выше. Если конструкторы не связаны друг с другом, то выше лучше объявить тот конструктор, у которого поменьше параметров (он более простой и, скорее всего, будет вызываться чаще, поэтому лучше, чтобы он находился читателем кода пораньше).
С методами то же самое: тот, который вызывает, должен быть выше того, кого вызывают. То есть чем ниже мы скроллим класс, тем всё глубже в детали реализации мы погружаемся. Чем ниже, тем более низкоуровневые методы мы видим. Бывает, что методы вызывают друг друга, тогда непонятно, что должно быть выше. Но взаимная рекурсия довольно редкая штука, на самом деле. Сложнее, если методы не связаны друг с другом. В этом случае уже надо подключать здравый смысл и логику: я предпочитаю группировать по функциональности. Связанные вещи должны быть рядом:
// Функциональность aС порядком параметров методов посложнее – здесь я ещё не до конца выработал правила. Есть только несколько не очень чётких принципов. Один из них: in-параметры, очевидно, должны быть раньше, чем out-параметры. Что такое in- и out-параметры? Суть должна стать понятна на примере:
a() {
aImpl1()
aImpl2()
}
aImpl1() {}
aImpl2() {}
// Функциональность b
b() {
bImpl1()
bImpl2()
}
bImpl1() {}
bImpl2() {}
public void copy(Path from, Path to) {}
Думаю, не стоит пояснять, что in, а что out. Всё очевидно. Что in, а что out, должно вытекать из здравого смысла.Ещё принцип: стабильные параметры должны быть раньше нестабильных. Это не так уж и просто объяснить, что стабильно, а что нет, но попытаюсь опять привести пример:
public void drawPoint(GC gc, int x, int y) {}
public void drawCircle(GC gc, int x, int y, int r) {}
GC – это graphics context. Он более стабилен, чем координаты, потому что создаётся один раз, а рисуется туда много всего и приходит много разных иксов и игреков. Другими словами, если что-то более постоянное и долгоживущее, тем оно более стабильно и должно идти раньше.Также я всегда объявляю лямбды последними. Например:
public void addEvent(EventType type, Runnable r);Иначе будет вот так:
addEvent(() -> {
// 100500 строк кода
}, EventType.MOUSE_CLICK);
Лучше, чтобы MOUSE_CLICK был первым, а после длинной лямбды не шло ничего.В общем, как-то так. Такой вот набор правил и принципов я применяю.
Вопросы? Замечания?