Обработка строк в Java. Часть I: String, StringBuffer, StringBuilder

Вступление


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

  1. String, StringBuffer, StringBuilder (реализация строк)
  2. Pattern, Matcher (регулярные выражения)

Реализация строк на Java представлена тремя основными классами: String, StringBuffer, StringBuilder. Давайте поговорим о них.

String


Строка — объект, что представляет последовательность символов. Для создания и манипулирования строками Java платформа предоставляет общедоступный финальный (не может иметь подклассов) класс java.lang.String. Данный класс является неизменяемым (immutable) — созданный объект класса String не может быть изменен. Можно подумать что методы имеют право изменять этот объект, но это неверно. Методы могут только создавать и возвращать новые строки, в которых хранится результат операции. Неизменяемость строк предоставляет ряд возможностей:

  • использование строк в многопоточных средах (String является потокобезопасным (thread-safe) )
  • использование String Pool (это коллекция ссылок на String объекты, используется для оптимизации памяти)
  • использование строк в качестве ключей в HashMap (ключ рекомендуется делать неизменяемым)

Создание


Мы можем создать объект класса String несколькими способами:

1. Используя строковые литералы:

String habr = "habrahabr";

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

System.out.print("habrahabr"); // создали объект и вывели его значение

2. С помощью конструкторов:

String habr = "habrahabr";
char[] habrAsArrayOfChars = {'h', 'a', 'b', 'r', 'a', 'h', 'a', 'b', 'r'};
byte[] habrAsArrayOfBytes = {104, 97, 98, 114, 97, 104, 97, 98, 114};
 
String first = new String();
String second = new String(habr);

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

String third = new String(habrAsArrayOfChars); // "habrahabr"
String fourth = new String(habrAsArrayOfChars, 0, 4); // "habr"

Конструкторы могут формировать объект строки с помощью массива символов. Происходит копирование массива, для этого используются статические методы copyOf и copyOfRange (копирование всего массива и его части (если указаны 2-й и 3-й параметр конструктора) соответственно) класса Arrays, которые в свою очередь используют платформо-зависимую реализацию System.arraycopy.

String fifth = new String(habrAsArrayOfBytes, Charset.forName("UTF-16BE")); // кодировка нам явно не подходит "桡扲慨慢�"

Можно также создать объект строки с помощью массива байтов. Дополнительно можно передать параметр класса Charset, что будет отвечать за кодировку. Происходит декодирование массива с помощью указанной кодировки (если не указано — используется Charset.defaultCharset(), который зависит от кодировки операционной системы) и, далее, полученный массив символов копируется в значение объекта.

String sixth = new String(new StringBuffer(habr));
String seventh = new String(new StringBuilder(habr));

Ну и наконец-то конструкторы использующие объекты StringBuffer и StringBuilder, их значения (getValue()) и длину (length()) для создания объекта строки. С этими классами мы познакомимся чуть позже.

Приведены примеры наиболее часто используемых конструкторов класса String, на самом деле их пятнадцать (два из которых помечены как deprecated).

Длина


Важной частью каждой строки есть ее длина. Узнать ее можно обратившись к объекту String с помощью метода доступа (accessor method) length(), который возвращает количество символов в строке, например:

public static void main(String[] args) {
    String habr = "habrahabr";
    // получить длину строки
    int length = habr.length();
    // теперь можно узнать есть ли символ символ 'h' в "habrahabr"
    char searchChar = 'h';
    boolean isFound = false;
    for (int i = 0; i < length; ++i) {
        if (habr.charAt(i) == searchChar) {
            isFound = true;
            break; // первое вхождение
        }
    }
    System.out.println(message(isFound)); // Your char had been found!
    // ой, забыл, есть же метод indexOf
    System.out.println(message(habr.indexOf(searchChar) != -1)); // Your char had been found!
}

private static String message(boolean b) {
    return "Your char had" + (b ? " " : "n't ") + "been found!";
}

Конкатенация


Конкатенация — операция объединения строк, что возвращает новую строку, что есть результатом объединения второй строки с окончанием первой. Операция для объекта String может быть выполнена двумя способами:

