Перейти к основному содержимому

Вопросы и ответы на собеседовании по Java String

· 11 мин. чтения

1. Введение

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

В этом уроке мы рассмотрим некоторые из наиболее распространенных вопросов о String на собеседовании .

2. Основы струн

Этот раздел состоит из вопросов, касающихся внутренней структуры и памяти String .

Q1. Что такое строка в Java?

В Java строка представлена внутри массивом байтовых значений (или значений char до JDK 9).

В версиях до Java 8 включительно строка состояла из неизменного массива символов Unicode. Однако для представления большинства символов требуется всего 8 бит (1 байт) вместо 16 бит ( размер символа ).

Чтобы улучшить потребление памяти и производительность, в Java 9 появились компактные строки . Это означает, что если строка содержит только 1-байтовые символы, она будет представлена с использованием кодировки Latin-1 . Если строка содержит хотя бы 1 многобайтовый символ, она будет представлена как 2 байта на символ с использованием кодировки UTF-16.

В C и C++ String также является массивом символов, но в Java это отдельный объект с собственным API.

Q2. Как мы можем создать объект String в Java ?

java.lang.String определяет 13 различных способов создания String . Однако, как правило, их два:

  • Через строковый литерал:
String s = "abc";
  • Через новое ключевое слово:
String s = new String("abc");

Все строковые литералы в Java являются экземплярами класса String .

Q3. Является ли String примитивным или производным типом?

String является производным типом, поскольку у него есть состояние и поведение. Например, у него есть такие методы, как substring() , indexOf() и equals(), которых не может быть у примитивов.

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

  • Хотя строки не хранятся в стеке вызовов, как примитивы, они хранятся в специальной области памяти, называемой пулом строк .
  • Как и в примитивах, мы можем использовать оператор + для строк.
  • И снова, как и в случае с примитивами, мы можем создать экземпляр String без ключевого слова new .

Q4. Каковы преимущества неизменяемости строк?

Согласно интервью Джеймса Гослинга, строки неизменяемы для повышения производительности и безопасности.

И на самом деле мы видим несколько преимуществ неизменяемых строк :

  • Пул строк возможен только в том случае, если строки, однажды созданные, никогда не изменяются, так как предполагается их повторное использование.
  • Код может безопасно передать строку другому методу , зная, что этот метод не может ее изменить.
  • Immutable автоматически делает этот класс потокобезопасным
  • Поскольку этот класс потокобезопасен, нет необходимости синхронизировать общие данные , что, в свою очередь, повышает производительность.
  • Поскольку они гарантированно не изменятся, их хэш-код можно легко кэшировать.

Q5. Как строка хранится в памяти?

Согласно спецификации JVM, строковые литералы хранятся в пуле констант времени выполнения , который выделяется из области методов JVM .

Хотя область метода логически является частью памяти кучи, спецификация не определяет расположение, размер памяти или политики сборки мусора. Это может зависеть от реализации.

Этот постоянный пул времени выполнения для класса или интерфейса создается, когда класс или интерфейс создается JVM.

Q6. Подходят ли интернированные строки для сборки мусора в Java?

Да, все String в пуле строк подлежат сборке мусора, если нет ссылок из программы.

Q7. Что такое пул строковых констант?

Пул строк , также известный как пул констант строк или внутренний пул строк , представляет собой специальную область памяти, в которой JVM хранит экземпляры строк .

Он оптимизирует производительность приложения , уменьшая частоту и количество выделяемых строк:

  • JVM хранит только одну копию конкретной строки в пуле.
  • При создании новой строки JVM ищет в пуле строку с таким же значением.
  • Если найдено, JVM возвращает ссылку на эту строку без выделения дополнительной памяти .
  • Если не найдено, то JVM добавляет его в пул (интернирует) и возвращает ссылку

Q8. Является ли строка потокобезопасной? Как?

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

Например, если поток изменяет значение строки, создается новая строка вместо изменения существующей.

Q9. Для каких строковых операций важно указывать локаль?

