Java Pattern Matching

Pattern matching (em português, "casamento de padrões") é perguntar "este objeto tem esta forma?" e, na mesma tacada, pegar as partes dele para usar. Antes, o Java obrigava você a testar o tipo, converter e só depois usar, em três passos repetitivos. Do Java 16 ao 21, a linguagem ganhou um jeito curto e seguro de fazer tudo isso de uma vez.

Você vai aprender
  • O instanceof com padrão Java 16+: testar o tipo e já ganhar uma variável.
  • Onde essa variável existe (e o erro cannot find symbol quando você a usa fora do lugar).
  • O switch com padrões de tipo e a palavra when Java 21+.
  • Os record patterns Java 21+: desmontar um record dentro do padrão, inclusive um dentro do outro.
  • Por que o compilador exige que o switch cubra "todos os casos", como as classes sealed dispensam o default e como tratar o null com case null.

Esta aula usa coisas que você já viu: polimorfismo (uma variável do tipo Object pode guardar um String, um Integer…), conversão de tipos (cast), o switch com setas e os records. Os exemplos rodaram no JDK 25 da máquina da Ana, na pasta ~/java.


A ideia antes do código

Imagine a Ana separando as encomendas da loja. Chega uma caixa fechada. Ela pergunta: "isto é uma caixa de frutas?". Se for, ela abre e pega as frutas. Se for uma caixa de livros, faz outra coisa. Repare que a pergunta e o "abrir" andam juntos: ninguém pergunta "é de frutas?", ouve "sim" e depois finge que não sabe e confere de novo.

No Java antigo era exatamente isso que acontecia. O programa perguntava o tipo com instanceof (em inglês, "é instância de", ou seja, "é um objeto do tipo…") e, mesmo depois de ouvir "sim", precisava converter o objeto para o tipo certo com um cast. Um padrão junta as duas coisas: "se for um String, chame-o de texto".


1. O jeito antigo: testar e converter

Uma variável do tipo Object aceita qualquer objeto. Só que, por ser genérica, ela não deixa você chamar métodos de String, como toUpperCase() (deixar em maiúsculas). Era preciso testar e converter:

public class Main {
    public static void main(String[] args) {
        Object coisa = "banana";

        // Jeito antigo: testar, depois converter (cast)
        if (coisa instanceof String) {
            String texto = (String) coisa;
            System.out.println("Antigo: " + texto.toUpperCase());
        }

        // Jeito novo (Java 16+): testar e já dar um nome
        if (coisa instanceof String texto) {
            System.out.println("Novo: " + texto.toUpperCase());
        }
    }
}
$ javac Main.java && java Main
Antigo: BANANA
Novo: BANANA

As duas formas fazem a mesma coisa. No jeito antigo, a palavra String aparece três vezes (no teste, na declaração e no cast), e nada impede você de errar uma delas, como testar String e converter para Integer. No jeito novo, aparece uma vez só.

2. instanceof com padrão Java 16+

coisa
O objeto que você quer examinar.
instanceof
"É um objeto do tipo…?". Responde true ou false.
String
O tipo que você está testando.
texto
A novidade: o nome de uma variável de padrão. Se o teste der true, ela nasce já do tipo String, guardando o mesmo objeto. Não tem cast.

O conjunto String texto é o padrão de tipo: um tipo seguido de um nome. Ele "casa" com o objeto quando o objeto é daquele tipo. Com vários else if, você trata cada tipo do seu jeito:

public class Main {
    static void descrever(Object item) {
        if (item instanceof String s) {
            System.out.println("Texto com " + s.length() + " letras: " + s);
        } else if (item instanceof Integer n) {
            System.out.println("Número inteiro; o dobro é " + (n * 2));
        } else if (item instanceof Double d) {
            System.out.println("Número decimal; metade é " + (d / 2));
        } else {
            System.out.println("Não sei descrever: " + item);
        }
    }

    public static void main(String[] args) {
        descrever("maçã");
        descrever(21);
        descrever(7.5);
        descrever(true);
    }
}
$ javac Main.java && java Main
Texto com 4 letras: maçã
Número inteiro; o dobro é 42
Número decimal; metade é 3.75
Não sei descrever: true

