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

Java

предисловие

Недавно, когда мы сканировали код с помощью сонара, мы обнаружили много проблем. В дополнение к обычным ошибкам и дырам в безопасности, есть несколько ошибок использования методов, которые меня очень интригуют. Почему меня так интересуют эти методы? Поскольку они настолько сбивают с толку, они могут сбить нас с толку.

1. replace заменит все символы?

Много раз, когда мы используем строки, мы хотим поместить символы в строки, такие как: ATYSDFA*YAзаменить на символыB, первой мыслью может быть использованиеreplaceметод.

Если вы хотите иметь всеAзаменены наB, очевидно, его можно использоватьreplaceAllметод, так как он очень интуитивно понятен, вы можете догадаться о его назначении из названия метода.

Итак, возникает вопрос:replaceБудет ли метод заменять все совпадающие символы?

Официальный jdk дал ответ.

Этот метод заменяет каждую совпадающую строку.

теперь, когдаreplaceа такжеreplaceAllможет заменить все совпадающие символы, так в чем же между ними разница?

  1. replaceЕсть два перегруженных метода.

Один из параметров метода: char oldChar и char newChar, поддерживает замену символов.

source.replace('A', 'B')

Параметры другого метода: цель CharSequence и замена CharSequence, которая поддерживает замену строки.

source.replace("A", "B")
  1. replaceAllПараметры метода: регулярное выражение строки и замена строки, замена на основе регулярного выражения.

Обычная замена строки:

source.replaceAll("A", "B")

Замена регулярного выражения (замените * на C):

source.replaceAll("\\*", "C")

Кстати, заменив * на C, используйтеreplaceметод также может быть реализован:

source.replace("*", "C")

Специальные символы не нужно экранировать.

Однако будьте осторожны, чтобы не использовать следующие обозначения:

source.replace("\\*", "C")

Такой способ записи сделает строку незаменимой.

Еще один небольшой вопрос: что, если я хочу заменить только первую совпадающую строку?

В это время вы можете использоватьreplaceFirstметод:

source.replaceFirst("A", "B")

2. Integer не может использовать == для оценки равенства?

Не знаю, видели ли вы это в проекте, некоторые коллегиIntegerИспользование двух параметров типа==Сравнения равны?

Я все равно это видел, так правильно ли это использование?

Мой ответ заключается в том, что это зависит от конкретной ситуации, и я не могу сказать, правильно ли это или неправильно.

Некоторые поля состояния, такие как:orderStatusЕсть: -1 (не заказано), 0 (заказано), 1 (оплачено), 2 (выполнено), 3 (отменено), 5 состояний.

В это время, если вы используете==Проверить на равенство:

  Integer orderStatus1 = new Integer(1);
  Integer orderStatus2 = new Integer(1);
  System.out.println(orderStatus1 == orderStatus2);

Результат возврата будетtrue?

ответfalse.

Некоторые учащиеся могут возразить,IntegerНет диапазона, в котором находится:-128-127кеш?

Зачемfalse?

Сначала взгляните на конструктор Integer:

На самом деле он не использует кеш.

Так где же используется кеш?

ответvalueOfВ методе:

Если вышеуказанное решение изменить на это:

String orderStatus1 = new String("1");
String orderStatus2 = new String("1");
System.out.println(Integer.valueOf(orderStatus1) == Integer.valueOf(orderStatus2));

Результат возврата будетtrue?

Ответ: правдаtrue.

Мы должны развивать хорошие привычки кодирования и использовать как можно меньше==судить двоихIntegerРавенство данных типа равно только в приведенных выше особых сценариях.

Вместо этого используйтеequalsОценка метода:

Integer orderStatus1 = new Integer(1);
Integer orderStatus2 = new Integer(1);
System.out.println(orderStatus1.equals(orderStatus2));

3. Использовать BigDecimal без потери точности?

Обычно мы определяем некоторые поля десятичного типа (например, количество) как BigDecimal вместо Double, чтобы избежать потери точности.

Может быть такой сценарий при использовании Double:

double amount1 = 0.02;
double amount2 = 0.03;
System.out.println(amount2 - amount1);