Класс Locale позволяет нам различать культурные локали, а также соответствующим образом форматировать наш контент.

Когда дело доходит до класса String , он нам нужен при рендеринге строк в формате или при отображении строк в нижнем или верхнем регистре.

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

Q10. Что такое базовая кодировка символов для строк?

Согласно String Javadocs для версий до Java 8 включительно, строки хранятся внутри в формате UTF-16.

Тип данных char и объекты java.lang.Character также основаны на исходной спецификации Unicode , которая определяет символы как 16-разрядные объекты фиксированной ширины.

Начиная с JDK 9, строки , содержащие только 1-байтовые символы, используют кодировку Latin-1 , а строки , содержащие как минимум 1 многобайтовый символ, используют кодировку UTF-16.

3. Строковый API

В этом разделе мы обсудим некоторые вопросы, связанные с String API.

Q11. Как мы можем сравнить две строки в Java? В чем разница между str1 == str2 и str1.equals(str2) ?

Мы можем сравнивать строки двумя разными способами: используя оператор равенства ( == ) и используя метод equals() .

Оба сильно отличаются друг от друга:

  • Оператор ( str1 == str2 ) проверяет ссылочное равенство
  • Метод ( str1.equals(str2) ) проверяет лексическое равенство

Хотя верно, что если две строки лексически равны, то str1.intern() == str2.intern() также верно .

Как правило, для сравнения двух строк на предмет их содержимого мы всегда должны использовать String.equals .

Q12. Как мы можем разделить строку в Java?

Сам класс String предоставляет нам метод split String# , `` который принимает разделитель регулярного выражения. Он возвращает нам массив String[] :

String[] parts = "john,peter,mary".split(",");
assertEquals(new String[] { "john", "peter", "mary" }, parts);

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

assertEquals(new String[] { "" }, "".split(","));

Конечно, разделение — это лишь один из многих способов разделения Java String .

Q13. Что такое Stringjoiner?

StringJoiner — это класс, представленный в Java 8 для объединения отдельных строк в одну, например , для получения списка цветов и возврата их в виде строки с разделителями-запятыми . Мы можем указать разделитель, а также префикс и суффикс:

StringJoiner joiner = new StringJoiner(",", "[", "]");
joiner.add("Red")
.add("Green")
.add("Blue");

assertEquals("[Red,Green,Blue]", joiner.toString());

Q14. Разница между String, Stringbuffer и Stringbuilder?

Строки неизменяемы. Это означает, что если мы попытаемся изменить или изменить его значения, то Java создаст абсолютно новую строку .

Например, если мы добавим к строке str1 после ее создания:

String str1 = "abc";
str1 = str1 + "def";

Затем JVM вместо модификации str1 создает совершенно новую строку String .

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

Но для более сложного кода, такого как циклы, будет создана совершенно новая строка , что ухудшит производительность . Вот где полезны StringBuilder и StringBuffer .

И StringBuilder , и StringBuffer в Java создают объекты, содержащие изменяемую последовательность символов. StringBuffer синхронизирован и, следовательно, потокобезопасен, а StringBuilder — нет.

Поскольку дополнительная синхронизация в StringBuffer обычно не требуется, мы часто можем получить прирост производительности, выбрав StringBuilder.

Q15. Почему безопаснее хранить пароли в массиве Char[], а не в строке?

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

Мы должны полагаться на сборщик мусора, чтобы удалить содержимое строки. Более того, в версии Java 6 и ниже строки хранились в PermGen, а это означает, что после создания строки она никогда не подвергалась сборке мусора.

Используя массив char[] , мы имеем полный контроль над этой информацией. Мы можем изменить его или полностью стереть, даже не полагаясь на сборщик мусора.

Использование char[] вместо String не обеспечивает полной защиты информации; это просто дополнительная мера, уменьшающая возможность злоумышленника получить доступ к конфиденциальной информации.

Q16. Что делает метод String intern()?

Метод intern() создает точную копию объекта String в куче и сохраняет ее в пуле констант String , который поддерживает JVM.

