Когда только начинал с Android, не мог понять — почему так много возни со строками? TextView, Toast, каждый диалог, логи... везде String. Причём постоянно приходилось гуглить то один метод, то другой. В итоге решил сделать себе такую шпаргалку, чтобы всё было под рукой. Может, и тебе пригодится.Класс String - 1

Первое и главное про String

String в Java (и в Android соответственно) — штука неизменяемая. То есть immutable, как говорят умные люди. Что это значит на практике? А вот что:
String name = "Барсик";
name = name + " рыжий"; // создается новая строка, старая уходит в мусор
То есть каждый раз, когда ты что-то делаешь со строкой, создается новый объект. Старый просто выбрасывается. Это важно понимать, потому что если ты начнёшь в цикле складывать строки через плюс, у тебя приложение начнёт тормозить. Особенно на слабых устройствах это заметно. Для таких случаев придумали StringBuilder (есть ещё StringBuffer, но он для многопоточности, в обычных случаях не нужен). Вот пример - допустим, мне надо собрать список имён котов:
// так делать не надо!
String result = "";
for(int i = 0; i < catNames.length; i++) {
    result = result + catNames[i] + ", ";
}

// а вот так нормально
StringBuilder sb = new StringBuilder();
for(int i = 0; i < catNames.length; i++) {
    sb.append(catNames[i]);
    sb.append(", ");
}
String result = sb.toString();
Кстати, все эти классы (String, StringBuilder, StringBuffer) в пакете java.lang, так что импортить не надо. И все они реализуют CharSequence — помнишь, когда в TextView метод setText принимает CharSequence? Вот поэтому туда можно и String передать, и StringBuilder.

Как создавать строки

Ну тут всё просто, самый обычный способ:
String aboutCat = "Кот - это звучит гордо, а если наступить на хвост, то громко";
Массив строк — тоже без проблем:
String[] cats = {"Васька", "Барсик", "Мурзик"};
Можно и через конструктор, хотя честно — зачем?
String str = new String(); // пустая строка, но обычно просто "" проще
Иногда нужно создать строку из массива символов (да, бывает и такое):
char[] chars = {'c', 'a', 't'};
String str = new String(chars);
А вот это уже интереснее — можно взять только часть массива:
char[] chars = {'c', 'a', 't', 'a', 'm', 'a', 'r', 'a', 'n'};
String str = new String(chars, 0, 3); 
// берём 3 символа начиная с позиции 0, получается "cat"

Строковые ресурсы в Android

Это важная штука. В Android не принято писать строки прямо в коде (ну можно, конечно, но потом замучаешься всё искать). Правильно — держать тексты в файле strings.xml. Во-первых, удобно всё в одном месте, во-вторых, если захочешь сделать приложение на разных языках, не придётся весь код перелопачивать. Достать строку из ресурсов можно так:
String catName = getResources().getString(R.string.cat_name);
Если работаешь во фрагменте, немного по-другому:
String catName = requireContext().getResources().getString(R.string.cat_name);
Хотя если честно, я обычно просто пишу:
String catName = getString(R.string.cat_name); // работает и в Activity, и во Fragment
Для массива строк:
Resources res = getResources();
String[] cats = res.getStringArray(R.array.cats_array);

Форматирование — вещь!

Вот это реально удобно. Представь — у тебя приложение про котов (почему бы нет?). Надо выводить информацию о каждом. Можно было бы для каждого кота отдельную строку в ресурсах делать... но это же глупо. Делаешь в strings.xml шаблон:
<string name="cat_info">Кот по имени %1$s, у него %2$s лапы, %3$s хвост. Ему %4$d лет</string>
Эти штуки с процентами — позиции для параметров. %s это для строк, %d для чисел. А дальше в коде:
String catName = "Барсик";
String paws = "четыре";
String tail = "один";
int age = 5;

String catInfo = getResources().getString(R.string.cat_info);
String result = String.format(catInfo, catName, paws, tail, age);

TextView tvInfo = findViewById(R.id.tvCatInfo);
tvInfo.setText(result);
И всё! Появился кот Васька, ему 6 лет? Просто меняешь переменные, а текст остаётся тем же. Кстати, есть способ ещё короче — getString() умеет сразу подставлять параметры:
String result = getString(R.string.cat_info, catName, paws, tail, age);
tvInfo.setText(result);
Намного лаконичнее же.

Склеивание строк