Обычно ожидаетсяamount2 - amount1должно быть равно0.01

Но результат выполнения:

0.009999999999999998

Фактические результаты оказались меньше ожидаемых.

Вычитание двух параметров типа Double будет преобразовано в двоичное, потому что количество значащих цифр Double равно16В этом случае будет не хватать десятичных знаков для хранения, и в этом случае будут возникать ошибки.

Здравый смысл подсказывает нам, что использование BigDecimal позволяет избежать потери точности.

Но позволяет ли использование BigDecimal избежать потери точности?

ответ отрицательный.

Почему?

BigDecimal amount1 = new BigDecimal(0.02);
BigDecimal amount2 = new BigDecimal(0.03);
System.out.println(amount2.subtract(amount1));

В этом примере определяются два параметра типа BigDecimal, данные инициализируются с помощью конструктора, а затем печатается значение после вычитания двух параметров.

результат:

0.0099999999999999984734433411404097569175064563751220703125

Ненаучно, почему точность все еще теряется?

Вот такое описание метода построения BigDecimal в jdk:

Грубо говоря, результат этого конструктора может быть непредсказуемым, и могут быть случаи, когда он равен 0,1 при создании, но на самом деле равен 0,10000000000000000055511151231257827021181583404541015625.

Видно, что при инициализации объекта с помощью конструктора BigDecimal также теряется точность.

Так как же не потерять точность?

BigDecimal amount1 = new BigDecimal(Double.toString(0.02));
BigDecimal amount2 = new BigDecimal(Double.toString(0.03));
System.out.println(amount2.subtract(amount1));

использоватьDouble.toStringМетод преобразует десятичное число типа double, чтобы не потерять точность.

На самом деле есть лучший способ:

BigDecimal amount1 = BigDecimal.valueOf(0.02);
BigDecimal amount2 = BigDecimal.valueOf(0.03);
System.out.println(amount2.subtract(amount1));

использоватьBigDecimal.valueOfМетод инициализирует параметр типа BigDecimal, который также может гарантировать, что точность не будет потеряна. В новой версии руководства по разработке Alibaba также рекомендуется использовать этот метод для создания параметров BigDecimal.

4. Нельзя использовать String для конкатенации строк?

StringТип строки называется неизменяемой последовательностью, что означает, что данные объекта не могут быть изменены после его определения.Если вы хотите изменить его, вам нужно создать новый объект.

String a = "123";
String b = "456";
String c = a + b;
System.out.println(c);

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

В этом случае мы можем использовать более эффективную последовательность переменных символов:StringBuilderа такжеStringBuffer, чтобы определить объект.

Так,StringBuilderа такжеStringBufferКакая разница?

StringBuffer добавлен к каждому основному методуsynchronizedключевое слово, которого нет в StringBuilder. Таким образом, StringBuffer является потокобезопасным, а StringBuilder — нет.

На самом деле у нас редко бывают сценарии, в которых нам нужно объединить строки в нескольких потоках, поэтому StringBuffer используется очень редко. Как правило, при объединении строк мы рекомендуем использоватьStringBuilder, через егоappendМетод добавляет строку, генерирует только один объект и не блокируется, что более эффективно.

String a = "123";
String b = "456";
StringBuilder c = new StringBuilder();
c.append(a).append(b);
System.out.println(c);

Далее следует ключевой вопрос:Сращивание строк с использованием объектов типа String должно быть менее эффективным, чем объекты типа StringBuilder?

ответ отрицательный.

Почему?

использоватьjavap -c StringTestКоманда для декомпиляции:

Из рисунка видно, что определены два параметра типа String и еще один параметр типа StringBuilder, а затем дважды используется метод append для добавления строки.

Если код такой:

String a = "123";
String b = "789";
String c = a + b;
System.out.println(c);

использоватьjavap -c StringTestЧто будет результатом декомпиляции команды?

Мы были удивлены, обнаружив, что также были определены два параметра типа String и еще один параметр типа StringBuilder, а затем дважды использовался метод append для добавления строки. Тот же результат, что и выше.

