Java - общие Gotchas

В том же духе других платформ было логично следить за этим вопросом: какие распространенные неочевидные ошибки в Java? Кажется, что они должны работать, но не надо.

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

См. также:

Ответы

Ответ 1

Сравнение равенства объектов с использованием == вместо .equals() - которое ведет себя совершенно по-разному для примитивов.

Эта информация гарантирует, что новички будут путаться при "foo" == "foo", но new String("foo") != new String("foo").

Ответ 2

"a,b,c,d,,,".split(",").length

возвращает 4, не 7, как вы могли бы (и я конечно же) ожидал. split игнорирует все возвращаемые возвращаемые строки. Это означает:

",,,a,b,c,d".split(",").length

возвращает 7! Чтобы получить то, что я думаю о "наименее удивительном" поведении, вам нужно сделать что-то весьма удивительное:

"a,b,c,d,,,".split(",",-1).length

чтобы получить 7.

Ответ 3

Переопределить equals(), но не hashCode()

Он может иметь действительно неожиданные результаты при использовании карт, наборов или списков.

Ответ 4

Я думаю, что очень подлый метод String.substring. Это повторно использует тот же базовый массив char[], что и исходная строка с разными offset и length.

Это может привести к очень труднодоступным проблемам памяти. Например, вы можете обрабатывать чрезвычайно большие файлы (возможно, XML) для нескольких маленьких бит. Если вы преобразовали весь файл в String (вместо того, чтобы использовать Reader для "ходьбы" над файлом) и используйте substring, чтобы захватить нужные вам биты, вы по-прежнему несете полный размер файла char[] массив за кулисами. Я видел это много раз, и это может быть очень сложно определить.

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

Ответ 5

SimpleDateFormat не является потокобезопасным.

Ответ 6

Есть два, которые меня очень раздражают.

Дата/Календарь

Во-первых, классы Java Date и Calendar серьезно перепутаны. Я знаю, что есть предложения по их исправлению, я просто надеюсь, что они преуспеют.

Calendar.get(Calendar.DAY_OF_MONTH) - 1-й вариант
Calendar.get(Calendar.MONTH) - 0

Авто-бокс, предотвращающий мышление

Другой - Integer vs int (это относится к любой примитивной версии объекта). Это, в частности, раздражение, вызванное неправильным пониманием Integer как отличного от int (поскольку вы можете относиться к ним столько же времени из-за автоматического бокса).

int x = 5;
int y = 5;
Integer z = new Integer(5);
Integer t = new Integer(5);

System.out.println(5 == x);     // Prints true
System.out.println(x == y);     // Prints true
System.out.println(x == z);     // Prints true (auto-boxing can be so nice)
System.out.println(5 == z);     // Prints true
System.out.println(z == t);     // Prints SOMETHING

Так как z и t - объекты, даже если они имеют одинаковое значение, они (скорее всего) разные объекты. Что вы на самом деле имеете в виду:

System.out.println(z.equals(t));   // Prints true

Это может быть болью для отслеживания. Вы отлаживаете что-то, все выглядит хорошо, и вы, наконец, в конечном итоге обнаруживаете, что ваша проблема в том, что 5!= 5, когда оба являются объектами.

Возможность сказать

List<Integer> stuff = new ArrayList<Integer>();

stuff.add(5);

так приятно. Это сделало Java гораздо более пригодным для использования, чтобы не перекладывать все эти "новые целые числа (5)" и "((Integer) list.get(3)). IntValue()" по всему месту. Но эти преимущества приходят с этой выгодой.

Ответ 7

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

Ответ 8

List<Integer> list = new java.util.ArrayList<Integer>();
list.add(1);
list.remove(1); // throws...

Старые API не были разработаны с учетом бокса, поэтому перегрузка с примитивами и объектами.

Ответ 9

Этот я только что наткнулся:

double[] aList = new double[400];

List l = Arrays.asList(aList);
//do intense stuff with l

Кто-нибудь видит проблему?


Что происходит, Arrays.asList() ожидает массив типов объектов (например, Double []). Было бы неплохо, если бы он просто выбросил ошибку для предыдущего ocde. Однако asList() также может принимать такие аргументы:

Arrays.asList(1, 9, 4, 4, 20);

Итак, что делает код, создайте List с одним элементом - a double[].

Я должен был подумать, когда понадобилось 0ms для сортировки массива элементов массива 750000...

Ответ 10

Поплавки

Я не знаю много раз, я видел

floata == floatb

где "правильный" тест должен быть

Math.abs(floata - floatb) < 0.001

Я действительно хочу, чтобы BigDecimal с литеральным синтаксисом был десятичным типом по умолчанию...

Ответ 11

