Pull to refresh

Comments 33

UFO just landed and posted this here
Достаточно было сделать compile-time generics :)
Проблема в том, что я этих выкрутасов тоже не видел, пока FindBugs не заюзал, но при этом они были (один из них даже я сам и породил). Они жили в коде пару лет, и их никто не замечал :-)
Вы используете compareTo для проверки совпадения значений? В общем случае, не с числами, он может вам неприятно удивить.
UFO just landed and posted this here
Видимо, имеется ввиду, что compareTo может быть не согласован с equals, хотя в документации настоятельно рекомендуется делать его согласованным.
UFO just landed and posted this here
Я и имел ввиду эту банальность: compareTo определяет порядок следования двух элементов, а equals — их равенство. Для тех же строк, в которых встречаются хитрые символы алфавита, мы получим compareTo = 0, equals = false.
UFO just landed and posted this here
На счет double я всегда знал что этот тип сравнивать обычным способом нельзя, всегда нужно учитывать погрешность, не только в Java, но и во многих других языках.
Зависит от задачи и от ситуации. Может оказаться, например, что значение в переменную double попадает прямиком из пользовательского ввода (а не в результате какого-либо математического действия) и, скажем значение 1.0 надо по алгоритму обработать специально. Тогда сравнивать по == совершенно уместно. Либо сравнение с нулём перед возможным делением на это число. В непараметрической статистике для обработки спаренных значений, возможно, адекватнее будет сравнивать по ==, чем вводить какую-то дельту…
Idea, кстати, про возможный NPE в тернарном операторе тоже предупреждает)
Напоминает задачки из java puzzlers. Ещё вот сравнение двух URL вспоминается, когда (new URL(«url1.org»)).equals(new URL(«url2.whatever»)) выдавало true, если у этих url совпадали ip.
Стало интересно, как C# ведёт себя в аналогичных ситуациях.

1.
Number n = flag ? new Integer(1) : new Double(2.0);

Ручного боксинга примитивных типов в зоопарк типов нет — проблем нет. (Вообще, странно это. Непонятно, зачем приводить типы.)

2.
Integer n = flag1 ? 1 : flag2 ? 2 : null;

Приведения int к null нет — проблем нет. (В шарпе нужно писать (int?)null, чтобы аналог скомпилировался.)

3.
private static final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

Какой ProblemFactory замечательный. :) В шарпе формат — это строка. Поглядел документацию — не вкурил, зачем было огород городить, тем более непотокобезопасный. Джависты, можете пояснить, какие это даёт преимущества? А то я в джаве нуб. :)

4.
System.out.println(new BigDecimal(1.1));

Здесь шарп в пролёте, потому что BigDecimal нет, есть только BigInteger. Попробовал с обычным decimal — шарп не позволил скомпилировать аналогичный код, потому что неявного приведения от double к decimal не существует. И даже если явно привести, то эта шибко умная зараза каким-то образом умудряется правильно округлить.

5.
System.out.printf("%s\n", "str#1");

В шарпе такая же хрень с переносами строк, только ни printf, ни %n нет. Пишите Environment.NewLine, чтоб ему пусто было (ну или WriteLine вместо Write).
Ручной боксинг в жаве нужен также из-за отсутствия возможности использовать примитивные типы в generics, и поэтому, например, когда создается объект HashMap<Integer, String>, можно использовать обычные цифры в качестве ключей, не создавая объекты. Да и для удобства.
Я так же сравнивал C++ и Java в очередном посте про PVS-Studio и получалось, что большинство C++ проблем для Java неактуальны. Потом понял, что в Java не меньше своих проблем, которые в плюсах не возникают. Наверняка и в Шарпе есть специфические проблемы :-)

Про DateFormat подозреваю, что так было сделано с целью улучшить производительность, если один формат используется многократно в рамках одного потока. Тогда строка формата парсится один раз. Конечно то, что сам форматтер не потокобезопасный, это какая-то ужасная кривость. Pattern (прекомпилированное регулярное выражение), например, не менее сложная сущность, но он потокобезопасен. Если затраты производительности вас не смущают, вы всегда можете написать утилитный метод типа
public static String formatDate(String format, Date date) {
  return new SimpleDateFormat(format).format(date);
}
В целом да, в шарпе есть неочевидные вещи, про которые мало кто (а иногда практически никто) не знает. Чего стоит одно создание делегатов: сколько объектов создастся по лямбдам и method group'ам, при каких условиях — это мозговыносяще (нагромождение обратной совместимости и оптимизаций). Существующие засады ещё и выпиливают (вон, даже создание переменных в цикле for поменяли).

