Вступление
В Java существует несколько способов форматирования строк. Некоторые из
них являются олдскульными и заимствованы непосредственно из старой
классики (например, printf
из C), в то время как другие больше в духе
объектно-ориентированного программирования, например, класс
MessageFormat
В этой статье мы рассмотрим несколько из этих подходов. Мы покажем некоторые особенности того, как можно использовать каждый из методов и в каких обстоятельствах. Используя эти знания, вы узнаете, как подойти к форматированию строк и какие методы использовать.
System.out.printf ()
Начнем со старой классики printf()
. Как упоминалось ранее, printf()
происходит от языка программирования C и означает форматирование для
печати. Под капотом printf()
использует java.util.Formatter
, о
котором мы поговорим позже.
Принцип работы printf()
можно объяснить ее аргументами. Наиболее
распространенный способ использования printf()
следующий:
System.out.printf(String format, String... arguments);
Мы видим, что метод ожидает format
и arguments
vararg. format
определяет способ форматирования строки - шаблон для окончательного
результата.
Например, вы можете захотеть напечатать десятичное число с семью десятичными знаками или число в шестнадцатеричном представлении. Или у вас может быть предопределенное сообщение для приветствия пользователей, но вы хотите отформатировать его, чтобы включить имя пользователя.
arguments
vararg обычно ожидают аргументы (т. Е. Значения) для строки
шаблона. Например, если в шаблоне есть заполнители для двух чисел, метод
printf()
также будет ожидать два числа в качестве arguments
:
System.out.printf("%d %d", 42, 23);
Мы поместили два %d
в строку шаблона. Эти два символа представляют
собой заполнители для определенного типа значения. Например, %d
- это
десятичное числовое значение. Поскольку у нас их два, мы должны передать
два аргумента, которые соответствуют числовым значениям, например 42
и
23
.
Запуск этого кода даст:
42 23
Спецификаторы формата
С помощью printf()
вы можете печатать такие значения, как числа,
строки, даты и т. Д. Чтобы метод знал, что именно вы пытаетесь
напечатать, вам необходимо предоставить спецификатор формата для
каждого из значений. Давайте посмотрим на пример:
System.out.printf("Hello, %s!", "reader");
При выполнении этот код напечатает Hello, reader
в консоль. Символ
%s
представляет описатель формата для строк, аналогично тому, как %d
представляет описатель формата для десятичных чисел.
Мы можем использовать множество спецификаторов формата. Вот несколько распространенных:
- % c - символ
- % d - десятичное число (основание 10)
- % e - экспоненциальное число с плавающей запятой
- % f - число с плавающей запятой
- % i - целое число (основание 10)
- % o - восьмеричное число (основание 8)
- % s - Строка
- % u - беззнаковое десятичное (целое) число
- % x - шестнадцатеричное число (основание 16)
- % t - Дата / время
- % n - Новая строка
Если мы хотим напечатать, например, символ и восьмеричное число, мы
должны использовать %c
и %o
соответственно. Вы можете заметить
кое-что необычное: спецификатор новой строки. Если вы не привыкли к
printf()
из C, может показаться немного странным указывать такие вещи.
Ну, printf()
по умолчанию не записывает новую строку. Фактически, по
умолчанию он почти ничего не делает. По сути, если вы хотите, чтобы
что-то произошло, вы должны сделать это сами.
То есть - если у нас есть несколько printf()
без спецификатора новой
строки:
System.out.printf("Hello, %s!", "Michael Scott");
System.out.printf("Hello, %s!", "Jim");
System.out.printf("Hello, %s!", "Dwight");
Результат будет:
Hello, Michael Scott!Hello, Jim!Hello, Dwight!
Хотя, если мы включим символ новой строки:
System.out.printf("Hello, %s!%n", "Michael Scott");
System.out.printf("Hello, %s!%n", "Jim");
System.out.printf("Hello, %s!%n", "Dwight");
Тогда результат будет:
Hello, Michael Scott!
Hello, Jim!
Hello, Dwight!
Примечание. %n
- это специальный формат, который может быть либо
\r\n
либо просто \n
. \n
- это фактический символ новой строки, а
\r
- это символ возврата каретки. Обычно рекомендуется использовать
\n
поскольку он работает должным образом во всех системах, в отличие
от %n
который можно понимать как любой из двух. Подробнее об этом
позже.
Персонажи побега
В дополнение к описанным выше описателям формата существует еще один тип символов форматирования: escape-символы.
Давайте представим , что мы хотим напечатать "
символ с помощью
printf()
Мы можем попробовать что - то вроде.:
System.out.printf(""");
Если вы попытаетесь запустить это, ваш компилятор наверняка выдаст
исключение. Если вы присмотритесь, даже код, который выделяет код на
этой странице, будет выделен );
как String, а не закрытая скобка
метода.
Случилось так, что мы попытались напечатать символ, имеющий особое, зарезервированное значение. Кавычки используются для обозначения начала и конца строки.
Мы начали и закончили строку ""
, после чего мы открыли еще один "
,
но не закрыл его. Это делает печать зарезервированных символов , как это
невозможно, используя этот подход.
Способ обойти это -
убежать . Чтобы
напрямую печатать специальные символы (такие как "
), нам нужно
сначала избежать его эффектов, а в Java это означает префикс обратной
косой черты ( \
). Чтобы официально печатать кавычки в Java, мы должны
сделать следующее:
System.out.printf("\"");
Сочетание \
и "
определенно говорит компилятору , что мы хотели бы,
чтобы вставить "
символ в том месте , и что он должен относиться к "
в качестве значения конкретного, а не зарезервированный символ.
Применение escape-символа \
может вызывать различные эффекты в
зависимости от последующего. Передача обычного символа
(незарезервированного) ничего не даст, а \
будет рассматриваться как
значение.
Однако некоторые комбинации (также называемые командами) имеют для компилятора другое значение:
- \ b - Вставить пробел
- \ f - Первый символ следующей строки начинается справа от последнего символа текущей строки
- \ n - вставить новую строку
- \ r - вставить возврат каретки
- \ t - Вставить вкладку
- \\ - Вставить обратную косую черту
- %% - вставить знак процента
Таким образом, вы должны использовать \n
для вывода разделителя строк
на консоль, эффективно начиная любое новое содержимое с начала следующей
строки. Точно так же, чтобы добавить вкладки, вы должны использовать
спецификатор \t
Вы могли заметить %%
как последнюю комбинацию.
Почему это? Почему просто не используется
\%
Символ %
уже является escape-символом специально для метода printf()
За которыми следуют такие символы, как d
, i
, f
и т. Д., Средство
форматирования во время выполнения знает, как обрабатывать эти значения.
Однако \
предназначен для компилятора. Он сообщает, куда и что
вставлять. Команда \%
просто не определена, и мы используем %
чтобы
избежать эффекта последующего %
- если это имеет смысл.
Для компилятора %
не является специальным символом, а является \
Кроме того, существует соглашение, согласно которому специальные символы
избегают самих себя. \
экранирует \
и %
экранирует %
.
Основное использование
Давайте отформатируем строку с несколькими аргументами разных типов:
System.out.printf("The quick brown %s jumps %d times over the lazy %s.\n", "fox", 2, "dog");
Результатом будет:
The quick brown fox jumps 2 times over the lazy dog.
Поплавок и двойная точность
С помощью printf()
мы можем определить настраиваемую точность для
чисел с плавающей запятой:
double a = 35.55845;
double b = 40.1245414;
System.out.printf("a = %.2f b = %.4f", a, b);
Поскольку %f
используется для чисел с плавающей запятой, мы можем
использовать его для вывода double
s. Однако, добавив .n
, где
n
- количество десятичных знаков, мы можем определить настраиваемую
точность.
Выполнение этого кода дает:
a = 35.56
b = 40.1245
Форматирование заполнения
Мы также можем добавить отступы, включая переданную строку:
System.out.printf("%10s\n", "stack");
Здесь после %
мы передали число и спецификатор формата. В частности,
нам нужна строка из 10
символов, за которой следует новая строка.
Поскольку stack
содержит только 5 символов, еще 5 добавляются в
качестве отступа, чтобы «заполнить» строку до целевого символа:
stack
Вместо этого вы также можете добавить правый отступ:
System.out.printf("%-10s\n", "stack");
Locale
Мы также можем передать Locale
в качестве первого аргумента,
форматируя строку в соответствии с ним:
System.out.printf(Locale.US, "%,d\n", 5000);
System.out.printf(Locale.ITALY, "%,d\n", 5000);
Это даст два целых числа в разных форматах:
5,000
5.000
Индекс аргумента
Если индекс аргумента не указан, аргументы будут просто следовать порядку присутствия в вызове метода:
System.out.printf("First argument is %d, second argument is %d", 2, 1);
Это приведет к:
First argument is 2, argument number is 1
Однако после %
и перед спецификатором формата мы можем добавить другую
команду. $n
укажет индекс аргумента:
System.out.printf("First argument is %2$d, second argument is %1$d", 2, 1);
Здесь 2$
находится между %
и d
. 2$
указывает, что мы хотим
присоединить второй аргумент из списка аргументов к этому
спецификатору. Точно так же 1$
указывает, что мы хотели бы
присоединить первый аргумент из списка к другому спецификатору.
Выполнение этого кода приводит к:
First argument is 1, second argument is 2
Вы можете указать оба спецификатора на один и тот же аргумент. В нашем случае это будет означать, что мы используем только один аргумент, указанный в списке. Это прекрасно - хотя нам все равно нужно предоставить все аргументы, присутствующие в шаблоне String:
System.out.printf("First argument is %2$d, second argument is %2$d", 2, 1);
Это приведет к:
First argument is 1, second argument is 1
System.out.format ()
Прежде чем говорить о System.out.format()
, давайте кратко остановимся
на System.out
.
Все системы UNIX имеют три основных канала - стандартный канал ввода (
stdin
), стандартный канал вывода ( stdout
) и стандартный канал
ошибок ( stderr
). Поле out
соответствует
PrintStream
stdout
и имеет тип PrintStream.
Этот класс имеет много различных методов для печати форматированных
текстовых представлений в поток, некоторые из которых - это format()
и
printf()
.
Согласно документации, они оба ведут себя одинаково . Это означает,
что между ними нет разницы, и их можно использовать для получения тех же
результатов. Все, что мы до сих пор говорили о printf()
также работает
для format()
.
И printf()
и System.out.format()
печатают в stdout
, который
обычно нацелен на консоль / терминал.
String.format ()
Другой способ форматирования строк - String.format()
который внутренне
также использует java.util.Formatter
, который мы рассмотрим в
следующем разделе.
Основное преимущество String.format()
перед printf()
- это его
возвращаемый тип - он возвращает String
. Вместо того, чтобы просто
печатать содержимое в стандартном канале вывода и не иметь возвращаемого
типа ( void
), как printf()
, String.format()
используется для
форматирования строки, которую можно использовать или повторно
использовать в будущем:
String formattedString = String.format("Local time: %tT", Calendar.getInstance());
Теперь вы можете делать все , что вы хотели бы в formattedString
. Вы
можете распечатать его, вы можете сохранить его в файл, вы можете
изменить его или сохранить в базе данных. Его печать приведет к:
Local time: 16:01:42
Метод String.format()
использует тот же базовый принцип, что и метод
printf()
. Оба внутренне используют Formatter
для фактического
форматирования строк. Таким образом, все, что сказано для printf()
также применимо к String.format()
.
Использование printf()
, String.format()
или Formatter
по сути
одно и то же. Единственное, что отличается, - это тип возвращаемого
значения - printf()
печатает в стандартный поток вывода (обычно в вашу
консоль), а String.format()
возвращает отформатированную String
.
При этом String.format()
более универсален, поскольку вы можете
использовать результат более чем одним способом.
Класс Formatter
Поскольку все вышеперечисленные методы по своей сути вызывают
Formatter
, знание только одного означает, что вы знаете их все.
Использование Formatter
очень похоже на другие методы, показанные
ранее. Самая большая разница в том, что для его использования нужно
создать экземпляр объекта Formatter
Formatter f = new Formatter();
f.format("There are %d planets in the Solar System. Sorry, Pluto", 8);
System.out.println(f);
Напрашивается вопрос:
Почему бы мне всегда просто не использовать предыдущие методы, поскольку они более краткие?
Есть еще одно важное отличие, делающее Formatter
достаточно гибким:
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb);
formatter.format("%d, %d, %d...\n", 1, 2, 3);
Вместо того, чтобы работать только с String
s, Formatter
также может
работать с StringBuilder
что позволяет (повторно) эффективно
использовать оба класса.
Фактически, Formatter
может работать с любым классом, реализующим
интерфейс Appendable
Одним из таких примеров является вышеупомянутый
StringBuilder
, но другие примеры включают такие классы, как
BufferedWriter
, FileWriter
, PrintStream
, PrintWriter
,
StringBuffer
и т. Д. Полный список можно найти в
документации
.
Наконец, все спецификаторы формата, escape-символы и т. Д. Также
действительны для Formatter
поскольку это основная логика для
форматирования строк во всех трех случаях: String.format()
,
printf()
и Formatter
.
MessageFormat
Наконец, давайте покажем еще один последний метод форматирования,
который не использует Formatter
под капотом.
MessageFormat
был создан для создания и предоставления объединенных
сообщений независящим от языка способом. Это означает, что
форматирование будет одинаковым, независимо от того, используете ли вы
Java, Python или какой-либо другой язык, поддерживающий MessageFormat
.
MessageFormat
расширяет абстрактный Format
точно так же, как
DateFormat
и NumberFormat
. Класс Format
предназначен для
форматирования объектов, зависящих от языкового стандарта, в строки.
Давайте посмотрим , хороший пример, любезно MessageFormat
«s
документации
.
int planet = 7;
String event = "a disturbance in the Force";
String result = MessageFormat.format(
"At {1, time} on {1, date}, there was {2} on planet {0, number, integer}.",
planet, new Date(), event
);
[Кредит кода: Oracle Docs]{.small}
Результат:
At 11:52 PM on May 4, 2174, there was a disturbance in the Force on planet 7.
Вместо описателей процентов, которые мы видели до сих пор, здесь мы
используем фигурные скобки для каждого аргумента. Возьмем первый
аргумент, {1, time}
. Число 1
представляет собой индекс аргумента,
который следует использовать вместо него. В нашем случае аргументами
являются planet
, new Date()
и event
.
Вторая часть, time
, относится к типу значения. Типы формата верхнего
уровня - это number
, date
, time
и choice
. Для каждого из
значений можно сделать более конкретный выбор, например, с помощью
{0, number, integer}
который говорит, что значение следует
рассматривать не только как число, но и как целое число.
Полный набор типов и подтипов форматов можно найти в документации .
Заключение
В этой статье мы рассмотрели изрядное количество способов форматирования строк в ядре Java.
У каждой из представленных нами техник есть своя причина существования.
printf()
, например, напоминает одноименный метод C старой школы из.
Другие подходы, такие как Formatter
или MessageFormat
предлагают
более современный подход, который использует некоторые преимущества
объектно-ориентированного программирования.
У каждого метода есть свои варианты использования, поэтому, надеюсь, вы сможете знать, когда использовать каждый из них в будущем.