этот меня несколько раз сбивал с толку, и я слышал немало опытных разработчиков java, которые тратили много времени.

ClassNotFoundException --- вы знаете, что класс находится в пути к классам, но вы НЕ уверены, почему класс НЕ загружается.

Собственно, этот класс имеет статический блок. В статическом блоке было исключение, и кто-то съел исключение. они НЕ должны. Они должны бросать ExceptionInInitializerError. Итак, всегда ищите статические блоки, чтобы путешествовать. Это также помогает перемещать любой код в статических блоках, чтобы перейти в статические методы, чтобы отладка метода была намного проще с отладчиком.

Ответ 12

Не очень специфичен для Java, поскольку многие (но не все) языки реализуют его таким образом, но оператор % не является истинным модульным оператором, так как он работает с отрицательными числами. Это делает его оператором остатка и может привести к неожиданностям, если вы не знаете об этом.

Появится следующий код: "четный" или "нечетный", но это не так.

public static void main(String[] args)
{
    String a = null;
    int n = "number".hashCode();

    switch( n % 2 ) {
        case 0:
            a = "even";
            break;
        case 1:
            a = "odd";
            break;
    }

    System.out.println( a );
}

Проблема заключается в том, что хеш-код для "числа" отрицательный, поэтому операция n % 2 в коммутаторе также отрицательна. Поскольку в коммутаторе нет случая для решения отрицательного результата, переменная a никогда не будет установлена. Программа выводит null.

Убедитесь, что вы знаете, как работает оператор % с отрицательными номерами, независимо от того, на каком языке вы работаете.

Ответ 13

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

  List list = new ArrayList();
    Iterator it = list.iterator();
    while(it.hasNext()){
      //some code that does some stuff
      list.remove(0); //BOOM!
  }

Ответ 14

Манипуляция компонентами Swing извне потока отправки событий может привести к ошибкам, которые чрезвычайно трудно найти. Это даже то, что мы (как опытные программисты с 3-мя соответствующими 6-летним опытом Java) часто забываем! Иногда эти ошибки прорываются после написания кода справа и рефакторинга небрежно после этого...

Смотрите tutorial, почему вы должны.

Ответ 15

Целочисленное деление

1/2 == 0 not 0.5

Ответ 16

Неизменяемые строки, что означает, что определенные методы не изменяют исходный объект, а вместо этого возвращают копию измененного объекта. Когда я начинал с Java, я все время забывал об этом и задавался вопросом, почему метод замены не работал на моем строковом объекте.

String text = "foobar";
text.replace("foo", "super");
System.out.print(text); // still prints "foobar" instead of "superbar"

Ответ 17

если у вас есть метод, который имеет то же имя, что и конструктор, но имеет тип возврата. Хотя этот метод выглядит как конструктор (для noob), он НЕ.

передача аргументов основному методу - для использования noobs требуется некоторое время.

. как аргумент classpath для выполнения программы в текущем каталоге.

Понимая, что имя массива строк не очевидно

hashCode и равно: многие разработчики java с более чем 5-летним опытом не совсем поняли.

Установить vs List

До JDK 6 у Java не было NavigableSets, чтобы вы могли легко выполнять итерацию с помощью Set and Map.

Ответ 18

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

В качестве простой демонстрации следующая программа может давать разные результаты в зависимости от того, как она выполняется:

public static void main(String[] args) {
    System.out.println(new Object().hashCode());
}

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

Ответ 19

(un) Бокс и длинная/длинная путаница. Вопреки опыту pre-Java 5, вы можете получить исключение NullPointerException во второй строке ниже.

Long msec = getSleepMsec();
Thread.sleep(msec);

Если getSleepTime() возвращает нулевые, распаковывающие броски.

Ответ 20

Когда вы создаете duplicate или slice для ByteBuffer, он не наследует значение свойства order из родительского буфера, поэтому такой код не будет делать то, что вы ожидаете:

ByteBuffer buffer1 = ByteBuffer.allocate(8);
buffer1.order(ByteOrder.LITTLE_ENDIAN);
buffer1.putInt(2, 1234);

ByteBuffer buffer2 = buffer1.duplicate();
System.out.println(buffer2.getInt(2));
// Output is "-771489792", not "1234" as expected

Ответ 21

Использование подстановочного файла ? generics.

Люди видят это и думают, что им нужно, например. используйте List<?>, когда они хотят, чтобы List они могли добавить что-нибудь, не останавливаясь, чтобы думать, что List<Object> уже делает это. Затем они задаются вопросом, почему компилятор не позволит им использовать add(), потому что List<?> действительно означает "список определенного типа, который я не знаю", поэтому единственное, что вы можете сделать с этим List, - это получить от него Object экземпляры.

