← Java: linguagem e decisões de aplicação
02 / 10 · 45 MIN

Referências, identidade e texto

Distingue igualdade, partilha e cópia para impedir alterações inesperadas entre componentes.

Uma referência não é uma cópia do objeto

Em int[] a = {4, 8}; int[] b = a;, há duas variáveis e um array. A atribuição copia o valor da referência. Se b[0] passa a 9, ler a[0] também dá 9. Se depois b recebe new int[]{1, 2}, apenas b passa a apontar para outro array: a[0] mantém 9 e b[0] passa a 1. Desenha separadamente as variáveis e os objetos ao interpretar este tipo de excerto.

Os argumentos dos métodos também são passados por valor. Quando esse valor é uma referência, o parâmetro e o chamador podem inicialmente alcançar o mesmo objeto. Um método pode alterar elementos desse objeto através do parâmetro; atribuir uma nova referência ao parâmetro não reatribui a variável do chamador. Dizer apenas que um array é "passado por referência" esconde esta diferença e conduz a previsões erradas sobre métodos de atualização.

final e fronteiras de mutabilidade

final int[] limits = {2, 5}; impede uma nova atribuição a limits depois da inicialização. Não impede limits[0] = 3, nem uma alteração feita através de outro alias. Uma referência fixa e um objeto imutável são propriedades diferentes. Também não há um bloqueio automático de escritas concorrentes nos elementos; a coordenação será tratada num módulo próprio.

String tem um contrato de imutabilidade. A chamada code.strip() produz um resultado textual sem alterar a string original. Com String code = " ab ";, ignorar o resultado mantém os espaços em code. É permitido ignorar o valor devolvido por um método. Quando pretendes usar o resultado, guarda-o ou passa-o para a próxima operação. A escolha de remover espaços deve respeitar o contrato da entrada; um identificador pode exigir rejeição de espaços em vez de normalização silenciosa.

Igualdade precisa de uma regra de ausência

== entre referências pergunta se ambas representam o mesmo objeto ou se ambas são null. Duas criações new String("READY") produzem referências distintas com conteúdo igual: == é false e equals é true. Evita usar identidade de objeto como substituto da comparação de texto.

Objects.equals(a, b) aceita referências nulas e considera dois null iguais. Isso é útil para comparar campos opcionais, mas não decide a regra de negócio. Numa atualização que exige um identificador recebido e um identificador guardado presentes, ambos null não podem autorizar a correspondência. Usa verificações de presença e depois igualdade por conteúdo. Esta correspondência não substitui a autorização do utilizador nem prova que o registo continua atual; essas são outras condições da operação. O mesmo mecanismo pode estar correto num formulário opcional e incompleto num controlo de identidade.

O tipo real do array continua a contar

Os arrays têm comprimento fixo depois de criados e índices de 0 a length - 1. Uma variável de referência não contém esses elementos; aponta para o array. É permitido atribuir um String[] a uma referência Object[]. Isso não transforma o objeto num array capaz de guardar qualquer Object.

Em Object[] values = new String[2]; values[0] = 12;, a conversão do valor int para Integer permite a atribuição segundo o tipo estático Object. Durante a escrita, o tipo real do array continua a exigir String ou null, pelo que ocorre ArrayStoreException. Não há conversão automática do número para "12". Ao diagnosticar, distingue erro de compilação, índice inválido e componente incompatível: cada um pede evidência diferente. Uma alteração do tipo da variável não elimina as restrições do objeto criado.

Aplicação guiada: um snapshot independente

Um componente recebe uma matriz int[][], guarda-a e devolve-a mais tarde. O contrato exige que alterações na entrada original não modifiquem o estado guardado e que alterações num resultado devolvido não afetem leituras futuras. Não existe concorrência; as linhas podem ter comprimentos diferentes e podem ser null. A identidade entre linhas não faz parte do contrato.

Clonar um int[] cria armazenamento independente para os valores primitivos. Clonar um int[][] cria apenas um novo array exterior: as referências para as linhas continuam partilhadas. Por isso, copiar apenas received.clone() na entrada e internal.clone() na saída não basta. Copia o array exterior e cada linha não nula nas duas fronteiras. Preserva null como null. A política tem de incluir a saída, pois devolver a matriz interna voltaria a expor os mesmos objetos. Se mais tarde surgirem objetos mutáveis nas folhas, esta solução precisa de ser revista; não é uma receita universal de cópia profunda.

Texto: unidade de código, ponto de código e significado

String usa UTF-16. length conta unidades de código char, e charAt devolve uma unidade. O ponto de código suplementar U+1F642 ocupa um par de surrogates: uma string que contém apenas esse ponto tem length igual a 2. codePointAt(0) combina o par válido e devolve o valor inteiro do ponto de código.

Isto importa ao definir limites e posições de texto numa integração. Um limite em bytes, em unidades UTF-16 ou em pontos de código não é a mesma regra. Pontos de código também não correspondem necessariamente aos símbolos visuais que uma pessoa conta. Nesta aula, a decisão é identificar qual unidade o contrato pede e evitar cortar entre as duas unidades de um par. Não deduzas uma política de apresentação a partir do resultado de length; deixa explícita a unidade usada em mensagens de validação e nos contratos entre componentes.

static int[][] snapshot(int[][] input) {
    java.util.Objects.requireNonNull(input);
    int[][] result = input.clone();
    for (int i = 0; i < result.length; i++) {
        if (result[i] != null) result[i] = result[i].clone();
    }
    return result;
}
NA PRÁTICA

Lê o método como membro de uma classe. Para {{2}, {7}}, o clone exterior conserva inicialmente ambas as referências; cada clone de linha separa os valores primitivos. O componente deve usar a mesma política ao receber a matriz e ao devolvê-la.

Este exemplo assume acesso sem concorrência e rejeita uma matriz exterior null.

Armadilhas comuns

Confundir reatribuição com mutação; tratar final como imutabilidade; aceitar dois identificadores ausentes por serem iguais; chamar cópia profunda a um clone exterior; contar pontos de código com length.

Tópicos relacionados: Tipos, conversões e validação · Objetos, interfaces e records · Coleções, generics e chaves · Concorrência, estado e cancelamento

Leva esta ideia contigo

Distingue a variável do objeto, define igualdade e ausência pelo contrato e copia todos os níveis mutáveis que atravessam uma fronteira de propriedade.

Criar conta

Referência: JLS 25: Arrays · Java SE 25; no preview features