1. Метод concat

String javaHub = "habrhabr".concat(".ru").concat("/hub").concat("/java");
System.out.println(javaHub); // получим "habrhabr.ru/hub/java"
// перепишем наш метод используя concat
private static String message(boolean b) {
    return "Your char had".concat(b ? " " : "n't ").concat("been found!");
}

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

2. Перегруженные операторы "+" и "+="

String habr = "habra" + "habr"; // "habrahabr"
habr += ".ru"; // "habrahabr.ru"

Это одни с немногих перегруженных операторов в Java — язык не позволяет перегружать операции для объектов пользовательских классов. Оператор "+" не использует метод concat, тут используется следующий механизм:

String habra = "habra";
String habr = "habr";
// все просто и красиво
String habrahabr = habra + habr;
// а на самом деле
String habrahabr = new StringBuilder()).append(habra).append(habr).toString(); // может быть использован StringBuffer

Используйте метод concat, если слияние нужно провести только один раз, для остальных случаев рекомендовано использовать или оператор "+" или StringBuffer / StringBuilder. Также стоит отметить, что получить NPE (NullPointerException), если один с операндов равен null, невозможно с помощью оператора "+" или "+=", чего не скажешь о методе concat, например:

String string = null;
string += " habrahabr"; // null преобразуется в "null", в результате "null habrahabr"
string = null;
string.concat("s"); // логично что NullPointerException

Форматирование


Класс String предоставляет возможность создания форматированных строк. За это отвечает статический метод format, например:

String formatString = "We are printing double variable (%f), string ('%s') and integer variable (%d).";
System.out.println(String.format(formatString, 2.3, "habr", 10));
// We are printing double variable (2.300000), string ('habr') and integer variable (10).

Методы


Благодаря множеству методов предоставляется возможность манипулирования строкой и ее символами. Описывать их здесь нет смысла, потому что Oracle имеет хорошие статьи о манипулировании и сравнении строк. Также у вас под рукой всегда есть их документация. Хотелось отметить новый статический метод join, который появился в Java 8. Теперь мы можем удобно объединять несколько строк в одну используя разделитель (был добавлен класс java.lang.StringJoiner, что за него отвечает), например:

String hello = "Hello";
String habr = "habrahabr";
String delimiter = ", ";

System.out.println(String.join(delimiter, hello, habr));
// или так
System.out.println(String.join(delimiter, new ArrayList<CharSequence>(Arrays.asList(hello, habr))));
// в обоих случаях "Hello, habrahabr"

Это не единственное изменение класса в Java 8. Oracle сообщает о улучшении производительности в конструкторе String(byte[], *) и методе getBytes().

Преобразование


1. Число в строку

int integerVariable = 10;
String first = integerVariable + ""; // конкатенация с пустой строкой
String second = String.valueOf(integerVariable); // вызов статического метода valueOf класса String
String third = Integer.toString(integerVariable); // вызов метода toString класса-обертки

2. Строку в число

String string = "10";
int first = Integer.parseInt(string); 
/* 
   получаем примитивный тип (primitive type) 
   используя метод parseXхх нужного класса-обертки,
   где Xxx - имя примитива с заглавной буквы (например parseInt) 
*/
int second = Integer.valueOf(string); // получаем объект wrapper класса и автоматически распаковываем


StringBuffer


Строки являются неизменными, поэтому частая их модификация приводит к созданию новых объектов, что в свою очередь расходует драгоценную память. Для решения этой проблемы был создан класс java.lang.StringBuffer, который позволяет более эффективно работать над модификацией строки. Класс является mutable, то есть изменяемым — используйте его, если Вы хотите изменять содержимое строки. StringBuffer может быть использован в многопоточных средах, так как все необходимые методы являются синхронизированными.

Создание


Существует четыре способа создания объекта класса StringBuffer. Каждый объект имеет свою вместимость (capacity), что отвечает за длину внутреннего буфера. Если длина строки, что хранится в внутреннем буфере, не превышает размер этого буфера (capacity), то нет необходимости выделять новый массив буфера. Если же буфер переполняется — он автоматически становиться больше.