Ответ 22

Среди распространенных ловушек, хорошо известных, но по-прежнему кусающих иногда программистов, есть классический if (a = b), который встречается на всех языках, подобных C.

В Java он может работать, только если a и b являются логическими, конечно. Но я слишком часто вижу тесты новичков, такие как if (a == true) (в то время как if (a) короче, читабельнее и безопаснее...) и иногда пишу по ошибке if (a = true), задаваясь вопросом, почему тест не работает.
Для тех, кто этого не получает: последний оператор сначала назначает true на a, а затем выполняет тест, который всегда преуспевает!

-

Тот, кто кусает много новичков, и даже некоторые отвлеченные более опытные программисты (нашли его в нашем коде), if (str == "foo"). Обратите внимание, что я всегда задавался вопросом, почему Sun переопределяет знак + для строк, но не ==, хотя бы для простых случаев (с учетом регистра).

Для новичков: == сравнивает ссылки, а не содержимое строк. Вы можете иметь две строки одного и того же содержимого, хранящиеся в разных объектах (разные ссылки), поэтому == будет ложным.

Простой пример:

final String F = "Foo";
String a = F;
String b = F;
assert a == b; // Works! They refer to the same object
String c = "F" + F.substring(1); // Still "Foo"
assert c.equals(a); // Works
assert c == a; // Fails

-

И я также видел if (a == b & c == d) или что-то в этом роде. Он работает (любопытно), но мы потеряли логический ярлык оператора (не пытайтесь писать: if (r != null & r.isSomething())!).

Для новичков: при оценке a && b Java не оценивает b, если a является ложным. В a & b Java оценивает обе части, а затем выполняет операцию; но вторая часть может потерпеть неудачу.

[EDIT] Хорошее предложение от J Coombs, я обновил свой ответ.

Ответ 23

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

Реализация системного типа с типом стиранием ужасна и бросает большинство студентов, когда они узнают о генериках для первого в Java: почему нам все еще нужно прибегать к типу, если параметр типа уже предоставлен? Да, они обеспечивали обратную совместимость, но при довольно глупой стоимости.

Ответ 24

Идти первым, здесь я поймал сегодня. Это связано с путаницей Long/Long.

public void foo(Object obj) {
    if (grass.isGreen()) {
        Long id = grass.getId();
        foo(id);
    }
}
private void foo(long id) {
    Lawn lawn = bar.getLawn(id);
    if (lawn == null) {
        throw new IllegalStateException("grass should be associated with a lawn");
    }   
}

Очевидно, имена были изменены для защиты невинных:)

Ответ 25

Еще один, который я хотел бы отметить, - это (слишком распространенный) диск для создания общих API. Использование хорошо продуманного общего кода в порядке. Разработка вашего проекта сложна. Очень сложно!

Посмотрите на функцию сортировки/фильтрации в новом Swing JTable. Это полный кошмар. Очевидно, что вы, вероятно, захотите подключить фильтры в реальной жизни, но я счел невозможным это сделать, просто используя необработанную типизированную версию предоставленных классов.

Ответ 26

    System.out.println(Calendar.getInstance(TimeZone.getTimeZone("Asia/Hong_Kong")).getTime());
    System.out.println(Calendar.getInstance(TimeZone.getTimeZone("America/Jamaica")).getTime());

Вывод одинаков.

Ответ 27

ИМХО  1. Использование vector.add(Collection) вместо vector.addall(Collection). Первый добавляет объект коллекции к вектору, а второй добавляет содержимое коллекции.  2. Хотя это точно не связано с программированием, использование парсеров xml, которые поступают из нескольких источников, таких как xerces, jdom. Опираясь на разные парсеры и имея свои банки в пути класса, это кошмар.

Ответ 28

У меня была некоторая забавная отладка TreeSet один раз, поскольку я не знал об этой информации из API:

Обратите внимание, что порядок, поддерживаемый набором (будь то явный компаратор), должен быть согласован с равными, если он правильно реализует интерфейс Set. (См. Comparable или Comparator для точного определения соответствия с равными.) Это происходит потому, что интерфейс Set определяется с помощью операции equals, но экземпляр TreeSet выполняет все сопоставления ключей, используя метод compareTo (или compare), поэтому два ключи, которые по этому методу считаются равными, равны, с точки зрения множества. Поведение множества хорошо определено, даже если его упорядочение не соответствует равным; он просто не подчиняется генеральному контракту интерфейса Set. http://download.oracle.com/javase/1.4.2/docs/api/java/util/TreeSet.html

Объекты с правильными реализациями equals/hashcode добавлялись и никогда больше не видели, поскольку реализация compareTo была несовместима с равными.