JavaRush /Java блог /Random UA /Знайомство з String, StringBuffer та StringBuilder в Java...

Знайомство з String, StringBuffer та StringBuilder в Java

Стаття з групи Random UA
Для роботи з текстовими даними в Java є три класи: String , StringBuffer і StringBuilder . З першим кожен розробник стикається ще на початку вивчення мови. А що щодо двох, що залишабося? Які вони мають відмінності, і коли краще використовувати той чи інший клас? Загалом різниця між ними невелика, але краще у всьому розібратися на практиці :) Знайомство з String, StringBuffer та StringBuilder в Java - 1

Клас String

Цей клас є послідовністю символів. Усі визначені у програм рядкові літерали, на зразок This is String - це екземпляри класу String. String має дві фундаментальні особливості:
  • це immutable (постійний) клас
  • це final клас
Загалом, у класу String може бути спадкоємців ( final) і екземпляри класу не можна змінити після створення ( immutable). Це дає класу String кілька важливих переваг:
  1. Завдяки незмінності, хешкод екземпляра класу String кешується. Його не потрібно обчислювати щоразу, тому що значення полів об'єкта ніколи не зміняться після його створення. Це дає високу продуктивність при використанні даного класу як ключ для HashMap.

  2. Клас String можна використовувати у багатопотоковому середовищі без додаткової синхронізації.

  3. Ще одна особливість класу String — для нього перевантажений оператор +Java в Java. Тому конкатенація (складання) рядків виконується досить просто:

public static void main(String[] args) {
    String command = "Follow" + " " + "the" + " " + "white" + " " + "rabbit";
    System.out.println(command); // Follow the white rabbit
}
Під капотом конкатенація рядків виконується класом StringBuilder або StringBuffer (на розсуд компілятора) і способом append(про ці класи поговоримо трохи пізніше). Якщо ми складатимемо екземпляри класу String з екземплярами інших класів, останні будуть наводитися до рядкового подання:
public static void main(String[] args) {
    Boolean b = Boolean.TRUE;
    String result = "b is " + b;
    System.out.println(result); //b is true
}
Це ще одна цікава властивість класу String: об'єкти будь-яких класів можна привести до рядкового уявлення, використовуючи метод toString(), визначений у класі Objectта успадкований усіма іншими класами. Часто метод toString() об'єкта викликається неявно. Наприклад, коли ми виводимо щось на екран або складаємо String з об'єктом іншого класу. Клас String має ще одну особливість. Усі строкові літерали, визначені в Java коді, на кшталт "asdf", на етапі компіляції кешуються і додаються у пул рядків. Якщо ми запустимо наступний код:
String a = "Wake up, Neo";
String b = "Wake up, Neo";

System.out.println(a == b);
Ми побачимо в консолі true , тому що змінні aі bнасправді посилатимуться на той самий екземпляр класу String, доданий в пул рядків на етапі компіляції. Тобто різні екземпляри класу з однаковим значенням не створюються, і пам'ять економиться.

Недоліки:

Неважко здогадатися, що клас String потрібен насамперед для роботи з рядками. Але в деяких випадках перелічені вище особливості класу String можуть перетворитися з переваг на недоліки. Після створення рядків у коді Java-програми з ними часто відбувається безліч операцій:
  • переведення рядків у різні регістри;
  • вилучення підрядок;
  • конкатенація;
  • і т.д.
Давайте подивимося на цей код:
public static void main(String[] args) {

    String s = " Wake up, Neo! ";
    s = s.toUpperCase();
    s = s.trim();

    System.out.println("\"" + s + "\"");
}
З першого погляду здається, що ми перевели рядок "Wake up, Neo!" у верхній регістр видалабо з цього рядка зайві прогалини і обернули в лапки. Насправді, через незмінність класу String, у результаті кожної операції створюються нові екземпляри рядків, а старі відкидаються, породжуючи велику кількість сміття. Як уникнути нераціонального використання пам'яті?

Клас StringBuffer

Щоб впоратися зі створенням тимчасового сміття через модифікації об'єкта String, можна використовувати клас StringBuffer. Це mutableклас, тобто. змінюється. Об'єкт класу StringBuffer може містити певний набір символів, довжину і значення якого можна змінити через виклик певних методів. Подивимося, як працює цей клас. Для створення нового об'єкта використовується один із його конструкторів, наприклад:
  • StringBuffer() — створить порожній (який не містить символів) об'єкт
  • StringBuffer(String str) — створить об'єкт на основі змінної str (що містить усі символи str у тій самій послідовності)
