Вступление
В 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 предлагают
более современный подход, который использует некоторые преимущества
объектно-ориентированного программирования.
У каждого метода есть свои варианты использования, поэтому, надеюсь, вы сможете знать, когда использовать каждый из них в будущем.