W3docs

Преобразования строк в Java

Преобразование между строками и примитивами в Java — Integer.parseInt, Double.parseDouble, String.valueOf и другие методы.

Большинство данных поступает в виде текста и уходит тоже в виде текста. Между этими двумя границами данные обычно существуют в более удобном формате — int, double, boolean, LocalDate. Эта глава посвящена двум направлениям преобразования между String и примитивными типами, типами-обёртками и массивами символов.

Существует небольшой словарь, который стоит запомнить; как только вы его знаете, ответ на вопрос «как преобразовать эту строку в X» всегда будет однострочным.

String → примитив: семейство parse...

Каждый класс-обёртка имеет статический метод, принимающий String и возвращающий соответствующий примитив:

int    i = Integer.parseInt("42");
long   l = Long.parseLong("9999999999");
double d = Double.parseDouble("3.14");
float  f = Float.parseFloat("3.14");
short  s = Short.parseShort("32000");
byte   b = Byte.parseByte("127");
boolean ok = Boolean.parseBoolean("true");  // any case-insensitive "true"; everything else → false

Это стандартные инструменты. Они выбрасывают NumberFormatException для входных данных, которые не удаётся разобрать, — включая null, пустые строки, строки с пробелами, десятичные разделители с учётом локали, такие как "3,14", и значения, выходящие за допустимый диапазон.

Integer.parseInt(" 42 ");      // NumberFormatException — trim first
Integer.parseInt("3.0");       // NumberFormatException — that's a double
Integer.parseInt("1234567890123"); // NumberFormatException — overflows int

Integer.parseInt принимает необязательный второй аргумент для основания системы счисления:

Integer.parseInt("ff", 16);    // 255
Integer.parseInt("1010", 2);   // 10
Integer.parseInt("777", 8);    // 511 — the radix is the second arg, not a prefix
Integer.parseInt("0x1A", 16);  // NumberFormatException — no "0x" prefix; pass just "1A"

parseInt читает буквальные цифры в переданном основании; он не понимает префиксы из исходного кода, такие как 0x или ведущий 0 для восьмеричных чисел. Так что Integer.parseInt("0777", 8) работает нормально — ведущий 0 просто ещё одна восьмеричная цифра — но Integer.parseInt("0x1A", 16) выбрасывает исключение, потому что x не является шестнадцатеричной цифрой. Самостоятельно удаляйте префиксы перед разбором.

Для беззнакового интерпретирования старшего бита 32- и 64-битных значений JDK предоставляет Integer.parseUnsignedInt и Long.parseUnsignedLong.

String → обёртка: valueOf против parse...

Статический метод valueOf каждого класса-обёртки выполняет тот же разбор, но возвращает тип обёртки:

Integer n = Integer.valueOf("42");     // Integer, not int
Double d  = Double.valueOf("3.14");    // Double, not double

Эти два метода различаются в трёх местах, которые иногда важны:

  • Тип возвращаемого значения. parseIntint, valueOfInteger. Выбирайте тот, который подходит вашей переменной.
  • Кэширование. Integer.valueOf(int) и Long.valueOf(long) кэшируют небольшие числа (по умолчанию −128..127), возвращая один и тот же экземпляр Integer при повторных вызовах. parseInt возвращает примитив, поэтому кэширование неактуально — но если вы всё равно собираетесь упаковывать результат, valueOf дешевле.
  • Поведение при переполнении. Одинаковое — оба выбрасывают NumberFormatException.

Правило большого пальца: если нужен примитив, используйте parseInt; если нужна обёртка, используйте valueOf. Не пишите Integer.valueOf(Integer.parseInt(s)) — это два разбора по объёму кода ради одного разбора по результату.

Примитив → String: String.valueOf и другие методы

Обратное направление предоставляет два хорошо названных инструмента:

String s1 = String.valueOf(42);              // "42"
String s2 = String.valueOf(3.14);            // "3.14"
String s3 = String.valueOf(true);            // "true"
String s4 = String.valueOf('a');             // "a"
String s5 = String.valueOf(new char[]{'h','i'}); // "hi"
String s6 = String.valueOf((Object) null);   // "null"  — NOT an NPE

Каждая обёртка также имеет метод toString, принимающий примитив:

Integer.toString(42);          // "42"
Integer.toString(255, 16);     // "ff"   — radix overload
Long.toString(123456789L);     // "123456789"
Double.toString(3.14);         // "3.14"

И конечно, конкатенация с "" тоже работает:

String s = "" + 42;            // "42"

String.valueOf — наиболее универсальный и наиболее защищённый стиль: он никогда не выбрасывает исключение при null и обрабатывает каждый примитив, а также Object. Для числового форматирования с шириной, знаком, группировкой или десятичными разделителями с учётом локали используйте String.format.

Строки и символы

char — это примитив (одна кодовая единица UTF-16); String — это последовательность таких единиц. Преобразования в обоих направлениях:

String  s   = String.valueOf('a');       // "a"
char    c1  = "abc".charAt(0);           // 'a'
char[]  arr = "abc".toCharArray();       // ['a', 'b', 'c']
String  s2  = new String(arr);           // "abc"
String  s3  = new String(arr, 1, 2);     // "bc" — offset + count

String.toCharArray() всегда возвращает новый изменяемый массив. Это правильный инструмент, когда нужно изменять символы (например, чтобы стереть буфер пароля) — сам String не может предоставить изменяемое представление.

Строки и байты

Байты — это проводной формат; строки — это форма в памяти. Преобразование всегда предполагает кодировку, и правильный ответ — всегда явно её передавать:

byte[] bytes = "héllo".getBytes(StandardCharsets.UTF_8);
String back  = new String(bytes, StandardCharsets.UTF_8);

Перегрузки без аргументов (getBytes(), new String(bytes)) используют кодировку JVM по умолчанию, которая зависит от платформы и является постоянным источником ошибок «работает на моей машине». Всегда передавайте Charset. UTF-8 — правильный выбор по умолчанию для нового кода; устаревшие системы иногда требуют Latin-1 или одну из кодовых страниц Windows.

Преобразования, которых не существует

Несколько преобразований, которые люди ищут, но которых нет:

  • intchar по цифровому значению. (char) 5 — это управляющий символ с кодовой точкой 5, а не '5'. Используйте Character.forDigit(5, 10) или (char) ('0' + 5).
  • charint по цифровому значению. (int) '5' — это 53, а не 5. Используйте Character.digit('5', 10) или '5' - '0'.
  • String → число со значением по умолчанию при ошибке. Java не поставляет parseIntOrDefault. Идиоматический вариант — небольшой вспомогательный метод:
static int parseIntOr(String s, int fallback) {
  try { return Integer.parseInt(s); }
  catch (NumberFormatException e) { return fallback; }
}

Преобразование — это не валидация

Самая распространённая ошибка при преобразовании строки в примитив — считать успешный разбор успешной валидацией. Integer.parseInt("0") возвращает 0 для любого введённого пользователем нуля, включая "0", "00", "+0" и "-0" — все они разбираются в одно и то же значение. Если вас волнует, было ли входное значение каноническим, разберите его и преобразуйте обратно:

int v = Integer.parseInt(input);
if (!Integer.toString(v).equals(input)) {
  throw new IllegalArgumentException("non-canonical integer: " + input);
}

Для числовых данных, вводимых пользователем через формы, сначала проверяйте формат (регулярное выражение, длина, допустимые символы), затем разбирайте. Не полагайтесь на парсер, чтобы узнать, было ли входное значение разумным.

Практический пример

Программа, которая выполняет преобразования в обоих направлениях для наиболее распространённых типов, включая надёжный вспомогательный метод с возвратом значения по умолчанию при ошибке, цикл round-trip с шестнадцатеричным форматом и цикл round-trip байты/строка с UTF-8.

java— editable, runs on the server

В выводе программы стоит обратить внимание на три момента. Строка héllo корректно проходит цикл round-trip через UTF-8 — пять символов превращаются в шесть байт (символ é занимает два), а полученные байты разбираются обратно в те же пять символов. String.valueOf((Object)null) возвращает "null", а не выбрасывает исключение — это намеренно; именно поэтому "x = " + nullable не падает с ошибкой. А вспомогательный метод parseIntOr обрабатывает некорректные данные, данные с пробелами и null без необходимости знать об этом в месте вызова.

Что дальше

Глава, завершающая часть 9, охватывает два наиболее используемых метода «строка на входе, список на выходе / список на входе, строка на выходе» в стандартной библиотеке — и их варианты на основе регулярных выражений. Перейдите к Java String split() и join().

Практика

Практика
В чём разница между `Integer.parseInt('42')` и `Integer.valueOf('42')`?
В чём разница между `Integer.parseInt('42')` и `Integer.valueOf('42')`?
Was this page helpful?