Java автоматически интернирует все строки, созданные с помощью строковых литералов, но если мы создадим String с помощью оператора new, например, String str = new String("abc") , то Java добавит его в кучу, как и любой другой объект.

Мы можем вызвать метод intern() , чтобы указать JVM добавить его в пул строк, если он там еще не существует, и вернуть ссылку на эту интернированную строку:

String s1 = "ForEach";
String s2 = new String("ForEach");
String s3 = new String("ForEach").intern();

assertThat(s1 == s2).isFalse();
assertThat(s1 == s3).isTrue();

Q17. Как мы можем преобразовать строку в целое число и целое число в строку в Java?

Самый простой способ преобразовать String в Integer — использовать Integer# parseInt :

int num = Integer.parseInt("22");

Чтобы сделать обратное, мы можем использовать Integer# toString :

String s = Integer.toString(num);

Q18. Что такое String.format() и как мы можем его использовать?

String#format возвращает отформатированную строку, используя указанную строку формата и аргументы.

String title = "ForEach"; 
String formatted = String.format("Title is %s", title);
assertEquals("Title is ForEach", formatted);

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

Locale usersLocale = Locale.ITALY;
assertEquals("1.024",
String.format(usersLocale, "There are %,d shirts to choose from. Good luck.", 1024))

Q19. Как мы можем преобразовать строку в верхний и нижний регистр?

String неявно предоставляет String#toUpperCase для изменения регистра на верхний регистр.

Тем не менее, Javadocs напоминает нам, что нам нужно указать локаль пользователя, чтобы обеспечить правильность:

String s = "Welcome to ForEach!";
assertEquals("WELCOME TO FOREACH!", s.toUpperCase(Locale.US));

Точно так же для преобразования в нижний регистр у нас есть String#toLowerCase :

String s = "Welcome to ForEach!";
assertEquals("welcome to foreach!", s.toLowerCase(Locale.UK));

Q20. Как мы можем получить массив символов из строки?

String предоставляет toCharArray , который возвращает копию своего внутреннего массива символов до JDK9 (и преобразует String в новый массив символов в JDK9+):

char[] hello = "hello".toCharArray();
assertArrayEquals(new String[] { 'h', 'e', 'l', 'l', 'o' }, hello);

Q21. Как бы мы преобразовали строку Java в массив байтов?

По умолчанию метод String#getBytes() кодирует строку в массив байтов, используя кодировку платформы по умолчанию.

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

byte[] byteArray2 = "efgh".getBytes(StandardCharsets.US_ASCII);
byte[] byteArray3 = "ijkl".getBytes("UTF-8");

4. Строковые алгоритмы

В этом разделе мы обсудим некоторые вопросы программирования, связанные со строками .

Q22. Как мы можем проверить, являются ли две строки анаграммами в Java?

Анаграмма — это слово, образованное перестановкой букв другого заданного слова, например, «автомобиль» и «дуга».

Для начала мы сначала проверяем, имеют ли обе строки одинаковую длину или нет.

Затем мы конвертируем их в массив char[] , сортируем их, а затем проверяем на равенство .

Q23. Как мы можем подсчитать количество вхождений данного символа в строку?

Java 8 действительно упрощает такие задачи агрегирования:

long count = "hello".chars().filter(ch -> (char)ch == 'l').count();
assertEquals(2, count);

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

Q24. Как мы можем перевернуть строку в Java?

Это можно сделать разными способами, самый простой из которых — использовать обратный метод из StringBuilder (или StringBuffer ):

String reversed = new StringBuilder("foreach").reverse().toString();
assertEquals("gnudleab", reversed);

Q25. Как мы можем проверить, является ли строка палиндромом или нет?

Палиндром — это любая последовательность символов, которая читается одинаково как вперед, так и назад, например, «мадам», «радар» или «уровень».

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

5. Вывод

В этой статье мы рассмотрели некоторые из наиболее распространенных вопросов на собеседовании по String .

Все используемые здесь примеры кода доступны на GitHub .