4

В книге вычитал, что приведение int в String с использованием конкатенации, например, где а это int

String s = a + "";

это плохой способ. А вот

String s = Integer.toString(a);

Или

String s = String.valueOf(a);

Это хорошие способы. Почему?введите сюда описание изображения

Nofate
  • 34,603
  • см. http://stackoverflow.com/questions/2506474/is-concatenating-with-an-empty-string-to-do-a-string-conversion-really-that-bad – Sublihim Jan 29 '17 at 18:54
  • рекомендуемое видео для любителей неявных преобразований: Wat (5min) – jfs Jan 30 '17 at 20:20

5 Answers5

4

Нет, конкатенация - это не плохо. Она выглядит более наглядно и в большинстве случаев, javac умеет ее оптимизировать с использованием java.lang.StringBuilder. Так же, срабатывает интернирование, что позволяет не создавать множество объектов. Например,

 String value = "hello world" + 1;
 System.out.println(value == "hello world1");

выведет

true

А методы Integer.toString и String.valueOf идентичны, т.к. последний делегирует вызов второму. Но у них есть особенность - при каждом вызове создается новая строка.

Artem
  • 14,967
  • String это же объект. Вы используете оператор сравнения ссылок для примитивов. Может всё-таки equals? –  Jan 29 '17 at 18:58
  • 3
    @СергейГрушин я сравниваю ссылки чтобы показать что создалось не два объекта а один. – Artem Jan 29 '17 at 19:01
  • Все ясно теперь :) конкатенации и правда лучший способ –  Jan 29 '17 at 19:07
  • Или "контекация", м? –  Jan 29 '17 at 19:07
  • @СергейГрушин да, вы правы, поправил) – Artem Jan 29 '17 at 19:12
  • Гляньте нижний ответ, я прав? –  Jan 29 '17 at 19:17
  • не это плохо - мб, это не плохо? – vp_arth Jan 30 '17 at 11:19
  • 1
    @vp_arth да, я очень неграмотный :) – Artem Jan 30 '17 at 11:30
3

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

System.out.println(6 + 4 + "");
System.out.println("" + 4 + 6);

В первой строке выведет 10, а во второй - 46.

  • В обоих случаях не будет сложения, разве нет? Конкатенация важнее сложения по увеличению приоритета –  Jan 29 '17 at 18:43
  • Будет считать слева направо. https://ideone.com/4QwTGg – Кирилл Малышев Jan 29 '17 at 18:44
  • Упс. Точно. Не понял правильно, сорри. –  Jan 29 '17 at 18:46
  • Но ведь можно поместить сложение в скобки, так как у них выше приоритет. А после этого уже и конкатенация выполнится. Значит, в книге, плохой способ означает способ с подводным камнем –  Jan 29 '17 at 18:48
  • Пишут, что valueOf быстрее (в конце статьи). https://habrahabr.ru/post/132241/ – Кирилл Малышев Jan 29 '17 at 18:52
  • @КириллМалышев: Не ведитесь на такое. Во-первых, если у кода одинаковый смысл, компилятор (не сейчас, так в следующей версии) сгенерирует для него и одинаковый код. А во-вторых, выигрыш в несколько микро-, а то и наносекунд не стоит того, чтобы заморачиваться. – VladD Jan 30 '17 at 12:14
2

Это плохо потому, что Java - это строго типизированный язык, и неявное приведение типов является нарушением парадигмы языка.

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

Два варианта кода:

String s = a + "";

String s = Integer.toString(a);

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

rjhdby
  • 13,850
  • Опа, опять мнения поменялись. Ну так а разве оно неявное? Конкатенация эквивалентна StringBuffer.append() и привидением через toString() Неявное != невидимое –  Jan 29 '17 at 19:12
  • 2
    Сергей, int + string - это неявное приведение int к string. Это не запрещено, является описанным поведением языка, но банально идеологически неправильно. Чем "опасно" неявное приведение написал в своем ответе Кирилл Малышев. Если дело касается, например, PHP с динамической типизацией, то там такое выражение совершенно нормально и ни у кого не вызовет нареканий, а в Java, где программист уверен, что типы приводятся только осознанно, оно может привести к длительному поиску ошибок на пустом месте. Вопрос гигиены можно сказать. – rjhdby Jan 29 '17 at 19:18
  • Вы частично правы, возможно я ещё не дорос, чтобы вступать в дискуссии, но, имхо, программист должен знать что делает конкатенация, прежде, чем её использовать. Привидение типов есть, просто оно выполняется при компиляции. Тут скорее всего ответственность на плечи тому кто пишет код. Он должен все прекрасно знать, и тут Java не причем –  Jan 29 '17 at 19:22
  • Я дополнил ответ – rjhdby Jan 29 '17 at 19:27
  • 3
    @rjhdby я бы не стал так категорично утверждать. В кривых руках, любой инструмент является "опасным". Не знание приоритета операций и порядка выполнения операций - я думаю проблема прежде всего программиста. Объективно конкатенация более читабельна + хорошо оптимизирована и зная как она работает в определенных ситуациях ощутимо принесет больше пользы чем вреда. – Artem Jan 29 '17 at 19:28
  • 1
    Стажера надо проверить на знание конкатенации :) –  Jan 29 '17 at 19:29
  • @ArtemKonovalov что хорошо в небольших проекта или "по фану", то не очень здорово в проектах больших и распределенных. Попытайся кто нибудь вставить такое http://ru.stackoverflow.com/questions/581668/%D0%9A%D0%BE%D0%B4-%D0%B3%D0%BE%D0%BB%D1%8C%D1%84-%D0%A0%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F-%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%B0-%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%BA%D0%B8-%D0%BA%D0%BE%D0%BC%D0%B1%D0%B8%D0%BD%D0%B0%D1%86%D0%B8%D0%B9/581929 в проект, на первый раз был бы серьезный разговор, а на второй он вылетел бы как пробка. Хотя знание языка и высоко. – rjhdby Jan 29 '17 at 19:41
  • @СергейГрушин может для вас это будет откровением, но стажеру надо не изощряться в изучении хитрых фишек языка, которые используют в 0.001% проектов, а как можно скорее начать писать код для решения бизнес задач работодателя. – rjhdby Jan 29 '17 at 19:49
  • @rjhdby не нужно путать, чисто по фану и реально работающий инструмент. То что вы напишите миллион раз String.valueOf создаст миллион объектов, которые будут висеть в хипе. В реально работающих высоконагруженных приложениях, как раз и избегают ненужного аллоцирования, т.к. это лишняя нагрузка на gc. По поводу стажеров, я считаю что программист должен знать язык, т.к. это его рабочий интрумент. И не нужно говорить, что это используется в 0.0001% случаях. Конкатенация используется достаточно часто и не является чем то специфичным вроде фантомных ссылок или инструментирования байткода. – Artem Jan 29 '17 at 20:03
  • @ArtemKonovalov конкатенация разнотиповых переменных часто используется!? Для чего же, позвольте спросить? – rjhdby Jan 29 '17 at 20:07
  • 1
    чтобы создать строку. Вообще я устал что то доказывать, не хотите писать красиво и лаконично "i'm number #" + number пишите "i'm number #"+Integer.toString(number) это разумеется более безопаснее, идеологически правильнее, читабельнее, разумеется еще и производительнее. – Artem Jan 29 '17 at 20:14
  • @ArtemKonovalov если мне нужно сформировать сложную строку и переменных разных типов, я воспользуюсь либо StringBuilder, либо printf, но никак не конкатенацией. Для всего свой инструмент, а складывать теплое с мягким конечно можно, но попахивает (в смысле code smell) – rjhdby Jan 29 '17 at 20:17