Cada chamada caiu num ramo diferente. O 21 virou um Integer por autoboxing, casou com Integer n, e dentro do ramo n é um número de verdade: dá para fazer n * 2. O true não casou com nenhum padrão e foi para o else.

Onde a variável de padrão existe

A variável s só existe nos lugares em que o Java tem certeza de que o teste deu certo. Essa regra tem até nome: flow scoping ("escopo pelo fluxo"). Na prática:

  • Dentro do if que testou: existe.
  • Depois de um && (e): existe, porque o lado direito só roda se o esquerdo deu true. Então o instanceof String s && s.length() > 3 funciona.
  • Depois de um || (ou): não existe, porque o lado direito roda justamente quando o teste deu false.
  • Com a negação !(… instanceof …) e um return: a variável existe depois do if, porque só chega lá quem passou no teste.
public class Main {
    static void boasVindas(Object nome) {
        if (!(nome instanceof String s)) {
            System.out.println("Isso não é um nome.");
            return;
        }
        // daqui para baixo o Java sabe que nome é String: s existe
        System.out.println("Olá, " + s + "!");
    }

    public static void main(String[] args) {
        Object[] itens = { "Ana", 42, "kiwi", 3.5 };
        for (Object o : itens) {
            if (o instanceof String s && s.length() > 3) {
                System.out.println("Palavra longa: " + s);
            }
        }
        boasVindas("Ana");
        boasVindas(10);
    }
}
$ javac Main.java && java Main
Palavra longa: kiwi
Olá, Ana!
Isso não é um nome.

"Ana" é um String, mas tem só 3 letras, então o && deu false; 42 e 3.5 nem são textos. Só "kiwi" passou nos dois testes. Na boasVindas, o return faz o método terminar cedo para quem não é texto; quem continua é String, e o Java deixa usar s.

Missão: o vetor itens mistura textos e números. Use instanceof com padrão (o instanceof Integer n) para somar só os inteiros e mostrar Soma: 49.

public class Main {
    public static void main(String[] args) {
        Object[] itens = { "Ana", 42, "kiwi", 7, 2.5 };
        int soma = 0;
        for (Object o : itens) {
            // teste aqui se o é um Integer e some
        }
        System.out.println("Soma: " + soma);
    }
}
public class Main {
    public static void main(String[] args) {
        Object[] itens = { "Ana", 42, "kiwi", 7, 2.5 };
        int soma = 0;
        for (Object o : itens) {
            if (o instanceof Integer n) {
                soma = soma + n;
            }
        }
        System.out.println("Soma: " + soma);
    }
}

3. switch com padrões de tipo Java 21+

Uma escada de if … else if testando tipos fica comprida. Desde o Java 21, o switch com setas aceita padrões no lugar de valores fixos. Cada case diz um tipo e um nome:

public class Main {
    static String descrever(Object item) {
        return switch (item) {
            case Integer n -> "inteiro " + n;
            case Double d  -> "decimal " + d;
            case String s  -> "texto \"" + s + "\"";
            default        -> "outra coisa: " + item;
        };
    }