StringBuffer firstBuffer = new StringBuffer(); // capacity = 16
StringBuffer secondBuffer = new StringBuffer("habrahabr"); // capacity = str.length() + 16
StringBuffer thirdBuffer = new StringBuffer(secondBuffer); // параметр - любой класс, что реализует CharSequence
StringBuffer fourthBuffer = new StringBuffer(50); // передаем capacity

Модификация


В большинстве случаев мы используем StringBuffer для многократного выполнения операций добавления (append), вставки (insert) и удаления (delete) подстрок. Тут все очень просто, например:

String domain = ".ru";
// создадим буфер с помощью String объекта
StringBuffer buffer = new StringBuffer("habrahabr"); // "habrahabr"
// вставим домен в конец
buffer.append(domain); // "habrahabr.ru"
// удалим домен
buffer.delete(buffer.length() - domain.length(), buffer.length()); // "habrahabr"
// вставим домен в конец на этот раз используя insert
buffer.insert(buffer.length(), domain); // "habrahabr.ru"

Все остальные методы для работы с StringBuffer можно посмотреть в документации.

StringBuilder


StringBuilder — класс, что представляет изменяемую последовательность символов. Класс был введен в Java 5 и имеет полностью идентичный API с StringBuffer. Единственное отличие — StringBuilder не синхронизирован. Это означает, что его использование в многопоточных средах есть нежелательным. Следовательно, если вы работаете с многопоточностью, Вам идеально подходит StringBuffer, иначе используйте StringBuilder, который работает намного быстрее в большинстве реализаций. Напишем небольшой тест для сравнения скорости работы этих двух классов:

public class Test {
    public static void main(String[] args) {
        try {
            test(new StringBuffer("")); // StringBuffer: 35117ms.
            test(new StringBuilder("")); // StringBuilder: 3358ms.
        } catch (java.io.IOException e) {
            System.err.println(e.getMessage());
        }
    }
    private static void test(Appendable obj) throws java.io.IOException {
        // узнаем текущее время до теста 
        long before = System.currentTimeMillis();
        for (int i = 0; i++ < 1e9; ) {
            obj.append("");
        }
        // узнаем текущее время после теста 
        long after = System.currentTimeMillis();
        // выводим результат
        System.out.println(obj.getClass().getSimpleName() + ": " + (after - before) + "ms.");
    }
}