В Java для строк работает оператор +. Просто складываешь их:
String cat = "Кот";
String name = "Васька";
String fullname = cat + " " + name; // "Кот Васька"
Обрати внимание на пробел — без него слипнется в "КотВаська". Java умная, сама приводит разные типы к строке:
int pawsCount = 4;
String paws = " лапы";
String catInfo = pawsCount + paws; // станет "4 лапы"
Но тут есть нюанс. Если ты начнёшь складывать строки в цикле, будет беда. Помнишь, я говорил что String неизменяемый? Вот смотри что происходит:
// так делать нельзя!
String result = "";
for(String name : catNames) {
    result += name + ", "; // каждый раз новый объект создаётся!
}
Если у тебя там 100 имён котов, то в памяти создастся 100 объектов, которые тут же станут мусором. GC (сборщик мусора) начнёт работать, приложение потормаживать будет. Особенно на слабых телефонах заметно. Правильно делать через StringBuilder:
StringBuilder sb = new StringBuilder();
for(String name : catNames) {
    sb.append(name).append(", ");
}
String result = sb.toString();
Или можно даже так:
StringBuilder sb = new StringBuilder();
for(int i = 0; i < catNames.length; i++) {
    sb.append(catNames[i]);
    if(i < catNames.length - 1) {
        sb.append(", ");
    }
}
String result = sb.toString();
Второй вариант не добавит лишнюю запятую в конце. Хотя если честно, я обычно просто потом trim() делаю, если что :)

Методы String: то, что реально нужно

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

charAt(int index)

Возвращает символ по индексу. Индексы с нуля, как обычно.
String catName = "Котёнок";
char symbol = catName.charAt(2); // вернёт 'т'

TextView tv = findViewById(R.id.textView);
tv.setText(String.valueOf(symbol)); // выведет т
Кстати, тут такая штука — если написать просто tv.setText(symbol), то упадёт с ошибкой. Потому что Android думает что ты передаёшь resource ID, а не символ. Поэтому нужен String.valueOf(). Я раз пять на это попадался, пока не запомнил.

length()

Возвращает длину строки. Проще некуда, но используется везде:
String input = editText.getText().toString();
if(input.length() < 3) {
    Toast.makeText(this, "Минимум 3 символа!", Toast.LENGTH_SHORT).show();
}

substring(int start) и substring(int start, int end)

Вырезает кусок строки. Вторая версия берёт символы от start до end - 1 (да, именно минус один, без последнего символа — это частая ошибка).
String word = "скотина";
String cat = word.substring(1, 4); // получим "кот"

TextView tv = findViewById(R.id.textView);
tv.setText(cat);
Или реальный пример — достать имя файла из пути:
String filePath = "/storage/emulated/0/Pictures/cat.jpg";
int lastSlash = filePath.lastIndexOf('/');
String fileName = filePath.substring(lastSlash + 1); 
// fileName будет "cat.jpg"

toLowerCase() и toUpperCase()

Переводят в нижний или верхний регистр:
String cat = "Кот";
String lower = cat.toLowerCase(); // кот
String upper = cat.toUpperCase(); // КОТ

TextView tv = findViewById(R.id.textView);
tv.setText(upper);

trim()

Убирает пробелы с краёв. Незаменимая вещь при работе с пользовательским вводом:
String input = editText.getText().toString().trim();
if(input.isEmpty()) {
    // пользователь просто пробелов понатыкал
}
Вообще, связка .getText().toString().trim() встречается в Android-коде настолько часто, что я её уже на автомате пишу. Как заклинание какое-то)

replace(CharSequence target, CharSequence replacement)

Заменяет одну подстроку на другую. Все вхождения сразу меняет:
String text = "кит";
String result = text.replace("и", "о"); 
// получится "кот"

TextView tv = findViewById(R.id.textView);
tv.setText(result);

split(String regex)

Разбивает строку на массив. Очень полезная штука, часто использую для парсинга:
String catNames = "Васька Рыжик Мурзик Барсик";
String[] cats = catNames.split(" "); 

// выведем в лог каждое имя
for(int i = 0; i < cats.length; i++) {
    Log.d("CatApp", "Кот #" + i + ": " + cats[i]);
}
В реальности часто нужно парсить CSV или что-то типа того. Например:
String data = "Васька,5,рыжий";
String[] parts = data.split(",");
String name = parts[0];  // Васька
String age = parts[1];   // 5
String color = parts[2]; // рыжий

contains(CharSequence s)

Проверяет, есть ли подстрока в строке:
String word = "котёнок";
if(word.contains("кот")) {
    Toast.makeText(this, "Нашли кота!", Toast.LENGTH_SHORT).show();
}

startsWith() и endsWith()

Проверяют начало и конец строки:
String filename = "photo_cat.jpg";