Практика:
StringBuffer sb = new StringBuffer();
StringBuffer sb2 = new StringBuffer("Not empty");
Конкатенація рядків через StringBuffer Java виконується за допомогою методу append. Взагалі метод appendу класі StringBuffer перевантажений таким чином, що може приймати практично будь-який тип даних:
public static void main(String[] args) {
    StringBuffer sb = new StringBuffer();

    sb.append(new Integer(2));
    sb.append("; ");
    sb.append(false);
    sb.append("; ");
    sb.append(Arrays.asList(1,2,3));
    sb.append("; ");

    System.out.println(sb); // 2; false; [1, 2, 3];
}
Метод appendповертає об'єкт, на якому був викликаний (як і багато інших методів), що дозволяє викликати його "ланцюжком". Приклад вище можна написати так:
public static void main(String[] args) {
    StringBuffer sb = new StringBuffer();

    sb.append(new Integer(2))
            .append("; ")
            .append(false)
            .append("; ")
            .append(Arrays.asList(1,2,3))
            .append("; ");

    System.out.println(sb); // 2; false; [1, 2, 3];
}
Для роботи з рядками клас StringBuffer має ряд методів. Перерахуємо основні:
  • delete(int start, int end)- Видаляє підстроку символів починаючи з позиції start, закінчуючиend
  • deleteCharAt(int index)- Видаляє символ в позиціїindex
  • insert(int offset, String str)- Вставляє рядок strу позицію offset. Метод insertтакож перевантажений і може приймати різні аргументи
  • replace(int start, int end, String str)— замінить усі символи, починаючи з позиції startдо позиції endнаstr
  • reverse()- Змінює порядок всіх символів на протилежний
  • substring(int start)— поверне підрядок, починаючи з позиції start
  • substring(int start, int end)— поверне підрядок, починаючи з позиції startдо позиціїend
Повний перелік методів та конструкторів є в офіційній документації . Як працюють зазначені вище методи? Подивимося практично:
public static void main(String[] args) {
     String numbers = "0123456789";

     StringBuffer sb = new StringBuffer(numbers);

     System.out.println(sb.substring(3)); // 3456789
     System.out.println(sb.substring(4, 8)); // 4567
     System.out.println(sb.replace(3, 5, "ABCDE")); // 012ABCDE56789

     sb = new StringBuffer(numbers);
     System.out.println(sb.reverse()); // 9876543210
     sb.reverse(); // Повернем початковий порядок

     sb = new StringBuffer(numbers);
     System.out.println(sb.delete(5, 9)); // 012349
     System.out.println(sb.deleteCharAt(1)); // 02349
     System.out.println(sb.insert(1, "One")); // 0One2349
    }

Переваги:

  1. Як вже сказано, StringBuffer — клас, що змінюється, тому при роботі з ним не виникає такої ж кількості сміття в пам'яті, як зі String. Тому, якщо над рядками проводиться багато модифікацій, краще використовувати StringBuffer.

  2. StringBuffer – потокобезпечний клас. Його методи синхронізовані, а екземпляри можуть бути використані кількома потоками одночасно.

Недоліки:

З одного боку, потокобезпека – перевага класу, а з іншого – недолік. Синхронізовані методи працюють повільніше не синхронізовані. І тут у гру вступає StringBuilder. Давайте розберемося, що це за клас Java - StringBuilder, які методи є і в чому його особливості.

Клас StringBuilder

StringBuilder в Java - клас, що представляє послідовність символів. Він дуже схожий на StringBuffer у всьому, крім потокобезпеки. StringBuilder надає API, аналогічний API StringBuffer'a. Продемонструємо це вже на знайомому прикладі, замінивши оголошення змінних з StringBufer'а на StringBuilder:
public static void main(String[] args) {
    String numbers = "0123456789";

    StringBuilder sb = new StringBuilder(numbers);

    System.out.println(sb.substring(3)); //3456789
    System.out.println(sb.substring(4, 8)); //4567
    System.out.println(sb.replace(3, 5, "ABCDE")); //012ABCDE56789

    sb = new StringBuilder(numbers);
    System.out.println(sb.reverse()); //9876543210
    sb.reverse(); // Повернем початковий порядок

    sb = new StringBuilder(numbers);
    System.out.println(sb.delete(5, 9)); //012349
    System.out.println(sb.deleteCharAt(1)); //02349
    System.out.println(sb.insert(1, "One")); //0One2349
}
Різниця лише в тому, що StringBuffer є потокобезпечним, і всі його методи синхронізовані, а StringBuilder — ні. Це єдина особливість. StringBuilder в Java працює швидше StringBuffer'а завдяки несинхронізованості методів. Тому в більшості випадків, крім багатопотокового середовища, краще використовувати для програми Java StringBuilder. Резюмуємо все у порівняльній таблиці трьох класів:

String vs StringBuffer vs StringBuilder

String StringBuffer StringBuilder
Змінність Immutable(ні) mutable(так) mutable(так)
Розширюваність final(ні) final(ні) final(ні)
Потокобезпека Так, за рахунок незмінності Так, за рахунок синхронізації Ні
Коли використовувати При роботі з рядками, які рідко модифікуватимуться При роботі з рядками, які часто модифікуватимуться в багатопотоковому середовищі При роботі з рядками, які часто модифікуватимуться, в однопотоковому середовищі
Вивчити цю тему докладніше можна на другому рівні квесту Java Multithreading на курсі JavaRush:
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