Спасибо за внимание. Надеюсь статья поможет узнать что-то новое и натолкнет на удаление всех пробелов в этих вопросах. Все дополнения, уточнения и критика приветствуются.
Поделиться публикацией
Комментарии 68
    –1
    Это перевод? Отметьте, откуда
      0
      это мой оригинальный текст
        +2
        Мне всегда было интересно хоть кто-то нашел применение StringBuffer. Не очень понятно в каком случае он может пригодиться. Попеременно дописывать какие-то строки и потом их куда-то выводить. Получается в такой строке порядок должен быть не очень важен. Могу подумать только о логировании и чем-то подобном, но для этого есть масса других средств.
          0
          именно StringBuffer? или в целом подход StringBuilder/StringBuffer?
            +1
            Конечно, именно StringBuffer. StringBuilder-то один из самых часто используемых классов в стандартной библиотеке.
              +2
              У меня только одно объяснение: по историческим причинам и в силу того, что java дает обратную совместимость

               * @since       1.5
               */
              public final class StringBuilder
              


               * @since   JDK1.0
               */
               public final class StringBuffer
              
      +7
      String second = new String(habr); // аналогично second = habr;

      Разве аналогично?
      1. String second = new String(habr) — создается новый объект second с тем же содержимым, что и в объекте habr. При этом second и habr ссылаются на разные объекты.
      2. String second = habr — копируется ссылка на объект habr, то есть second и habr ссылаются на один объект.
      Верно?
        0
        Опечатался, спасибо. Все верно отметили.
        Хотелось бы дополнить и сказать, что все ссылки на string объекты хранится в String Pool и перед созданием строки с помощью литерала проверяется нет ли эквивалентной строки в пуле, если нет — добавляется, иначе просто получаем ссылку на уже готовый объект. В случаи с new, новый объект создаться в любом случаи, независимо от пула.
          0
          Вам спасибо за статью! Давайте продолжение.

          (Чисто языковой комментарий: слово «что», конечно, заменяет слово «который», но не без потерь: оно не передает род заменяемого слова, поэтому текст воспринимается хуже. Пример:
          «если длина строки, что хранится...» — к чему здесь относится «что»: к длине или к строке? Конкретно в этом случае подойдет причастие:
          «длина строки, хранящаяся...» или
          «длина строки, хранящейся...»)
            0
            спасибо, исправил :)
        +6
        А теперь запустите тот же тест с -XX:BiasedLockingStartupDelay=0:
        StringBuffer: 5590ms.
        StringBuilder: 5493ms.
        

        И чего в итоге померили?

        Также изменения коснулись hashCode (новый алгоритм hashing)
        Оппа! Как такое может быть, если алгоритм хеширования для String строго прописан в спецификации?
          +3
          А при добавлении третьей строчки у меня вообще весело получается:
              test(new StringBuffer("")); // StringBuffer: 5020ms.
              test(new StringBuilder("")); // StringBuilder: 14041ms.
              test(new StringBuilder("")); // StringBuilder: 10716ms.
          
            0
            Учитывая цикл 1e9 итераций и что StringBuilder/StringBuffer расширяются — может произойти GC, наверняка может случится и JIT — словом, что именно происходило в фоне — неизвестно, так, что можно гадать на кофейной гуще, что же именно мерялось.
            0
            Алгоритм действительно не изменился (где-то прочитал, но не проверял тогда), спасибо.
            +1
            Вдобавок к вышеперечисленному, хочу добавить, что одним из важных аспектов, почему String объявлен как final — это безопасность. Например, когда загружаем класс, то имя класса передается в виде строки. Если бы строки были не финальные, то вредоносный код мог бы изменить имя класса и, таким образом, загрузить неправильный класс. Например, вместо java.io.FileReader был бы загружен com.malicious.FileRemover. Также можно было бы сломать контракт equals/hashCode.

            Для обхода всех этих проблем, возможно, был бы введен класс java.lang.SafeString, который бы помог предотвратить эти проблемы, но попутно создал бы новых (например, конвертацию из SafeString в String и обратно, замусоривание API). Но так как это один из базовых классов, то было принято решение сделать класс String финальным.
              +3
              В принципе не плохая статья, но ни слова о String pool, злом методе intern и злом методе substring(до версии 1.7.0_06)
                +3
                Просто оставлю это здесь

                  –1
                  А я это видел и итак знаю)
                    0
                    Это ни в коем случае не камень в ваш огород, просто логическое продолжение замечания, учитывая то, что этого доклада в комментариях еще не было
                  0
                  Метод substring напротив был добрым до 1.7.0_06, если им правильно пользоваться =)
                    0
                    Ну вообще да, смотря что называть «добротой» трату памяти или трату времени)
                      +2
                      При правильном использовании substring мог даже сэкономить память: можно было на один char[]-массив повесить тыщу строк, снижая оверхед на заголовки каждого массива. К примеру, вы загружаете из базы readonly-словарь, который всё равно будете держать всегда целиком в памяти. Можно было конкатенировать все строки и разбить по substring.
                  +1
                  Маленькое уточнение по поводу оператора "+". Как с казано в документации к Oracle JDK 6, 7 и 8, реализация этого оператора сделана с использованием StringBuilder:

                  The Java language provides special support for the string concatenation operator ( + ), and for conversion of other objects to strings. String concatenation is implemented through the StringBuilder(or StringBuffer) class and its append method. String conversions are implemented through the method toString, defined by Object and inherited by all classes in Java.
                  (ссылка на v8)

                  Что также отражено в спецификации:
                  An implementation may choose to perform conversion and concatenation in one step to avoid creating and then discarding an intermediate String object. To increase the performance of repeated string concatenation, a 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.

                  For primitive types, an implementation may also optimize away the creation of a wrapper object by converting directly from a primitive type to a string.
                  (ссылка)

                  Эта тема также поднималась на StackOverflow и там был детально описан процесс конкатенации (ссылка).
                    0
                    Как сказано в документации к Oracle JDK 6, 7 и 8


                    это не документация к Oracle JDK. Это javadoc, то есть, документация к стандарту Java SE (6, 7, 8).
                      0
                      String habr = new StringBuilder(String.valueOf("habra")).append("habr").toString();
                      

                      это уточнения было в статьи
                        +3
                        Объясните — откуда взялось это убожество String.valueOf?

                        public String s(String v, String f){
                            return v + f;
                        }
                        


                        и его bytecode
                         public java.lang.String s(java.lang.String, java.lang.String);
                            descriptor: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
                            flags: ACC_PUBLIC
                            Code:
                              stack=2, locals=3, args_size=3
                                 0: new           #2                  // class java/lang/StringBuilder
                                 3: dup
                                 4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
                                 7: aload_1
                                 8: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
                                11: aload_2
                                12: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
                                15: invokevirtual #5                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
                                18: areturn
                              LineNumberTable:
                                line 3: 0
                        


                        т.е + это синтетический сахар, а на деле

                        public String s(String v, String f){
                            return new StringBuilder().append(v).append(f).toString();
                        }
                        

                        и никаких String.valueOf — не вводите людей в заблуждение.

                        И вы уверены, что в следующем методе используется конкатенация?
                        public String q(){
                        return "ha" + "br";
                        }


                        Данный строковый литерал может быть вычислен уже на стадии компиляции и javac подставляет уже результат
                        Constant pool:
                           #2 = String             #20            // habr
                        
                         public java.lang.String q();
                            descriptor: ()Ljava/lang/String;
                            flags: ACC_PUBLIC
                            Code:
                              stack=1, locals=1, args_size=1
                                 0: ldc           #2                  // String habr
                                 2: areturn
                              LineNumberTable:
                                line 3: 0
                        


                        Что может привести к неожиданным результатам:
                        К примеру, вы используете константу из сторонней библиотеки, которая скажем возвращает ее версию в виде строки, н-р «v1». Если вы обновите эту библиотеку (которая возвращает «v2») без пересборки класса, который ее использует — результат будет по прежнему «v1» ибо именно это значение было подставлено на стадии компиляции.

                        Hack, который позволяет обходить данные грабли — использование метода intern — как например тут. По сути строковый литерал уже находится в пуле строк и никакого выигрыша/проигрыша нет, кроме того, что теперь javac не может (по крайней мере пока) вычислить значение на стадии компиляции и уже будет честно загружать класс и его константу.
                          0
                          String habrahabr = new StringBuilder()).append(habra).append(habr).toString();
                          


                          вы правы, исправил
                            0
                            поддерживаю. Проверить очень легко: скомпирировать конкатенацию, а потом декомпилировать.
                            Даже в байткод руками лазать не надо :)
                              +2
                              скомпирировать… декомпилировать.


                              Настоящим пацанам нужна только консолька, vi, javac да javap для этого
                        +6
                        Напишем небольшой тест для сравнения скорости работы этих двух классов


                        Возьми вольтметр и измерь!
                          –2
                          предложите свой вариант
                            +6
                            Прежде, чем писать любые перфомансные тесты и юзать всякие System.currentTimeMillis(), мы с vladimir_dolzhenko настоятельно рекомендуем Вам ознакомиться вот с этой лекцией:
                              +1
                              спасибо, посмотрю, перепишу тест :)
                                +3
                                Всецело согласен с Лешей 23derevo, но стоит добавить, что одной лекцией не стоит ограничится, чтобы понять как же измерять и пользоваться jmh, но вектор правильный.

                                Стоит обратить внимание, что в java есть такая вещь как Escape Analysys, которая делает много волшебных вещей, н-р, стирание блокировок, если экземпляр никуда не утекает.

                                Т.е в однопоточном случае разница между StringBuilder и StringBuffer будет в пределах погрешности измерений (после того, как JIT свернет). Это следует учитывать, когда будете писать jmh-тест.
                          +2
                          Используйте метод concat, если слияние нужно провести только один раз, для остальных случаев рекомендовано использовать или оператор "+" или StringBuffer / StringBuilder.
                          Никогда не используйте concat. Обычный "+" или StringBuilder всегда будет эффективней, даже всего для двух строк. Просто потому, что JVM оптимизирует "+" специальным образом (это JVM intrinsic), а concat — нет (это обычный Java метод).
                            0
                            Можно подробнее про JVM intrinsic  используемый в +?

                            Как бы байткод всегда показывает цепочку new StringBuilder().append()...toString() и не знал, что intrinsic может действовать над цепочкой
                            0
                            в теории ведь никто не мешает concat реализовать как JVM intrinsic?
                              0
                              Представляешь как было бы… фантастично-феерично иметь pluggable intrinsic?
                                0
                                а чего там представлять? Есть такая штука — OpenJDK. Она некоторыми и используется именно так, как ты хочешь: Taobao JVM (слайды 34-37)
                                  0
                                  Все клева конечно, только доклад 2011.
                                  Почему-то на ум приходит академическая разработка RedHat под названием Shenandoah — вызов Zing VM и С4 — но только больше о них ничего не слышно.
                                    0
                                    погоди, тут другой случай :)

                                    Как я понял — это просто кастомизированная OpenJDK (classlib, jvm), работающая внутри Taobao. Они делают определенные изменения и оптимизации, заточенные под их конкретные юзкейсы (ворклоады, железо, сценарии и т.п.). И как я понимаю, именно поэтому они не всегда коммитят назад в OpenJDK — их изменения не всем подойдут.
                                      +1
                                      Я вообще о том, что ежели продукт пошел — то должны быть упоминания и развитие, кроме данного доклада.

                                      Но все равно было бы варенье-с-маслом видеть pluggable intrinsic в java-по-умолчанию
                                        +3
                                        Ну вот смотри, у нас в Одноклассниках тоже есть свои сборки OpenJDK патченные — и classlib и JVM. Мы об этом упоминаем периодически, но чтобы прямо кричать об этом на каждом углу…

                                        Многие, кстати, патчат. Например, тот же гугл. Посмотри доклады Jeremy Manson с JVMLS 2013 и JVMLS 2014. И тоже об этом мало в интернете есть. Потому что это больше не инфоповод и не рокит саенс.
                                0
                                В теории — да. На практике вряд ли его напишут, потому что его мало используют. Практический выход никакой, зато трудозатраты на написание низкоуровневого кода и новое место для багов. Многие куда более полезные интринсики (например, Array.get) и то отсутствуют.
                                +1
                                На самом деле дело ещё в том, что по факту внутренняя реализация оператора "+" не специфицирована строго. Спецификация не требует, чтобы это обязательно был StringBuilder. Вон Алексей колбасит новую реализацию на invokedynamic. Если что-то получится, может работать ещё быстрее.

                                String.concat может быть полезен, если ожидается много пустых строк на входе. Тогда он просто вернёт непустую строку, не создавая новых объектов вообще. Это будет быстрее, чем "+", даже с учётом интринсиков: спецификация обязывает "+" возвращать новый объект (если это не compile-time constant).
                                +3
                                Мне, кстати, не очень понятно, почему операторы "+" и "+=" названы «перегруженными». Можно ли считать, что оператор деления "/" «перегружен», потому что он ведёт себя по-разному для целых чисел и для дробных? По-моему, слово «перегружен» здесь неуместно вообще. Обычно его используют для пользовательской перегрузки в тех языках, где она поддерживается (в Java — нет). А так это просто «оператор конкатенации строк».
                                  –2
                                  Оператор "+" «перегружен» для строк, потому что главное его назначение это сложения для числовых примитивных типов данных.
                                    +1
                                    А на каком основании вы полагаете, что одно применение «главнее», чем другое?
                                      0
                                      литералы числовых типов используются для создания примитивных типов, строковые литералы же используются для создания объектов класса String.

                                      если ошибаюсь, с радостью узнаю истину :)
                                        0
                                        а как эта ваша реплика связана с моим вопросом? :)
                                          –3
                                          с этой реплики следует, что операции с примитивными типами «главнее» за операции с объектами, имхо
                                            0
                                            вот-вот :) и «имхо» здесь ключевое слово.
                                          +1
                                          В языке Java некоторые объекты обрабатываются специальным образом на уровне синтаксиса или имеют специальное значение. К примеру, после слова throw может идти только объект класса Throwable или его наследник. Вот и String имеет специальное значение не только как часть библиотеки, но и как часть самого языка.

                                          Кстати, вот вам такой код:

                                          Integer a = 1;
                                          Integer b = 2;
                                          Integer c = a + b;
                                          Integer d = a - b;

                                          Здесь мы применяем плюс и минус к объектам. Значит ли это по вашему, что и тут эти операторы «перегружены»?
                                            0
                                            тот же пример хотел скинуть, но ты меня опередил :)
                                              0
                                              нет, здесь операторы не перегружены, потому что операции совершаются над примитивными типами, а не над объектами, в которые «завернуты» эти типы:

                                                      Integer a = Integer.valueOf(1);
                                                      Integer b = Integer.valueOf(2);
                                                      Integer с = Integer.valueOf(a.intValue() + b.intValue());
                                                      Integer d = Integer.valueOf(a.intValue() - b.intValue());
                                              
                                                +4
                                                Что-то вы не последовательны — значит как строки складывать — так + перегружен, а как Integer/Long/Double и т.п — так не перегружен.

                                                Откуда же тогда берется эта магия с unboxing?
                                                0
                                                И да, я понимаю, что объекты некоторых классов имеет «особый статус» и их обработка оговорена в спецификации.
                                                  +3
                                                  С моей точки зрения многие люди часто путают Java и C++ — как-то generics vs templates или перегрузку операторов с особыми случаями синтаксиса.

                                                  В этом смысле и string + string и number + number в каком-то смысле «перегружены», что для данных классов на уровне языка определен расширенный синтаксис (или если угодно синтаксический сахар). В самом деле, в java же невозможно задать некоторый свой класс my.package.Vector(x,y) и определить для него + как операцию сложения векторов на уровне синтаксиса, тогда как в C++ / groovy / др это можно сделать легко — и это неловкое чувство, что some animals are more equal.
                                                    +1
                                                    lany, vladimir_dolzhenko, 23derevo, спасибо за помощь, разобрался, почитал спецификацию.

                                                    Не понимаю людей, которые яро минусуют посты, где обсуждаются вопросы по теме. Я пытаюсь разобраться, докопаться до истины, высказывая свои мысли. Это ни в коем случаи не камень в огород знаний собеседников.
                                          +2
                                          А StringJoiner в Java-8 адски медленный. В девятке разогнали.
                                            +1
                                            Ну ты же наверняка разобрался почему. Ждем статью с подробностями.
                                              +2
                                              А что там описывать? В восьмёрке под капотом обычный StringBuilder. В девятке элементы складывают в массив и подсчитывают суммарную длину, а итоговую строку создают через приватный конструктор (SharedSecrets, всё такое). В итоге массив char[] создаётся ровно один раз вместо того, чтобы его пересоздавать и копировать, когда он кончается (а в конце ещё раз пересоздавать и копировать, потому что его длина почти наверняка не совпадает с длиной результирующей строки). Я сам хотел подобную оптимизацию предложить, но потом догадался в девятке посмотреть. В общем-то на целую статью этот рассказ вроде не тянет.
                                                +1
                                                В любом случае от тебя давно статей не было. Было бы прекрасно увидеть очередной труд.

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                          Самое читаемое