Является ли образец на java.util.Formattable некорректным?
Используя пример, приведенный для java.util.Formattable
(измененный для фактического задания значений в конструкторе), кажется, что они работают в основном правильно:
import java.nio.CharBuffer;
import java.util.Formatter;
import java.util.Formattable;
import java.util.Locale;
import static java.util.FormattableFlags.*;
public class StockName implements Formattable {
private String symbol, companyName, frenchCompanyName;
public StockName(String symbol, String companyName,
String frenchCompanyName) {
this.symbol = symbol;
this.companyName = companyName;
this.frenchCompanyName = frenchCompanyName;
}
public void formatTo(Formatter fmt, int f, int width, int precision) {
StringBuilder sb = new StringBuilder();
// decide form of name
String name = companyName;
if (fmt.locale().equals(Locale.FRANCE))
name = frenchCompanyName;
boolean alternate = (f & ALTERNATE) == ALTERNATE;
boolean usesymbol = alternate || (precision != -1 && precision < 10);
String out = (usesymbol ? symbol : name);
// apply precision
if (precision == -1 || out.length() < precision) {
// write it all
sb.append(out);
} else {
sb.append(out.substring(0, precision - 1)).append('*');
}
// apply width and justification
int len = sb.length();
if (len < width)
for (int i = 0; i < width - len; i++)
if ((f & LEFT_JUSTIFY) == LEFT_JUSTIFY)
sb.append(' ');
else
sb.insert(0, ' ');
fmt.format(sb.toString());
}
public String toString() {
return String.format("%s - %s", symbol, companyName);
}
}
Запуск
System.out.printf("%s", new StockName("HUGE", "Huge Fruit, Inc.", "Fruit Titanesque, Inc."));
выводит Huge Fruit, Inc.
, как ожидалось.
Однако следующее не работает:
System.out.printf("%s", new StockName("PERC", "%Company, Inc.", "Fruit Titanesque, Inc."));
Он выбрасывает java.util.MissingFormatArgumentException
:
Exception in thread "main" java.util.MissingFormatArgumentException: Format specifier '%C'
at java.util.Formatter.format(Formatter.java:2519)
at java.util.Formatter.format(Formatter.java:2455)
at StockName.formatTo(FormattableTest.java:44)
at java.util.Formatter$FormatSpecifier.printString(Formatter.java:2879)
at java.util.Formatter$FormatSpecifier.print(Formatter.java:2763)
at java.util.Formatter.format(Formatter.java:2520)
at java.io.PrintStream.format(PrintStream.java:970)
at java.io.PrintStream.printf(PrintStream.java:871)
at FormattableTest.main(FormattableTest.java:55)
В примере используется Formatter.format
для добавления текста, а format
- для форматирования строки формата. Это заставляет вещи сломаться, когда текст, который должен быть добавлен, содержит процент.
Как мне решить эту проблему в formatTo
?. Должен ли я вручную записывать в форматировщик Appendable (formatter.out().append(text)
, который может каким-то образом выбросить IOException
)? Должен ли я попытаться избежать строки формата (что-то вроде formatter.format(text.replace("%","%%"))
, хотя этого может быть недостаточно)? Должен ли я передать простую строку формата в форматтер (formatter.format("%s", text)
, но это кажется излишним)? Все они должны работать, но какой правильный путь семантически?
Чтобы уточнить, в этой гипотетической ситуации параметры, заданные StockName
, управляются пользователем и могут быть произвольными; У меня нет точного контроля над ними (и я не могу запретить ввод %
). Тем не менее, я могу редактировать StockName.formatTo
.
Ответы
Ответ 1
На самом деле это просто. Вы избегаете процентов символов только во время форматирования, а не в исходном свойстве:
// apply precision
if (precision == -1 || out.length() < precision) {
// write it all
sb.append(out.replaceAll("%", "%%"));
}
else {
sb.append(out.substring(0, precision - 1).replaceAll("%", "%%")).append('*');
}
Затем, если вы это сделаете:
StockName stockName = new StockName("HUGE", "%Huge Fruit, Inc.", "Fruit Titanesque, Inc.");
System.out.printf("%s%n", stockName);
System.out.printf("%s%n", stockName.toString());
System.out.printf("%#s%n", stockName);
System.out.printf("%-10.8s%n", stockName);
System.out.printf("%.12s%n", stockName);
System.out.printf(Locale.FRANCE, "%25s%n", stockName);
Результат выглядит следующим образом:
%Huge Fruit, Inc.
HUGE - %Huge Fruit, Inc.
HUGE
HUGE
%Huge Fruit*
Fruit Titanesque, Inc.
Ответ 2
Если вам нужен символ процента печати%, вы должны избежать его двойным письмом, например.
System.out.printf("%s", new StockName("PERC", "%%Company, Inc.", "Fruit Titanesque, Inc."));
это напечатает %Company, Inc.
Ответ 3
Использование format()
потребует экранирования текста, и метод будет искать спецификаторы формата, даже если вы знаете, что ничего не будет содержаться. Оба излишне замедляют вашу программу.
Использование formatter.out().append(text)
(что вы предложили) - это, вероятно, путь.
Для обработки потенциальных IOException
от Appendable.append
, вы можете либо игнорировать его (подобно тому, как Formatter
это делает, за исключением того, вы можете получить доступ к нему) или обернуть его внутри непроверенного исключения. Упаковка это, вероятно, лучший выбор. Для Приложений, которые никогда не генерировали исключение, это не имело бы значения, а для тех, кто их выбрасывает, это сообщало бы вызывающей стороне, что что-то пошло не так.
Таким образом, решение может быть:
try {
// Do not have to call sb.toString() because StringBuilder implements CharSequence
fmt.out().append(sb);
}
catch (IOException ioException) {
throw new UncheckedIOException("Appending formatted output failed", ioException);
}