Что меня ужасно радует — в шарпе нет маразмов, которые преследуют тебя на каждом шагу. В джаве есть == у строк и прочие вещи, которые не имеют никакого оправдания кроме перфекционизма. BigDecimal.equals из статьи — из той же серии. В шарпе из таких глобальных обломов могу вспомнить разве что события, принимающие значение null.

Впрочем, в большинстве случаев ReSharper и IDEA помножают такие проблемы на ноль для обоих языков. Причём сразу по мере ввода кода.
Работа с датами в Java вообще не лучшим образом сделана. Видимо, тяжелое наследие Java 1.0. Гораздо лучше использовать стороннюю библиотеку типа JodaTime.
В Java 8 обещают все это безобразие исправить.
Я как-то несколько лет назад работал в одной конторе, мы занимались переписыванием серверной части сайта symantec.com. И у нас в Эклипсе по их требованию стояли семантековские настройки (правда, мне кажется, это был PMD).

Так вот, там тернарные операторы помечались как ошибка. Т.е. совсем ошибка, код не компилировался, т.е. их нельзя было использовать вообще.

У меня нет мнения, насколько это правильно, просто вспомнил.

Отличный инструмент! Спасибо за то, что познакомили с ним!
Маленький вопрос от не-джависта:
можно написать либо new BigDecimal("1.1"), либо BigDecimal.valueOf(1.1).

Здесь опечатка или valueOf(1.1) как-то специальным образом компилируется, что 1.1 оказывается не IEEE754?
Вопрос не ясен. 1.1 не может быть выражено в стандарте IEEE 754 никак. Специально ничего не компилируется.
Тогда в тексте должно быть BigDecimal.valueOf("1.1"), а не BigDecimal.valueOf(1.1), если я правильно Вас понял.
В посте нет ошибки. Данный метод реализован так:
public static BigDecimal valueOf(double val) {
    return new BigDecimal(Double.toString(val));
}
То есть double сперва преобразуется в строку, используя обычный алгоритм, который работает с точностью double, а не с идеальной точностью. А потом уже вызывается конструктор от строки. Поэтому valueOf сработает не так как конструктор от double.
То есть ошибка в точности представления числа 1.1 в double при использовании valueOf(double) сохранится, поскольку double представляется в IEEE754?

Кстати, в посте еще одна опечатка:
А конструктор BigDecimal(double) напротив работает точно

— должно быть от string:
А конструктор BigDecimal(string), напротив, работает точно

То есть ошибка в точности представления числа 1.1 в double при использовании valueOf(double) сохранится, поскольку double представляется в IEEE754?
Не понял, что вы имели в виду. Произойдёт следующее:
1. Во время компиляции компилятор заменит 1.1 на ближайшее допустимое число, представимое в IEEE754, а именно на 1.100000000000000088817841970012523233890533447265625.
2. Во время выполнения это число будет преобразовано в строку с использованием метода Double.toString. Этот метод преобразует в строку, имея в виду точность типа double. Когда он поймёт, что получается что-то типа 1.100000000000000, а более точно в типе double всё равно не выразишь, он просто обрежет нули и вернёт результат «1.1».
3. Отработает конструктор BigDecimal от строки «1.1», который создаст объект BigDecimal, представляющий собой число 1.1

А конструктор BigDecimal(double) напротив работает точно

Нет здесь никакой опечатки. Это истинное утверждение и именно то, что я хотел сказать.
Спасибо. Теперь понятно, этого алгоритма в тексте поста и не хватало, чтобы понять, что происходит на самом деле и почему конструктор BigDecimal(double), напротив, работает точно.
Вчера подключил FindBugs у нас на проекте и обнаружил несколько одинаковых ошибок с тернарным оператором.
Есть два метода:
  static void m(int i) {}
  static void m(Integer i) {}

Есть код, который вызывает один из этих методов:
 m(s != null ? Integer.parseInt(s) : (Integer) null);

, где s — типа String.
И суть ошибки — вызывается метод с примитивом (int) и если s == null, то будет вызван m(((Integer)null).intValue()); что приведёт к NPE.
Если не кастить null к Integer, или вместо Integer.parseInt(s) использовать Integer.valueOf(s) то будет вызываться метод m(Integer). Но из за стечения обстоятельств , говнокода и кривых рук будет вызываться метод m(int) и в случае s == null будет NPE
Sign up to leave a comment.

Articles