2

При использовании конкатенации выполняется несколько ненужных действий:

  • неявно вызывается Integer.toString(a);

  • выделяется StringBuffer для конкатенации, куда копируется пустая строка "" и переведённое в строку число

  • преобразовывается StringBuffer в строку

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

Artem
  • 14,967
Michel_T.
  • 260
  • 1
  • 9
  • 1
    вставлю свои пять копеек, не BufferString а StringBuilder, и если посмотреть реализацию (java 8) то там не создается новая строка, которая потом апендится. Там к внутреннему char массиву в StringBuilder дописываются char'ы созданные из int. – Artem Jan 30 '17 at 11:29
  • 1
    StringBuffer обычно: Java compiler may use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression. Отсюда: http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.18.1 Но как я понимаю, это зависит от реализации и может быть реализовано инача – Michel_T. Jan 30 '17 at 11:54
  • спасибо за пруф. Хм, не очень понятно, чем они мотивировали использование синхронизированного StringBuffer вместо StringBuilder. Понятно что при отсутствии контеншена затраты на синхронизацию отсутствуют но все же. – Artem Jan 30 '17 at 12:03
2

Напишем тестовый класс с двумя способами приведения числа к строки:

public class TestString {

    public void intToStringConcat(int value){
        String stringValue = value + "";
    }

    public void intToStringValueOf(int value){
        String stringValue = String.valueOf(value);
    }
}

Байткод для них будет следующий:

  public void intToStringConcat(int);
    Code:
       0: new           #2                  // class java/lang/StringBuilder
       3: dup
       4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
       7: iload_1
       8: invokevirtual #4                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      11: ldc           #5                  // String
      13: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      16: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      19: astore_2
      20: return

  public void intToStringValueOf(int);
    Code:
       0: iload_1
       1: invokestatic  #8                  // Method java/lang/String.valueOf:(I)Ljava/lang/String;
       4: astore_2
       5: return

Глядя на байткод можно сделать вывод что в случае конкатенации числа со строкой создается объект StringBuilder, выполняется два раза метод append() и затем toString().

Методы StringBuilder.append(int value) и Integer.toString(int values) в конечном счете вызывают метод Integer.getChars(). Однако для Android метод Integer.toString(int values) оптимизирован, путем кэширования значений в диапазоне -100..100. Не знаю правда c какой версии эта оптимизация добавилась, но тем не менее она есть.

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

Если надо преобразовать число в строку то лучше пользоваться методом Integer.toString(int values). Если необходимо сформировать строку из нескольких слов и чисел, то конкатенация не плохой вариант, если она не выполняется в цикле. Конкатенация в цикле вообще плохая идея.

temq
  • 5,857
  • 1
  • 12
  • 18
  • 1
    Смотреть в байткод ради смотрения в байткод бессмысленно. JIT С2 все заоптимизирует неочевидным для вас образом. Причем, чем очевиднее будет написан код, тем проще для JVM его будет оптимизировать в рантайме. – Nofate Jan 30 '17 at 21:26
  • @Notafe может и за оптимизирует, но это не означает что писать код надо по принципу "и так сойдёт". Лучше сразу написать более оптимально, чем потом надеется на какие то оптимизации в рантайме. – temq Jan 31 '17 at 08:12
  • Я не говорю, что надо писать абы как. Я больше про то, что гадание на байткоде - это именно гадание. А что до оптимизаций, на всех конференциях люди из Oracle говорят: пишите простой, понятный код, пока не станет очевидно, что узкое место в нем. – Nofate Jan 31 '17 at 09:07