** Фактически, начиная с jdk5, java оптимизировала операцию + строк типа String.После того, как операция скомпилирована в файл байт-кода, она будет оптимизирована как операция добавления StringBuilder.

5. Разница между isEmpty и isBlank

Когда мы работаем со строками, нам часто нужно определить, пуста ли строка. Без помощи каких-либо инструментов мы обычно судим следующим образом:

if (null != source && !"".equals(source)) {
    System.out.println("not empty");
}

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

  • StringUtils весной
  • StringUtils в jdbc
  • StringUtils в apache common3

ноspringсерединаStringUtilsтолько классisEmptyметод, нетisNotEmptyметод.

jdbcсерединаStringUtilsтолько классisNullOrEmptyметод, ниisNotNullOrEmptyметод.

Так что очень рекомендую сюдаapache common3серединаStringUtilsкласс, который содержит множество практичных нулевых методов:isEmpty,isBlank,isNotEmpty,isNotBlankИ т. д., есть и другие методы манипулирования строками.

Вот проблема,isEmptyа такжеisBlankКакая разница?

использоватьisEmptyОценка метода:

 StringUtils.isEmpty(null)      = true
 StringUtils.isEmpty("")        = true
 StringUtils.isEmpty(" ")       = false
 StringUtils.isEmpty("bob")     = false
 StringUtils.isEmpty("  bob  ") = false

использоватьisBlankОценка метода:

StringUtils.isBlank(null)      = true
StringUtils.isBlank("")        = true
StringUtils.isBlank(" ")       = true
StringUtils.isBlank("bob")     = false
StringUtils.isBlank("  bob  ") = false

Основное различие между этими двумя методами заключается в том, что это" "В случае пустой строкиisNotEmptyвозвращает ложь, аisBlankВернуть истину.

6. Должен ли результат запроса картографа быть пустым?

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

List<User> list = userMapper.query(search);
if(CollectionUtils.isNotEmpty(list)) {
    List<Long> idList = list.stream().map(User::getId).collect(Collectors.toList());
}

Потому что, согласно здравому смыслу, коллекция, запрашиваемая общим вызовом метода, может бытьnull, который должен быть нулевым. Впрочем, здесь он особенный, я проверилmybatisИсходный код, этот пустой код действительно можно удалить.

Что происходит?

mybatisМетод запроса в итоге будет передан методу handleResultSets класса DefaultResultSetHandler:

Этот метод возвращаетmultipleResultsСписок объектов коллекции в начале методаnewВыходите, он точно не будет пустым.

Так что не удивляйтесь, если увидите, что кто-то использует результат запроса прямо в коде проекта, не удивляйтесь:

List<User> list = userMapper.query(search);
List<Long> idList = list.stream().map(User::getId).collect(Collectors.toList());

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

7. Правильное использование метода indexOf

Когда я просматривал чужой код, я увидел местоindexOfИспользуя этот способ письма, я больше впечатлен:

String source = "#ATYSDFA*Y";
if(source.indexOf("#") > 0) {
    System.out.println("do something");
}

Вы, ребята, говорите, что этот код будет распечатанdo something?

ответ отрицательный.

Зачем?

JDK официально заявил, что если его не существует, он вернет -1

indexOfМетод возвращает позицию указанного элемента в строке, начиная с 0. в то время как приведенный выше пример#в первой позиции строки, поэтому вызовитеindexOfЗначение после метода на самом деле равно 0. Итак, условие ложно и не будет напечатаноdo something.

Если вы хотите пройтиindexOfПри определении того, существует ли элемент, используйте:

if(source.indexOf("#") > -1) {
    System.out.println("do something");
}

На самом деле есть более изящныеcontainsметод:

if(source.contains("#")) {
   System.out.println("do something");
}

Последнее слово (пожалуйста, обратите внимание, не проституируйте меня по пустякам)

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

Попросите в один клик три ссылки: лайк, вперед и смотреть.

Следите за официальной учетной записью: [Су Сан сказал о технологии], ответьте в официальной учетной записи: интервью, артефакты кода, руководства по разработке, управление временем имеют большие преимущества для поклонников, и ответьте: присоединяйтесь к группе, вы можете общаться и учиться со многими старшими из BAT. производители .