if(filename.endsWith(".jpg")) {
    // это картинка
} else if(filename.endsWith(".mp4")) {
    // это видео
}
Ещё есть вариант startsWith с указанием позиции:
String word = "Суккот";
if(word.startsWith("кот", 3)) { 
    // ищем "кот" начиная с 3 позиции
    Toast.makeText(this, "Есть кот!", Toast.LENGTH_SHORT).show();
}

equals() и equalsIgnoreCase()

Сравнение строк — больная тема для новичков. Я сам когда-то потратил пару часов на баг, который оказался из-за ==. Запомни навсегда: строки сравниваются через equals(), а не через ==!
String input = editText.getText().toString();

// так НЕЛЬЗЯ!
if(input == "admin") { 
    // может сработать, а может нет, это рулетка
}

// а вот так ПРАВИЛЬНО
if(input.equals("admin")) {
    // вход разрешён
}
Ещё лучше писать так (на случай если input окажется null):
if("admin".equals(input)) {
    // так безопаснее
}
Если регистр не важен:
if("admin".equalsIgnoreCase(input)) {
    // "Admin", "ADMIN", "aDmIn" - всё пройдёт
}

indexOf() и lastIndexOf()

Возвращают позицию подстроки. Если не нашли — вернут -1.
String email = "user@example.com";
int atPos = email.indexOf('@');

if(atPos > 0) {
    String username = email.substring(0, atPos); // user
    String domain = email.substring(atPos + 1);  // example.com
}
Или достать расширение файла:
String filename = "my_cat.jpg";
int dotIndex = filename.lastIndexOf('.');

if(dotIndex != -1) {
    String ext = filename.substring(dotIndex + 1); // jpg
}

isEmpty() и проверка на пустоту

isEmpty() просто проверяет длину:
String str1 = "";
String str2 = "   ";

str1.isEmpty(); // true
str2.isEmpty(); // false, там же пробелы!
В Android есть ещё удобная штука — TextUtils:
if(TextUtils.isEmpty(str)) {
    // строка пустая или вообще null
}
Я обычно так и делаю, чтобы не проверять на null отдельно.

valueOf()

Конвертирует всё подряд в строку:
int catAge = 7;
TextView tv = findViewById(R.id.textView);
tv.setText(String.valueOf(catAge)); 
Это важно! Если напишешь просто tv.setText(catAge), то Android решит что 7 это resource ID и попытается найти строковый ресурс с таким номером. Упадёт с ошибкой типа "Resource not found". Я на это раз пять попадался, если честно.

compareTo() и compareToIgnoreCase()

Лексикографическое сравнение. Возвращает 0 если строки равны, отрицательное число если первая "меньше", положительное если "больше". Используется обычно для сортировки:
String[] cats = {"Мурзик", "Барсик", "Васька", "Рыжик"};

Arrays.sort(cats, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

// теперь массив отсортирован
Если поддерживаешь Android API 24+, можно короче через лямбду:
Arrays.sort(cats, String::compareTo);

format()

Форматирование строк в стиле printf из C. Очень удобная штука:
String name = "Барсик";
int age = 5;
String msg = String.format("Коту %s уже %d лет", name, age);

TextView tv = findViewById(R.id.textView);
tv.setText(msg); // Коту Барсик уже 5 лет
Основные спецификаторы: - %s для строк - %d для целых чисел - %f для дробных чисел - %.2f для чисел с 2 знаками после запятой Пример с ценой:
double price = 123.456;
String priceStr = String.format("Цена: %.2f usd.", price); 
// Цена: 123.46 usd.

concat()

Соединяет строки. Работает так же как +, но название метода лучше — concat же содержит слово "кот"!
String part1 = "Сук";
String result = part1.concat("кот"); // Суккот

TextView tv = findViewById(R.id.textView);
tv.setText(result);
Да, я специально примеры с котами делаю. Настоящие кошатники оценят :)

Генерация случайной строки

Иногда нужно сгенерировать случайную строку — например, для временного ID или какого-нибудь токена. Вот как я обычно это делаю:
public class MainActivity extends AppCompatActivity {
    
    private static final String ALLOWED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    private Random random = new Random();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button btn = findViewById(R.id.button);
        TextView tv = findViewById(R.id.textView);
        
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String randomStr = generateRandomString(10);
                tv.setText(randomStr);
            }
        });
    }
    
    private String generateRandomString(int length) {
        StringBuilder result = new StringBuilder();
        for(int i = 0; i < length; i++) {
            int index = random.nextInt(ALLOWED_CHARS.length());
            char randomChar = ALLOWED_CHARS.charAt(index);
            result.append(randomChar);
        }
        return result.toString();
    }
}
При каждом нажатии на кнопку генерируется новая строка типа "A7K9M2X5Q1".

