Java Formatar Texto
Mostrar 9.5 como 9,50, alinhar uma tabela de alunos em colunas, escrever R$ 1.234,56: tudo isso é formatar. O Java tem um "molde" de texto, com lacunas como %d e %.2f, que é usado em três lugares: String.format, printf e formatted. E tem um detalhe que pega todo brasileiro: o idioma do computador muda a vírgula e o ponto.
- Usar
String.format,System.out.printfe"…".formatted(…). - Os especificadores:
%s %d %f %.2f %5d %-10s %05d %,d %x %e %n %%. - Por que, num computador em português,
%.2fmostra vírgula, e como forçar o ponto comLocale.US. - Montar tabelas alinhadas.
- Formatar dinheiro com
NumberFormate o espaço "invisível" deR$ 1.234,56. - As exceções de formato mais comuns.
A ideia antes do código
Pense num formulário de papel: "Eu, ________, de ___ anos, declaro…". O texto fixo já está impresso; você só preenche as lacunas. Cada lacuna tem um formato esperado: o nome é texto, a idade é número.
No Java, o formulário é uma String com especificadores de formato (as lacunas), que começam com %: "%s tem %d anos". Depois do formulário, você passa os valores, na mesma ordem das lacunas.
Três formas de usar o mesmo molde
| Forma | O que faz | Exemplo |
|---|---|---|
String.format(molde, valores…) | Devolve uma String pronta | String s = String.format("%d anos", 29); |
System.out.printf(molde, valores…) | Mostra na tela (não pula linha sozinho) | System.out.printf("%d anos%n", 29); |
molde.formatted(valores…) Java 15+ | Igual ao String.format, escrito a partir do molde | String s = "%d anos".formatted(29); |
O printf vem da linguagem C ("print formatted", imprimir formatado). System.out.format é um sinônimo dele. As três formas entendem exatamente os mesmos especificadores.
String nome = "Ana";
int idade = 29;
System.out.printf("%s tem %d anos%n", nome, idade);
- "%s tem %d anos%n"
- O molde. Tem três especificadores.
- %s
- Lacuna para texto (string). Recebe o primeiro valor,
nome. - %d
- Lacuna para número inteiro (decimal, "na base 10"). Recebe o segundo valor,
idade. - %n
- Quebra de linha (new line). O
printfnão pula linha sozinho como oprintln. Não consome valor nenhum. - nome, idade
- Os valores, na ordem das lacunas.
Os especificadores
Um programa que mostra os mais usados, rodado na máquina da Ana (em português do Brasil):
public class Main {
public static void main(String[] args) {
String nome = "Ana";
int idade = 29;
double nota = 9.5;
System.out.printf("%s tem %d anos%n", nome, idade);
System.out.printf("Nota: %f%n", nota);
System.out.printf("Nota: %.2f%n", nota);
System.out.printf("Nota: %.0f%n", nota);
System.out.printf("Pi: %.4f%n", Math.PI);
System.out.printf("[%5d]%n", 42);
System.out.printf("[%-5d]%n", 42);
System.out.printf("[%05d]%n", 42);
System.out.printf("[%10s]%n", nome);
System.out.printf("[%-10s]%n", nome);
System.out.printf("%,d%n", 1234567);
System.out.printf("%,.2f%n", 1234567.891);
System.out.printf("%x %X%n", 255, 255);
System.out.printf("%e%n", 1234567.891);
System.out.printf("Desconto de 10%%%n");
System.out.printf("%b %c%n", true, 'A');
System.out.println(nota);
}
}
$ java Main.java Ana tem 29 anos Nota: 9,500000 Nota: 9,50 Nota: 10 Pi: 3,1416 [ 42] [42 ] [00042] [ Ana] [Ana ] 1.234.567 1.234.567,89 ff FF 1,234568e+06 Desconto de 10% true A 9.5
Os colchetes [ ] fazem parte do texto fixo: estão ali só para você ver os espaços. Linha a linha:
| Especificador | Valor | Saída | O que fez |
|---|---|---|---|
%f | 9.5 | 9,500000 | Número com casas decimais (float). Sem precisão, mostra 6 casas |
%.2f | 9.5 | 9,50 | .2 = duas casas decimais. O mais usado de todos |
%.0f | 9.5 | 10 | Zero casas: arredonda (9,5 sobe para 10) |
%.4f | Math.PI | 3,1416 | Quatro casas, arredondando |
%5d | 42 | [ 42] | Largura 5: ocupa pelo menos 5 posições, alinhado à direita |
%-5d | 42 | [42 ] | O - alinha à esquerda |
%05d | 42 | [00042] | O 0 completa com zeros (códigos, matrículas) |
%10s / %-10s | "Ana" | [ Ana] / [Ana ] | Largura também vale para texto |
%,d | 1234567 | 1.234.567 | A , liga o separador de milhar |
%,.2f | 1234567.891 | 1.234.567,89 | Milhar e duas casas juntos |
%x / %X | 255 | ff / FF | Hexadecimal (base 16), minúsculo ou maiúsculo |
%e | 1234567.891 | 1,234568e+06 | Notação científica: 1,234568 × 106 |
%% | — | % | Um sinal de porcentagem de verdade |
%b / %c | true / 'A' | true / A | boolean e caractere (char) |
%n | — | quebra de linha | Funciona em qualquer sistema |
A anatomia de um especificador
Todos seguem a mesma ordem: %, depois (opcionais) as opções, a largura e a precisão, e por fim a conversão (a letra):
- %
- Começo da lacuna.
- - 0 , +
- Opções (flags):
-alinha à esquerda;0completa com zeros;,separa milhares;+mostra o sinal também nos positivos. - 10
- Largura mínima, em caracteres.
- .2
- Precisão: casas decimais (em
%f) ou máximo de caracteres (em%s:%.3smostra só 3 letras). - f
- A conversão:
s,d,f,x,e,b,c…
Exemplo completo: %,10.2f = separador de milhar, largura 10, duas casas, número decimal.
Missão: complete os dois printf: o primeiro deve mostrar 12 x Pão de queijo (use %d e %s) e o segundo, Preço: R$ 7,50 (duas casas decimais). Não esqueça o %n.
public class Main {
public static void main(String[] args) {
String produto = "Pão de queijo";
int quantidade = 12;
double preco = 7.5;
System.out.printf("", quantidade, produto);
System.out.printf("", preco);
}
}
public class Main {
public static void main(String[] args) {
String produto = "Pão de queijo";
int quantidade = 12;
double preco = 7.5;
System.out.printf("%d x %s%n", quantidade, produto);
System.out.printf("Preço: R$ %.2f%n", preco);
}
}
Vírgula ou ponto? O locale
Repare de novo na saída acima: %.2f mostrou 9,50, com vírgula, mas o println(nota) da última linha mostrou 9.5, com ponto. Por quê?
O String.format, o printf e o formatted seguem o locale (pronuncia-se "lôu-quél"): a configuração de idioma e país do computador. A máquina da Ana está em pt_BR (português do Brasil), onde a vírgula separa os decimais e o ponto separa os milhares. Já o println e o + com texto ignoram o locale e usam sempre o ponto, o formato do próprio Java.
import java.util.Locale;
public class Main {
public static void main(String[] args) {
double valor = 1234567.891;
System.out.println(Locale.getDefault());
System.out.println(String.format("%,.2f", valor));
System.out.println(String.format(Locale.US, "%,.2f", valor));
System.out.println(String.format(Locale.GERMANY, "%,.2f", valor));
System.out.println(String.format(Locale.FRANCE, "%,.2f", valor));
System.out.println("%.2f".formatted(valor));
System.out.println(valor);
}
}
$ java Main.java pt_BR 1.234.567,89 1,234,567.89 1.234.567,89 1 234 567,89 1234567,89 1234567.891
- Locale.getDefault()
- O locale padrão da JVM, que ela pega do sistema:
pt_BR(idiomapt, paísBR). A classeLocalefica emjava.util, por isso oimport. - String.format("%,.2f", valor)
- Sem dizer o locale: usa o padrão.
1.234.567,89. - String.format(Locale.US, …)
- Passando um locale como primeiro argumento, ele vale só para esta chamada. Estados Unidos:
1,234,567.89, o contrário do Brasil. - Locale.GERMANY, Locale.FRANCE
- A Alemanha usa o mesmo formato do Brasil; a França separa milhares com um espaço (um espaço especial, que não quebra a linha).
- "%.2f".formatted(valor)
- O
formattedusa sempre o locale padrão: não tem versão comLocale. - println(valor)
- Sem formatação nenhuma: o formato do Java, com ponto e todas as casas.
Um programa com printf("%.2f") mostra 9,50 no computador da Ana e 9.50 num servidor configurado em inglês. Quando o texto vai ser lido por pessoas, isso é bom: cada uma vê no formato do seu país. Quando o texto vai ser lido por outro programa (um arquivo CSV, um JSON, um número que será convertido de volta com Double.parseDouble, que só aceita ponto), use um locale fixo: String.format(Locale.US, …) ou Locale.ROOT (o locale "neutro").
Missão: no JShell, formate 3.14159 com duas casas usando ponto decimal, passando Locale.US para o String.format. (O JShell já importa java.util, então Locale funciona direto.) Experimente também sem o locale e veja a vírgula.
String.format(Locale.US, "%.2f", 3.14159)
Na máquina da Ana, as duas versões ficam assim:
$ jshell
| Welcome to JShell -- Version 25.0.4.1
| For an introduction type: /help intro
jshell> String.format("%.2f", 3.14159)
$1 ==> "3,14"
jshell> String.format(Locale.US, "%.2f", 3.14159)
$2 ==> "3.14"
jshell> /exit
| Goodbye
Tabelas alinhadas
Com largura e alinhamento, a lista de alunos da Ana (a mesma do arquivo Documentos/alunos.csv) vira uma tabela. A regra prática: texto à esquerda (%-8s), números à direita (%5d, %5.1f), e a mesma largura no cabeçalho e nas linhas.
public class Main {
public static void main(String[] args) {
String[] nomes = {"Ana", "Bruno", "Carla", "Diego", "Elisa"};
int[] idades = {29, 34, 22, 41, 27};
String[] cidades = {"Recife", "Curitiba", "Salvador", "Manaus", "Porto Alegre"};
double[] notas = {9.5, 7.0, 8.5, 6.0, 10.0};
System.out.printf("%-8s %5s %-14s %5s%n", "Nome", "Idade", "Cidade", "Nota");
System.out.println("-".repeat(36));
for (int i = 0; i < nomes.length; i++) {
System.out.printf("%-8s %5d %-14s %5.1f%n", nomes[i], idades[i], cidades[i], notas[i]);
}
}
}
$ java Main.java Nome Idade Cidade Nota ------------------------------------ Ana 29 Recife 9,5 Bruno 34 Curitiba 7,0 Carla 22 Salvador 8,5 Diego 41 Manaus 6,0 Elisa 27 Porto Alegre 10,0
- "%-8s %5s %-14s %5s%n"
- O cabeçalho usa
%sem todas as colunas (são todos textos), mas com as mesmas larguras das linhas de dados, para as colunas baterem. "Idade" e "Nota" ficam à direita, em cima dos números. - "-".repeat(36)
- Uma linha de 36 traços (a soma das larguras e dos espaços).
- %5.1f
- Nota com uma casa, ocupando 5 posições, alinhada à direita:
9,5e10,0terminam na mesma coluna.
A largura é um mínimo: um nome com mais de 8 letras empurraria o resto da linha. Escolha larguras que caibam o maior valor. (Letras acentuadas contam como um caractere cada, então Maçã ocupa 4 posições, como esperado.)
Dinheiro: NumberFormat
Para moeda, em vez de montar "R$ " + String.format("%,.2f", …), use o NumberFormat (pacote java.text), que conhece o símbolo, a posição do sinal e as casas de cada moeda:
import java.text.NumberFormat;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
double preco = 1234.56;
NumberFormat real = NumberFormat.getCurrencyInstance(Locale.of("pt", "BR"));
NumberFormat dolar = NumberFormat.getCurrencyInstance(Locale.US);
NumberFormat porcento = NumberFormat.getPercentInstance();
System.out.println(real.format(preco));
System.out.println(dolar.format(preco));
System.out.println(real.format(-5));
System.out.println(porcento.format(0.256));
String texto = real.format(preco);
System.out.println(texto.equals("R$ 1.234,56"));
System.out.println((int) texto.charAt(2));
}
}
$ java Main.java R$ 1.234,56 $1,234.56 -R$ 5,00 26% false 160
- NumberFormat.getCurrencyInstance(locale)
- "Formatador de moeda" do país indicado.
Locale.of("pt", "BR")cria o locale do Brasil Java 19+. - real.format(preco)
- Devolve a
Stringformatada:R$ 1.234,56. Sempre duas casas:-5vira-R$ 5,00. - getPercentInstance()
- Porcentagem no locale padrão: multiplica por 100 e arredonda:
0.256vira26%.
O espaço que não é espaço
As duas últimas linhas mostram uma surpresa: texto.equals("R$ 1.234,56") deu false, embora o texto pareça idêntico. O caractere na posição 2 (entre o $ e o 1) tem código 160, e não 32, o espaço comum. É o espaço não separável (non-breaking space, \u00A0), usado para o "R$" nunca ficar sozinho no fim de uma linha. Olhando os bytes com o od:
$ java Main.java | head -1 | od -c | head -2 0000000 R $ 302 240 1 . 2 3 4 , 5 6 \n 0000015
Os bytes 302 240 são esse caractere em UTF-8. Na tela, ele é igual a um espaço. Em testes e comparações, lembre-se dele: compare com "R$\u00A01.234,56" ou troque antes com texto.replace('\u00A0', ' ').
O NumberFormat resolve a exibição. Para calcular com dinheiro, não use double: 0.1 + 0.2 não dá exatamente 0.3. Veja BigDecimal e Dinheiro (o NumberFormat formata BigDecimal também).
Qual usar
| Você quer… | Use |
|---|---|
| Mostrar algo formatado na tela | System.out.printf(…%n) |
| Guardar o texto formatado numa variável | String.format(…) ou "…".formatted(…) |
| Número com ponto, para outro programa ler | String.format(Locale.US, …) ou Locale.ROOT |
| Moeda | NumberFormat.getCurrencyInstance(Locale.of("pt", "BR")) |
| Porcentagem | NumberFormat.getPercentInstance() ou %.1f%% |
| Juntar duas ou três coisas sem formato especial | + (veja Concatenação) |
| Datas | DateTimeFormatter (veja Datas e Horas) |
Quando dá errado
Os erros de formato não são pegos pelo javac: o molde é só uma String para ele. Eles aparecem na execução, como exceções. Repare que o texto antes da lacuna problemática chega a ser mostrado.
MissingFormatArgumentException: faltou valor
public class Main {
public static void main(String[] args) {
System.out.printf("%s tem %d anos%n", "Ana");
}
}
$ java Main.java Ana tem Exception in thread "main" java.util.MissingFormatArgumentException: Format specifier '%d' at java.base/java.util.Formatter.format(Formatter.java:2760) at java.base/java.io.PrintStream.format(PrintStream.java:1183) at java.base/java.io.PrintStream.printf(PrintStream.java:1081) at Main.main(Main.java:3)
O que aconteceu: "falta argumento para o especificador %d". Há duas lacunas e um valor só. O Ana tem foi escrito antes de o problema ser percebido.
Como resolver: conte as lacunas (sem contar %n e %%) e os valores: devem ser iguais.
IllegalFormatConversionException: d != java.lang.String
public class Main {
public static void main(String[] args) {
System.out.printf("Idade: %d%n", "29");
}
}
$ java Main.java Idade: Exception in thread "main" java.util.IllegalFormatConversionException: d != java.lang.String at java.base/java.util.Formatter$FormatSpecifier.failConversion(Formatter.java:4662) at java.base/java.util.Formatter$FormatSpecifier.printInteger(Formatter.java:3200) at java.base/java.util.Formatter$FormatSpecifier.print(Formatter.java:3155) at java.base/java.util.Formatter.format(Formatter.java:2761) at java.base/java.io.PrintStream.format(PrintStream.java:1183) at java.base/java.io.PrintStream.printf(PrintStream.java:1081) at Main.main(Main.java:3)
O que aconteceu: "d é diferente de String": a lacuna %d espera um inteiro e recebeu o texto "29" (entre aspas!).
Como resolver: passe um número (29), converta (Integer.parseInt("29")) ou use %s.
f != java.lang.Integer: %f com número inteiro
public class Main {
public static void main(String[] args) {
int total = 30;
System.out.printf("Média: %.2f%n", total / 4);
}
}
$ java Main.java Média: Exception in thread "main" java.util.IllegalFormatConversionException: f != java.lang.Integer at java.base/java.util.Formatter$FormatSpecifier.failConversion(Formatter.java:4662) at java.base/java.util.Formatter$FormatSpecifier.printFloat(Formatter.java:3213) at java.base/java.util.Formatter$FormatSpecifier.print(Formatter.java:3161) at java.base/java.util.Formatter.format(Formatter.java:2761) at java.base/java.io.PrintStream.format(PrintStream.java:1183) at java.base/java.io.PrintStream.printf(PrintStream.java:1081) at Main.main(Main.java:4)
O que aconteceu: total / 4 é uma divisão de inteiros: dá 7, um int. O %f não aceita inteiros (nem converte sozinho).
Como resolver: faça a conta com double: total / 4.0 (que dá 7,50). Veja Operadores Aritméticos.
UnknownFormatConversionException
public class Main {
public static void main(String[] args) {
System.out.printf("Nota: %.2f", 9.5);
System.out.printf("Nota: %.2f", 7.0);
System.out.println();
System.out.printf("Desconto: 10%");
}
}
$ java Main.java Nota: 9,50Nota: 7,00 Exception in thread "main" java.util.UnknownFormatConversionException: Conversion = '%' at java.base/java.util.Formatter.parse(Formatter.java:2800) at java.base/java.util.Formatter.format(Formatter.java:2744) at java.base/java.io.PrintStream.format(PrintStream.java:1183) at java.base/java.io.PrintStream.printf(PrintStream.java:1081) at Main.main(Main.java:6)
O que aconteceu: dois problemas. Sem %n, as notas saíram coladas. E um % sozinho no fim do molde foi lido como o começo de uma lacuna sem letra: "conversão desconhecida".
Como resolver: termine cada printf com %n, e escreva %% para um sinal de porcentagem.
$ java Main.java
Main.java:6: warning: [deprecation] Locale(String,String) in Locale has been deprecated
NumberFormat real = NumberFormat.getCurrencyInstance(new Locale("pt", "BR"));
^
1 warning
R$ 10,00
O que aconteceu: não é erro, é um aviso: o programa rodou (R$ 10,00). O construtor new Locale("pt", "BR"), que aparece em muitos tutoriais, foi marcado como obsoleto (deprecated) no Java 19.
Como resolver: use Locale.of("pt", "BR"). Em Java 17 ou anterior, new Locale("pt", "BR") continua sendo o jeito certo.
Referência
| Especificador | Para | Exemplo (pt_BR) |
|---|---|---|
%s | Texto (qualquer objeto) | Ana |
%d | Inteiros (int, long…) | 29 |
%f, %.2f | Decimais (double, float) | 9,500000, 9,50 |
%5d, %-10s | Largura, alinhamento à esquerda | [ 42], [Ana ] |
%05d | Completar com zeros | 00042 |
%,d, %,.2f | Separador de milhar | 1.234.567, 1.234.567,89 |
%x, %X | Hexadecimal | ff, FF |
%e | Notação científica | 1,234568e+06 |
%b, %c | boolean, char | true, A |
%n | Quebra de linha | |
%% | O caractere % | % |
String.format(Locale.US, …) | Forçar ponto decimal | 3.14 |
NumberFormat.getCurrencyInstance(Locale.of("pt","BR")) | Moeda | R$ 1.234,56 (com \u00A0) |
Exercícios
1. Duas casas
Complete o especificador para mostrar o double com duas casas decimais:
2. Matrícula com zeros
Complete para o número 37 aparecer como 000037 (6 dígitos):
3. Porcentagem
Na máquina da Ana, o que String.format("%.1f%%", 12.34) devolve?
4. Num computador em pt_BR, o que System.out.println(2.5) e System.out.printf("%.1f%n", 2.5) mostram?
5. Que exceção String.format("%d", 9.5) lança?
6. Para gravar 9.5 num arquivo CSV que outro programa vai ler com Double.parseDouble, qual é o certo?
Missão final: monte a tabela de preços da feira com um único printf dentro do laço: nome alinhado à esquerda em 10 posições, um espaço, R$ e o preço com duas casas em 6 posições. A saída deve ser exatamente:
Maçã R$ 3,50 Banana R$ 4,99 Melancia R$ 12,00
public class Main {
public static void main(String[] args) {
String[] produtos = {"Maçã", "Banana", "Melancia"};
double[] precos = {3.5, 4.99, 12.0};
for (int i = 0; i < produtos.length; i++) {
System.out.println(produtos[i] + " R$ " + precos[i]);
}
}
}
public class Main {
public static void main(String[] args) {
String[] produtos = {"Maçã", "Banana", "Melancia"};
double[] precos = {3.5, 4.99, 12.0};
for (int i = 0; i < produtos.length; i++) {
System.out.printf("%-10s R$ %6.2f%n", produtos[i], precos[i]);
}
}
}
Resumo
- Um molde com lacunas
%…e os valores na ordem:String.formatdevolve,printfmostra,formatted(Java 15+) devolve. - Especificador =
%+ opções (- 0 , +) + largura +.precisão+ letra (s d f x e b c).%npula linha;%%é o próprio%. - A formatação segue o locale: em
pt_BR,%.2fdá9,50e%,ddá1.234.567. Oprintlnignora o locale e usa ponto. - Para ponto fixo (arquivos, outros programas), passe
Locale.USouLocale.ROOTcomo primeiro argumento. - Tabelas: texto com
%-Ns, números com%Nd/%N.2f, mesmas larguras no cabeçalho. - Moeda:
NumberFormat.getCurrencyInstance(Locale.of("pt", "BR"))→R$ 1.234,56, com um espaço não separável (\u00A0) depois doR$. - Erros de formato são exceções em tempo de execução:
MissingFormatArgumentException,IllegalFormatConversionException,UnknownFormatConversionException.