    public static void main(String[] args) {
        System.out.println(descrever(10));
        System.out.println(descrever(2.5));
        System.out.println(descrever("pera"));
        System.out.println(descrever('x'));
    }
}
$ javac Main.java && java Main
inteiro 10
decimal 2.5
texto "pera"
outra coisa: x
LinhaO que faz
return switch (item) {Um switch que produz um valor (uma switch expression); o return devolve esse valor.
case Integer n -> …Se item for um Integer, chame-o de n e produza o texto da direita.
case String s -> …Mesmo raciocínio para String. Os case são testados de cima para baixo.
default -> …Para todo o resto (aqui, o char 'x', que virou um Character).

Condições extras com when

Às vezes o tipo não basta: você quer "um inteiro maior ou igual a 7". Para isso existe a guarda when (inglês para "quando"): depois do padrão, uma condição que também precisa ser verdadeira.

public class Main {
    static String classificar(Object nota) {
        return switch (nota) {
            case Integer n when n >= 7 -> "aprovado com " + n;
            case Integer n when n >= 5 -> "recuperação com " + n;
            case Integer n             -> "reprovado com " + n;
            case String s              -> "nota em texto? \"" + s + "\"";
            default                    -> "não é nota";
        };
    }

    public static void main(String[] args) {
        System.out.println(classificar(9));
        System.out.println(classificar(6));
        System.out.println(classificar(3));
        System.out.println(classificar("dez"));
        System.out.println(classificar(8.5));
    }
}
$ javac Main.java && java Main
aprovado com 9
recuperação com 6
reprovado com 3
nota em texto? "dez"
não é nota
case
Começa uma alternativa do switch.
Integer n
O padrão de tipo: casa com inteiros e cria n.
when n >= 7
A guarda: só vale se, além de ser inteiro, n for pelo menos 7. Ela pode usar a variável do padrão.
-> "aprovado com " + n
O resultado deste caso.

O 6 não passou na guarda do primeiro case, então o Java tentou o segundo, que casou. O 8.5 é um Double, não casou com nenhum padrão de Integer nem de String, e caiu no default.

A ordem importa

O caso mais específico vem primeiro. case Integer n sem guarda casa com qualquer inteiro; se ele vier antes de case Integer n when n >= 7, o segundo nunca seria alcançado, e o javac recusa o programa (veja "Quando dá errado", mais abaixo).

Missão: a etiqueta de cada item da loja depende do tipo e do tamanho. Complete o switch com padrões e when: textos de até 4 letras viram fruta curta: …, os outros fruta longa: …; inteiros acima de 10 viram caixa grande com …, os outros caixa pequena com ….

public class Main {
    static String etiqueta(Object item) {
        return switch (item) {
            case String s when s.length() <= 4 -> "fruta curta: " + s;
            // falta: os outros textos e os inteiros
            default -> "não sei";
        };
    }

    public static void main(String[] args) {
        System.out.println(etiqueta("kiwi"));
        System.out.println(etiqueta("melancia"));
        System.out.println(etiqueta(24));
        System.out.println(etiqueta(6));
    }
}
public class Main {
    static String etiqueta(Object item) {
        return switch (item) {
            case String s when s.length() <= 4 -> "fruta curta: " + s;
            case String s -> "fruta longa: " + s;
            case Integer n when n > 10 -> "caixa grande com " + n;
            case Integer n -> "caixa pequena com " + n;
            default -> "não sei";
        };
    }

    public static void main(String[] args) {
        System.out.println(etiqueta("kiwi"));
        System.out.println(etiqueta("melancia"));
        System.out.println(etiqueta(24));
        System.out.println(etiqueta(6));
    }
}

4. E o null? case null Java 21+

null quer dizer "nenhum objeto". Ele não é String, nem Integer, nem nada; por isso null instanceof String dá false. Mas um switch que recebe null e não diz o que fazer com ele lança uma exceção, mesmo tendo default:

public class Main {
    static String descrever(Object item) {
        return switch (item) {
            case Integer n -> "inteiro " + n;
            case String s  -> "texto " + s;
            default        -> "outra coisa";
        };
    }

    public static void main(String[] args) {
        System.out.println(descrever("caqui"));
        System.out.println(descrever(null));
    }
}
$ javac Main.java && java Main
texto caqui
Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.Objects.requireNonNull(Objects.java:220)
	at Main.descrever(Main.java:3)
	at Main.main(Main.java:12)

A exceção NullPointerException ("exceção de ponteiro nulo") apareceu na linha 3, a do switch. O default não pega o null: isso foi decidido para que programas antigos continuassem se comportando igual. Para tratar o null, escreva um caso para ele:

        return switch (item) {
            case null      -> "nada (null)";
            case Integer n -> "inteiro " + n;
            case String s  -> "texto " + s;
            default        -> "outra coisa";
        };
$ javac Main.java && java Main
texto caqui
nada (null)

Se o null deve ter o mesmo destino do default, junte os dois: case null, default -> "nulo ou outra coisa";.


5. Record patterns: desmontar um record Java 21+

Um record é uma classe feita só para guardar dados, como record Ponto(int x, int y). Os record patterns (padrões de record) deixam você testar "é um Ponto?" e, ao mesmo tempo, tirar o x e o y de dentro dele. Isso se chama desconstrução: o contrário de construir com new Ponto(3, 4).

record Ponto(int x, int y) {}

public class Main {
    public static void main(String[] args) {
        Object o = new Ponto(3, 4);

        // Antes: testar, converter e chamar os métodos de acesso
        if (o instanceof Ponto p) {
            System.out.println("x = " + p.x() + ", y = " + p.y());
        }

        // Java 21+: desmontar o record direto no padrão
        if (o instanceof Ponto(int x, int y)) {
            System.out.println("x = " + x + ", y = " + y);
        }

        // var também vale dentro do padrão
        if (o instanceof Ponto(var x, var y) && x == y) {
            System.out.println("Está na diagonal");
        } else {
            System.out.println("Fora da diagonal");
        }
    }
}
$ javac Main.java && java Main
x = 3, y = 4
x = 3, y = 4
Fora da diagonal
o instanceof
O teste de sempre.
Ponto(
"É um Ponto? Então abra-o."
int x, int y
Um padrão para cada componente, na ordem em que o record os declara. Os nomes são seus: Ponto(int a, int b) também funcionaria.
)
Fecha o padrão. Se casar, x e y já existem com os valores de dentro.

No lugar dos tipos, você pode escrever var e deixar o Java descobrir (int, neste caso), como na aula de var.

Padrões aninhados: um record dentro do outro

A força dos record patterns aparece quando um record guarda outro. Uma Linha tem dois Pontos; num único padrão, você abre a linha e os dois pontos:

record Ponto(int x, int y) {}
record Linha(Ponto inicio, Ponto fim) {}

public class Main {
    static String descrever(Object o) {
        return switch (o) {
            case Ponto(var x, var y) when x == 0 && y == 0 -> "a origem";
            case Ponto(var x, var y) -> "ponto (" + x + ", " + y + ")";
            case Linha(Ponto(var x1, var y1), Ponto(var x2, var y2)) when x1 == x2 ->
                "linha vertical em x = " + x1;
            case Linha(Ponto a, Ponto b) -> "linha de " + a + " até " + b;
            default -> "não sei";
        };
    }

    public static void main(String[] args) {
        System.out.println(descrever(new Ponto(0, 0)));
        System.out.println(descrever(new Ponto(2, 5)));
        System.out.println(descrever(new Linha(new Ponto(1, 0), new Ponto(1, 9))));
        System.out.println(descrever(new Linha(new Ponto(0, 0), new Ponto(4, 4))));
    }
}
$ javac Main.java && java Main
a origem
ponto (2, 5)
linha vertical em x = 1
linha de Ponto[x=0, y=0] até Ponto[x=4, y=4]

O terceiro case lê assim: "uma Linha cujo início é um Ponto com x1 e y1, e cujo fim é um Ponto com x2 e y2, quando x1 == x2". No último case, os pontos não foram abertos: a e b são Pontos inteiros, e o println mostra o toString automático dos records (Ponto[x=0, y=0]).

Não precisa de uma parte? Use _ Java 22+

Se o y não interessa, escreva _ (sublinhado) no lugar dele: o instanceof Ponto(int x, _). O mesmo vale para o nome inteiro: case Ponto _ -> "é um ponto". São as unnamed variables and patterns, finais desde o Java 22 (veja Novidades do Java 17 ao 25). No JDK 25 da Ana, if (o instanceof Ponto(int x, _)) mostrou Só me interessa o x: 3.


6. Exaustividade: o switch precisa cobrir tudo

Um switch que produz um valor precisa ter uma resposta para toda entrada possível. Os case juntos devem ser exaustivos ("esgotar todas as possibilidades"). Com Object, as possibilidades são infinitas (qualquer classe!), então o default é obrigatório. Sem ele:

Este código tem um erro de propósito.
public class Main {
    static String descrever(Object item) {
        return switch (item) {
            case Integer n -> "inteiro " + n;
            case String s  -> "texto " + s;
        };
    }

    public static void main(String[] args) {
        System.out.println(descrever(10));
    }
}
$ javac Main.java
Main.java:3: error: the switch expression does not cover all possible input values
        return switch (item) {
               ^
1 error

"A expressão switch não cobre todos os valores de entrada possíveis." E se o tipo tiver um número fechado de possibilidades? É para isso que existem as classes seladas: uma interface sealed lista quem pode implementá-la, e o compilador passa a saber que não existe mais nenhuma. Aí o default se torna desnecessário:

sealed interface Forma permits Circulo, Retangulo {}
record Circulo(double raio) implements Forma {}
record Retangulo(double largura, double altura) implements Forma {}

public class Main {
    static double area(Forma f) {
        return switch (f) {
            case Circulo(double r) -> Math.PI * r * r;
            case Retangulo(double l, double a) -> l * a;
        };
    }

    public static void main(String[] args) {
        System.out.println(area(new Circulo(1)));
        System.out.println(area(new Retangulo(3, 4)));
    }
}
$ javac Main.java && java Main
3.141592653589793
12.0

A vantagem aparece quando alguém aumenta a família. Se você acrescentar Triangulo ao permits e esquecer de atualizar o switch, o programa não compila, e o javac aponta o lugar exato:

$ javac Main.java
Main.java:8: error: the switch expression does not cover all possible input values
        return switch (f) {
               ^
1 error

Com um default, o triângulo cairia nele em silêncio e talvez calculasse uma área errada. Sem o default, o compilador vira seu aliado. A aula Classes Seladas detalha sealed, permits, final e non-sealed.


Antes × depois

Resumo de como o mesmo problema era escrito antes e como se escreve no Java moderno:

SituaçãoAntesDepoisDesde
Testar o tipo e usarif (o instanceof String) { String s = (String) o; … }if (o instanceof String s) { … }Java 16
Tipo + condiçãoif (o instanceof String) { String s = (String) o; if (s.length() > 3) … }if (o instanceof String s && s.length() > 3)Java 16
Vários tiposEscada de if … else if com castsswitch (o) { case Integer n -> …; case String s -> …; }Java 21
Tipo + condição no switchif dentro do casecase Integer n when n >= 7 -> …Java 21
Ler os dados de um recordPonto p = (Ponto) o; int x = p.x(); int y = p.y();if (o instanceof Ponto(int x, int y))Java 21
null no switchif (o == null) … antes do switchcase null -> …Java 21
Garantir que todos os tipos foram tratadosdefault -> throw … e torcerInterface sealed + switch sem defaultJava 17/21
Ignorar uma parteDar um nome que nunca é usadoPonto(int x, _)Java 22

Qual usar?

Você quer…Use
Testar um tipo, num ifinstanceof com padrão
Escolher entre vários tiposswitch com padrões
Separar casos pelo valor, além do tipocase … when condição
Usar os componentes de um recordRecord pattern: Ponto(var x, var y)
Que o compilador avise quando surgir um tipo novoInterface sealed e switch sem default
Rodar num Java 11 ou 8O jeito antigo (instanceof + cast); padrões não existem lá

Quando dá errado

Usar a variável de padrão fora do if

Este código tem um erro de propósito.
public class Main {
    public static void main(String[] args) {
        Object coisa = "uva";
        if (coisa instanceof String s) {
            System.out.println("É texto");
        }
        System.out.println(s.length());
    }
}
$ javac Main.java
Main.java:7: error: cannot find symbol
        System.out.println(s.length());
                           ^
  symbol:   variable s
  location: class Main
1 error

O que quer dizer: "não encontrei o símbolo s". Na linha 7 o Java não tem certeza de que coisa é String (o if pode ter dado false), então s não existe ali. Como consertar: use s só dentro do if, ou inverta o teste com if (!(coisa instanceof String s)) return;, como na boasVindas.

Usar a variável depois de um ||

Este código tem um erro de propósito.
        if (coisa instanceof String s || s.isEmpty()) {
$ javac Main.java
Main.java:4: error: cannot find symbol
        if (coisa instanceof String s || s.isEmpty()) {
                                         ^
  symbol:   variable s
  location: class Main
1 error

O que quer dizer: o lado direito do || só roda quando o esquerdo é false, isto é, quando coisa não é String; aí não há s. Como consertar: provavelmente você queria &&: coisa instanceof String s && s.isEmpty().

Caso geral antes do caso específico

Este código tem um erro de propósito.
        return switch (nota) {
            case Integer n             -> "reprovado com " + n;
            case Integer n when n >= 7 -> "aprovado com " + n;
            default                    -> "não é nota";
        };
$ javac Main.java
Main.java:5: error: this case label is dominated by a preceding case label
            case Integer n when n >= 7 -> "aprovado com " + n;
                 ^
1 error

O que quer dizer: "este rótulo de case é dominado por um rótulo anterior". case Integer n já pega todo inteiro, então o caso de baixo nunca seria usado. Como consertar: do mais específico para o mais geral: primeiro os casos com when, depois o case Integer n sozinho.

Switch sem default e sem cobrir tudo

Mensagem: error: the switch expression does not cover all possible input values (veja a seção 6). Como consertar: acrescente o case que falta (se o tipo for sealed) ou um default.

NullPointerException no switch

O valor era null e não havia case null (seção 4). Como consertar: case null -> … ou case null, default -> ….


Referência

FormaExemploSignificado
Padrão de tipoo instanceof String sSe o é String, cria s
Padrão + condiçãoo instanceof String s && !s.isEmpty()s vale à direita do &&
Negação + saídaif (!(o instanceof String s)) return;s vale depois do if
Switch com tiposcase Integer n -> …Escolhe pelo tipo
Guardacase Integer n when n > 0 -> …Tipo e condição
Nulocase null -> … / case null, default -> …Trata null sem exceção
Record patterncase Ponto(int x, int y) -> …Testa e desmonta
Com varPonto(var x, var y)Tipos inferidos
AninhadoLinha(Ponto(var a, var b), Ponto p)Abre records dentro de records
IgnorarPonto(int x, _), case Ponto _Parte sem nome (Java 22+)

Exercícios

1. Complete para testar se o é um String e chamá-lo de nome:

if (o ) { System.out.println(nome.length()); }

2. Complete a guarda para que o caso valha só para inteiros negativos:

case Integer n -> "negativo";

3. Complete o caso que trata o valor nulo:

case -> "vazio";

4. Em if (o instanceof String s || s.isEmpty()), o que acontece?

5. Por que um switch sobre uma interface sealed pode dispensar o default?

6. Um switch com default, mas sem case null, recebe null. O que acontece?

Missão final: a loja da Ana aceita dois tipos de pagamento, e a interface é sealed. Escreva o switch sem default, com record patterns: Pix(String chave, double valor) vira Pix de 50.0 para ana@email.com; um Cartao com 1 parcela vira Cartão à vista: 80.0; com mais parcelas, Cartão em 4x de 30.0 (valor dividido pelas parcelas).

sealed interface Pagamento permits Pix, Cartao {}
record Pix(String chave, double valor) implements Pagamento {}
record Cartao(int parcelas, double valor) implements Pagamento {}

public class Main {
    static String descrever(Pagamento p) {
        return switch (p) {
            // escreva os casos aqui
        };
    }

    public static void main(String[] args) {
        System.out.println(descrever(new Pix("ana@email.com", 50.0)));
        System.out.println(descrever(new Cartao(1, 80.0)));
        System.out.println(descrever(new Cartao(4, 120.0)));
    }
}
sealed interface Pagamento permits Pix, Cartao {}
record Pix(String chave, double valor) implements Pagamento {}
record Cartao(int parcelas, double valor) implements Pagamento {}

public class Main {
    static String descrever(Pagamento p) {
        return switch (p) {
            case Pix(String chave, double valor) -> "Pix de " + valor + " para " + chave;
            case Cartao(int parcelas, double valor) when parcelas == 1 -> "Cartão à vista: " + valor;
            case Cartao(int parcelas, double valor) -> "Cartão em " + parcelas + "x de " + (valor / parcelas);
        };
    }

    public static void main(String[] args) {
        System.out.println(descrever(new Pix("ana@email.com", 50.0)));
        System.out.println(descrever(new Cartao(1, 80.0)));
        System.out.println(descrever(new Cartao(4, 120.0)));
    }
}

Resumo

  • Um padrão testa a forma de um objeto e já extrai as partes dele, sem cast.
  • o instanceof String s Java 16+ cria s onde o teste é certamente verdadeiro: dentro do if, à direita do && ou depois de um if (!…) return;. Fora disso: cannot find symbol.
  • O switch aceita padrões de tipo e guardas when Java 21+; os casos são testados de cima para baixo, do mais específico ao mais geral.
  • Record patterns Java 21+ desmontam records, inclusive aninhados; var e _ ajudam a escrever menos.
  • Um switch que produz valor precisa ser exaustivo; com uma interface sealed, dá para dispensar o default e deixar o compilador avisar quando surgir um tipo novo.
  • null num switch sem case null lança NullPointerException, mesmo com default.

Próxima aula: Classes Seladas, a outra metade desta história.