Почему == не работает для сравнения строк

Это классика. Все на это натыкаются. Я когда-то потратил целый вечер на поиск бага, а оказалось — ==.
String cat1 = "Мурзик";
String cat2 = new String("Мурзик");

if(cat1 == cat2) {
    Log.d("Test", "Строки равны");
} else {
    Log.d("Test", "Строки НЕ равны"); // выполнится ЭТО!
}
Хотя обе переменные содержат "Мурзик", == вернёт false. Почему? Потому что == сравнивает ссылки на объекты, а не их содержимое. А когда мы пишем new String("Мурзик"), создаётся новый объект в памяти, даже если такая строка уже есть. Правильно так:
if(cat1.equals(cat2)) {
    Log.d("Test", "Строки равны"); // вот это сработает!
}
Метод equals() сравнивает содержимое строк. Вот ещё примеры для полного понимания:
String s1 = "hello";
String s2 = "hello";
String s3 = s1;
String s4 = "h" + "e" + "l" + "l" + "o";
String s5 = new String("hello");
String s6 = new String(new char[]{'h', 'e', 'l', 'l', 'o'});

Log.d("Test", "s1 == s2: " + (s1 == s2));     // true (компилятор оптимизирует)
Log.d("Test", "s1 equals s2: " + s1.equals(s2)); // true

Log.d("Test", "s1 == s3: " + (s1 == s3));     // true (одна ссылка)
Log.d("Test", "s1 equals s3: " + s1.equals(s3)); // true

Log.d("Test", "s1 == s4: " + (s1 == s4));     // true (компилятор склеил литералы)
Log.d("Test", "s1 equals s4: " + s1.equals(s4)); // true

Log.d("Test", "s1 == s5: " + (s1 == s5));     // FALSE (новый объект!)
Log.d("Test", "s1 equals s5: " + s1.equals(s5)); // true

Log.d("Test", "s1 == s6: " + (s1 == s6));     // FALSE (новый объект!)
Log.d("Test", "s1 equals s6: " + s1.equals(s6)); // true
Простое правило: всегда используй equals() для строк. Оператор == оставь для примитивов (int, boolean и прочее) и для проверки на null.

Полезные штуки для Android

Напоследок пару советов, которые реально выручают в работе: 1. Строки держи в ресурсах Серьёзно, не пиши тексты прямо в коде. Потом сам замучаешься искать где что написано. Держи всё в strings.xml:
// так не надо
textView.setText("Привет, мир!");

// а вот так правильно
textView.setText(R.string.hello_world);
Если потом захочешь сделать приложение на английском или ещё на каком языке — просто добавишь новый strings.xml и всё. 2. TextUtils — полезная штука Android даёт класс TextUtils с удобными методами:
// проверка на пустоту, работает даже если str == null
if(TextUtils.isEmpty(str)) {
    // строка пустая
}

// склеить массив строк с разделителем
String result = TextUtils.join(", ", arrayOfStrings);
Я обычно всегда через TextUtils.isEmpty проверяю — не надо отдельно на null смотреть. 3. SpannableString для красоты Если нужен текст с разными стилями в одном TextView:
SpannableString spannable = new SpannableString("Этот текст жирный");
spannable.setSpan(new StyleSpan(Typeface.BOLD), 5, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannable);
Да, выглядит страшновато, но работает отлично. Можно цвет менять, размер, стиль — что угодно. 4. Форматирование через ресурсы Помнишь я про String.format рассказывал? В Android можно ещё проще:
// В strings.xml пишешь:
// <string name="welcome">Привет, %1$s! Тебе %2$d лет.</string>

// а в коде просто:
String message = getString(R.string.welcome, "Вася", 25);
textView.setText(message);
Удобно же. 5. Html.fromHtml() если нужен простой HTML Иногда нужно вставить жирный текст или курсив:
String html = "Текст с <b>жирным</b> словом";

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    textView.setText(Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY));
} else {
    textView.setText(Html.fromHtml(html));
}
Правда, тут надо проверять версию Android, потому что метод изменился. --- Ну вот и всё. Конечно, у String методов там ещё штук сто наверное, но эти — самые нужные. Я реально использую их чуть ли не в каждом проекте. Сохрани себе эту статью куда-нибудь, пригодится. Я вот у себя в закладках держу подобные справочники — всё равно не запомнишь все методы наизусть, иногда надо подсмотреть синтаксис.