Objetos, referências e igualdade em Java

Aprenda o que uma variável Java realmente armazena: um valor primitivo ou uma referência a um objeto. Acompanhe referências ao mesmo objeto e null, passe objetos para métodos e retorne-os, e construa classes a partir de outros objetos e de listas de objetos. Depois, escreva equals e hashCode para que listas, conjuntos e mapas comparem seus objetos pelo conteúdo, e mantenha a interface textual de um programa separada de sua lógica.

BásicoAula 6 de 11 do curso Curso de Programação orientada a objetos com Java

O que você vai aprender

  1. Explique o que uma variável Java armazena para tipos primitivos e de referência, preveja o efeito da atribuição em cada caso e trabalhe com variáveis que não se referem a nenhum objeto.
    • Compare variáveis de tipos primitivos e de referência
      Nomeie os tipos primitivos de Java, explique que uma variável de referência armazena uma referência a um objeto e preveja o efeito de copiar cada tipo de variável.
    • Acompanhe referências ao mesmo objeto, reatribuições e objetos inalcançáveis
      Acompanhe várias variáveis que se referem a um objeto, distinga a alteração de um objeto da alteração da referência de uma variável e explique o que acontece com objetos aos quais nada se refere.
    • Trabalhe com segurança com a referência nula
      Explique o significado de null, reconheça onde ele aparece, evite NullPointerException e decida quando null é um valor adequado.
  2. Explique como Java passa argumentos, escreva métodos que recebem e alteram objetos ou os comparam com outro objeto da mesma classe e retorne objetos novos ou existentes com segurança.
    • Use objetos como parâmetros de métodos
      Preveja o que um método pode ou não alterar ao receber um valor primitivo ou uma referência e escreva métodos que recebem objetos de outra classe ou de sua própria classe.
    • Retorne objetos de métodos
      Escreva métodos que criam e retornam novos objetos, retornam referências a objetos que já existem e evitam expor acidentalmente o estado interno de um objeto.
  3. Projete classes cujos campos são outros objetos ou listas de objetos, trabalhe com listas que armazenam objetos e decida quando objetos contidos devem ser compartilhados ou copiados.
    • Componha uma classe com outros objetos
      Modele relações de posse com campos de tipo de referência, delegue tarefas aos objetos contidos e decida se um objeto contido deve ser compartilhado ou copiado.
    • Armazene e processe objetos em listas
      Adicione, encontre, altere e remova objetos em um ArrayList e explique por que a lista armazena referências em vez de cópias.
    • Desenvolva uma classe em torno de uma lista de objetos
      Escreva uma classe que mantenha uma lista de objetos como campo, inicialize-a, ofereça métodos para trabalhar com seu conteúdo e mantenha a própria lista oculta.
  4. Distinga identidade de igualdade de conteúdo, escreva métodos equals e hashCode corretos e preveja como listas, conjuntos e mapas os usam.
    • Distinga identidade de igualdade de conteúdo
      Explique o que == e equals comparam em tipos primitivos, cadeias de caracteres, objetos empacotadores e suas próprias classes, e preveja o resultado de cada comparação.
    • Escreva um método equals
      Sobrescreva equals com a assinatura e as etapas corretas, compare cada tipo de campo corretamente e mantenha o método consistente com seu contrato geral.
    • Implemente hashCode em conjunto com equals
      Explique o contrato de hashCode, escreva hashCode para uma classe que sobrescreve equals e preveja como HashSet e HashMap se comportam com e sem esse método.
  5. Estruture um programa textual de modo que uma classe converse com o usuário e outras classes mantenham os dados e as regras, depois amplie-o e verifique-o sem misturar as duas partes.
    • Divida um programa em interface do usuário e classes de lógica
      Decida qual código pertence à interface do usuário e qual pertence à lógica, e conecte-os por meio de parâmetros de construtor e um método de início.
    • Amplie e verifique um programa com interface e lógica separadas
      Adicione recursos ampliando a lógica e chamando-a da interface, verifique a lógica diretamente com valores conhecidos e mantenha cada classe focada em uma tarefa.

Guia de estudo de Objetos e referências em Java

5 capítulos

1

Acompanhe valores e referências

Uma variável e um objeto são partes diferentes de um programa Java. Compreender o que cada variável armazena permite explicar por que uma atribuição cria números independentes enquanto outra dá a dois nomes acesso ao mesmo objeto. Comece pelos valores copiados e acompanhe as alterações uma instrução por vez. A mesma representação ajudará você a encontrar objetos ausentes, interpretar erros de null e decidir se uma alteração deve ser compartilhada.

1.1 O que uma variável armazena

Uma variável Java armazena um valor. A questão importante é se esse valor é um dado comum ou uma referência que aponta para um objeto. A atribuição copia esse valor nos dois casos.

Dados primitivos e referências a objetos

Java tem oito tipos primitivos: byte, short, int, long, float, double, char e boolean. Uma variável de tipo primitivo armazena seu dado diretamente. Uma variável de referência armazena uma referência a um objeto ou o valor especial null. Classes como String, Scanner e ArrayList, suas próprias classes e vetores são tipos de referência.

DeclaraçãoO que a variável armazena
int count = 5;O valor inteiro 5
Counter c = new Counter();Uma referência a um novo contador
Counter c = null;Nenhuma referência a objeto

Em new Counter(), Java cria um objeto, executa seu construtor e produz uma referência. A atribuição armazena essa referência em c. Desenhe a variável como uma caixa com uma seta que aponta para um objeto separado. Cada avaliação de new cria um objeto separado, mesmo quando os argumentos são iguais.

Copie o valor e acompanhe o resultado

Com tipos primitivos, alterar a variável copiada deixa a primeira variável inalterada:

int a = 5;
int b = a;
b = b + 1;

A última linha lê 5 de b, soma 1 e armazena 6 em b. O valor em a permanece 5.

Para um exemplo com referência, use esta pequena classe:

class Counter {
    private int value;

    Counter() { this.value = 0; }
    Counter(int value) { this.value = value; }
    void increase() { value++; }
    int value() { return value; }
}

Agora acompanhe estas instruções:

Counter first = new Counter();
Counter second = first;
second.increase();
System.out.println(first.value()); // 1

Apenas a primeira linha cria um contador. A segunda linha copia a referência, então a chamada de método altera o único contador que ambas as variáveis alcançam. Para criar um contador independente com os mesmos dados, escreva new Counter(first.value()). Copiar uma variável não chama um construtor.

Classes empacotadoras e strings imutáveis

Cada tipo primitivo tem uma classe empacotadora, como Integer, Double, Boolean ou Character. Variáveis de tipos empacotadores armazenam referências ou null. Java pode converter automaticamente entre um tipo primitivo e seu tipo empacotador; essas conversões são empacotamento automático e desempacotamento. Listas armazenam referências, então uma lista de inteiros é ArrayList<Integer>.

String também é um tipo de referência, mas seus objetos são imutáveis: seus caracteres não podem mudar após a criação.

String s = "java";
String t = s;
t = t.toUpperCase();

Aqui, s ainda se refere a “java” e t se refere a “JAVA”. A última linha atribui o resultado a t; ela não altera a cadeia de caracteres original compartilhada. Métodos de String podem reutilizar o objeto original quando nenhuma alteração é necessária.

Exibir um objeto usa seu método toString(). Sem uma sobrescrita, o resultado herdado tem uma forma como Counter@7e9e5f8a: nome da classe, @ e um código de dispersão hexadecimal. Defina toString() quando a saída exibida deva mostrar os dados do objeto.

Principais conclusões

  • A atribuição copia um valor: um dado primitivo ou uma referência a objeto.
  • Cada avaliação de new cria um objeto separado.
  • Copiar uma referência compartilha o objeto; não copia seus campos.
  • Tipos empacotadores e strings são tipos de referência; strings são imutáveis.
  • Exibir um objeto usa toString().

1.2 Objetos compartilhados e alteração de setas

Duas variáveis que se referem ao mesmo objeto são apelidos de referência. Um apelido oferece outro caminho até dados existentes. Para acompanhar um programa, mantenha as variáveis separadas dos objetos que elas alcançam.

A mutação altera dados; a reatribuição altera uma variável

Uma mutação altera o estado de um objeto, como seu peso armazenado. Uma reatribuição coloca um valor diferente em uma variável. Para uma variável de referência, isso muda sua seta.

Use uma caixa cujo peso é dado em quilogramas:

class Box {
    private double weight;
    Box(double weight) { this.weight = weight; }
    double getWeight() { return weight; }
    void setWeight(double weight) { this.weight = weight; }
}

Acompanhe esta sequência:

Box a = new Box(2);
Box b = a;
b.setWeight(5);
b = new Box(7);
  1. Desenhe a variável a apontando para o objeto A, com peso 2.
  2. Desenhe b apontando para esse mesmo objeto A. Ainda há uma caixa.
  3. Altere o peso dentro do objeto A para 5. Ambos os caminhos agora chegam ao peso 5.
  4. Crie o objeto B com peso 7 e redirecione apenas b para ele.

Ao final, a.getWeight() é 5 e b.getWeight() é 7. A quarta instrução não reinicia o objeto A nem move a para o objeto B.

AçãoAlteração no diagramaEfeito por meio de outros apelidos
b.setWeight(5)Altera um campo dentro do objetoEles veem o novo peso
b = new Box(7)Redireciona a seta de bEles mantêm suas setas existentes
b = nullRemove a seta de bEles ainda alcançam seus objetos
A alcançabilidade controla o tempo de vida de um objeto

Um objeto está alcançável enquanto o programa em execução puder seguir alguma cadeia utilizável de referências até ele. Essa cadeia pode passar por uma variável, um campo de objeto ou um elemento de lista. Portanto, o desaparecimento de uma variável local não é suficiente para tornar um objeto inalcançável se uma lista ainda o armazenar.

Continue o rastreamento com a = null;. O objeto A, que pesa 5 kg, agora não tem nenhum caminho alcançável neste exemplo. Ele está disponível para coleta de lixo. Java recupera automaticamente a memória de objetos inalcançáveis, mas o coletor escolhe quando fazê-lo; a atribuição não garante coleta imediata. O objeto B permanece alcançável por meio de b.

Você não libera objetos Java manualmente. Quando um programa perde sua última referência utilizável a um objeto, também perde a capacidade de inspecionar ou alterar os dados desse objeto.

Encontre os apelidos ocultos

Apelidos surgem quando um objeto é adicionado a uma lista, passado a um método, retornado de um método ou armazenado no campo de outro objeto. Cada operação pode passar uma referência adiante. Antes de alterar um objeto recebido de outro lugar, identifique quais outros trechos de código ainda podem alcançá-lo.

Um erro comum começa com o pensamento: “Copiei a variável, então tenho minha própria caixa.” Use um objeto novo quando as alterações precisarem ser independentes. Para diagnosticar o erro, conte as criações de objetos separadamente das atribuições, identifique cada objeto uma vez e processe cada instrução em ordem. Não redesenhe um objeto só porque uma segunda variável agora aponta para ele.

Principais conclusões

  • Apelidos apontam para um único objeto, portanto compartilham suas mutações.
  • A reatribuição redireciona uma variável e deixa os outros apelidos inalterados.
  • Acompanhe os campos e as setas de referência separadamente.
  • Um objeto inalcançável está disponível para coleta de lixo; o momento não é especificado.

1.3 Nenhum objeto: null e verificações seguras

O valor null significa que uma referência não aponta para nenhum objeto. Ele é útil para um resultado ausente, mas um programa deve tratar essa ausência antes de tentar usar um objeto.

Ausente é diferente de vazio

String text = ""; refere-se a uma cadeia de caracteres real de comprimento 0. String text = null; não se refere a nenhuma cadeia, então não há objeto no qual chamar length().

Um tipo primitivo não pode armazenar null: int x = null; não compila. Um tipo empacotador pode armazená-lo: Integer x = null; compila. Converter esse valor empacotado em um tipo primitivo com int y = x; lança uma NullPointerException, pois o desempacotamento precisa de um objeto empacotador.

Campos recebem valores padrão iniciais quando um objeto é criado. Campos de referência começam como null, campos numéricos como 0 e campos booleanos como false, até que a inicialização lhes atribua outros valores. Variáveis locais não têm valor padrão. Ler uma variável local antes de lhe atribuir um valor causa um erro de compilação, como “a variável pode não ter sido inicializada”.

Encontre a referência ausente

Chamar um método de instância por meio de null, acessar um campo de instância por meio dele ou ler o comprimento de um vetor por meio dele lança NullPointerException em tempo de execução. Por exemplo:

String name = null;
System.out.println(name.length());

Leia o número da linha da exceção e identifique qual referência nessa linha estava ausente. Mensagens explicativas são ativadas por padrão a partir do Java 15. Uma mensagem pode dizer Cannot invoke "SimpleDate.getYear()" because "this.birthday" is null. Nomes locais podem aparecer como marcadores, como <local4>, dependendo dos detalhes da compilação. Acompanhe o código de volta até o construtor, a atribuição ou a busca que deveria ter fornecido o objeto.

Exibir uma variável String cujo valor é null exibe “null”; concatená-la produz um texto como “Hi null”. Essas operações não precisam chamar um método de instância por meio dessa variável. Elas podem revelar dados ausentes antes que uma chamada de método posterior falhe.

Verifique antes de usar

O operador && usa avaliação de curto-circuito: ele avalia o lado direito apenas se o lado esquerdo for verdadeiro.

if (name != null && name.length() > 0) {
    System.out.println(name);
}

Quando name é null, o primeiro teste é falso e length() é ignorado. Inverter os dois testes elimina essa proteção: a chamada de método seria executada primeiro.

FinalidadeExpressão segura
Verificar ausêncianame == null
Verificar presençaname != null
Comparar com um comando fixo"quit".equals(command)

A cadeia de caracteres fixa é um objeto conhecido, então seu equals pode receber null e retornar falso. command.equals("quit") falha quando command é null. Da mesma forma, x.equals(null) não é uma maneira de verificar se x existe: ela já exige um objeto.

Faça da ausência uma decisão clara de projeto

Inicialize um campo de lista com new ArrayList<>() para que uma coleção vazia esteja disponível imediatamente. Se uma busca retornar null para “não encontrado”, documente esse resultado e verifique-o no local da chamada. Evite preencher todos os campos com objetos arbitrários apenas para silenciar uma exceção: forneça o objeto de que o programa precisa ou dê à ausência um significado claro que o código que chama o método trate.

Principais conclusões

  • null significa ausência de objeto; uma cadeia de caracteres vazia é um objeto utilizável.
  • Campos recebem valores padrão, mas variáveis locais precisam receber valores antes de serem lidas.
  • Desreferenciar null e desempacotar um objeto empacotador nulo causam falhas em tempo de execução.
  • Coloque a verificação de null antes do acesso ao membro em &&.
  • Chame comparações com texto fixo na cadeia de caracteres conhecida.
  • Inicialize coleções e trate deliberadamente resultados ausentes de buscas.
2

Use objetos em chamadas de métodos

Métodos permitem que objetos trabalhem juntos. Uma chamada pode ler ou alterar um objeto compartilhado, criar um resultado independente ou retornar um objeto já armazenado em outro lugar. Essas escolhas afetam o que quem chamou o método vê depois da chamada. Acompanhe explicitamente as referências dos parâmetros e dos retornos e coloque as comparações na classe que contém os dados. Depois, decida quais métodos devem compartilhar o estado existente e quais devem protegê-lo com uma cópia.

2.1 Parâmetros recebem valores copiados

Java passa todos os argumentos por valor. Um parâmetro é uma nova variável que recebe uma cópia do valor do argumento. Para um argumento que é um objeto, o valor copiado é uma referência.

Alterar o parâmetro ou alterar o objeto?

Um parâmetro de tipo primitivo tem seus próprios dados:

static void addTen(int n) {
    n = n + 10;
}

Se x for 5, chamar addTen(x) deixa x em 5. Apenas o parâmetro local n passa a ser 15.

Um parâmetro de referência pode alcançar o mesmo objeto que quem chamou o método. Suponha que Person.growOlder() aumente a idade armazenada:

static void celebrate(Person p) {
    p.growOlder();
}

static void replace(Person p) {
    p = new Person("Bo", 30);
}

Durante celebrate(ana), o parâmetro p e a variável ana são apelidos de referência. A idade alterada permanece visível depois. Durante replace(ana), atribuir um valor a p redireciona apenas esse parâmetro. A variável ana de quem chamou o método continua se referindo à pessoa original. Um método que troca dois parâmetros de referência tem a mesma limitação: ele troca suas próprias cópias, não as variáveis de quem o chamou.

Dentro de um métodoQuem o chamou observa
Atribuir um valor a um parâmetro primitivoNenhuma alteração na variável de quem chamou
Alterar o objeto do parâmetroEstado alterado do objeto compartilhado
Reatribuir um parâmetro de referênciaNenhuma alteração na variável de quem chamou
Receba um objeto de outra classe

Um controle de entrada pode ler a altura de uma pessoa e manter sua própria contagem de verificações. Esta classe espera uma pessoa não nula com um método getHeight() que retorna centímetros:

class HeightGate {
    private int minHeight;
    private int checks;

    HeightGate(int minHeight) {
        this.minHeight = minHeight;
    }

    boolean allowIn(Person p) {
        checks++;
        return p.getHeight() >= minHeight;
    }

    int checks() { return checks; }
}

Para um controle configurado para 120 cm, verificar pessoas de 132 cm e 115 cm retorna verdadeiro e falso. O controle informa então 2 verificações. Ele lê a altura da pessoa por meio de um método público e altera seu próprio contador.

Receba outro objeto da mesma classe

Dentro de SimpleDate.before(SimpleDate other), this é a data que recebe a chamada e other é o argumento. Uma classe pode ler os campos privados de qualquer objeto dessa mesma classe.

Para os campos day, month e year, compare nesta ordem:

boolean before(SimpleDate other) {
    if (year != other.year) {
        return year < other.year;
    }
    if (month != other.month) {
        return month < other.month;
    }
    return day < other.day;
}

Para 14 de março de 2001 e 2 de novembro de 2001, os anos coincidem e o mês 3 é menor que o mês 11, então a primeira data é anterior. Datas iguais retornam falso porque nenhuma é anterior à outra.

Person.olderThan(other) pode delegar a birthday.before(other.birthday). A comparação de datas pertence à classe que contém os dados da data. Métodos que chamam métodos de instância em um parâmetro precisam de um argumento não nulo; verifique-o ou inclua essa exigência na documentação de uso do método.

Principais conclusões

  • Todo parâmetro recebe um valor copiado, incluindo parâmetros de referência.
  • Uma mutação pode afetar um objeto compartilhado; a reatribuição de parâmetro não pode substituir a variável de quem chamou o método.
  • this identifica o objeto receptor; um parâmetro de objeto identifica o argumento.
  • Uma classe pode acessar campos privados em outro objeto da mesma classe.
  • Delegue comparações à classe que contém os dados relevantes.

2.2 Retorno de um objeto novo ou existente

Um método que retorna um objeto retorna uma referência. Seu tipo informa que tipo de objeto pode ser retornado, mas não informa se o objeto é novo, compartilhado ou ausente. A finalidade do método deve deixar essa escolha clara.

Crie um resultado

Um método fábrica fornece um objeto. Ele pode criar um ou reutilizar um existente. Aqui, cada chamada cria deliberadamente um tíquete separado:

class TicketMachine {
    private int nextNumber = 1;

    Ticket issue() {
        Ticket t = new Ticket(nextNumber);
        nextNumber++;
        return t;
    }
}

A máquina espera um construtor Ticket que armazene seu número. Três chamadas criam três tíquetes numerados 1, 2 e 3. Retornar uma variável local é seguro: sua referência é copiada para quem chamou o método, que pode manter o objeto alcançável depois que a variável local sai de escopo.

Um método de cópia promete um objeto independente. Na classe Counter:

Counter copy() {
    return new Counter(this.value);
}

Após Counter a = new Counter(4); Counter b = a.copy(); b.increase();, a.value() é 4 e b.value() é 5. Substituir a.copy() por a compartilharia o contador.

Um método também pode construir um objeto calculado. Para um Point com campos double x e y:

Point midpoint(Point other) {
    return new Point((x + other.x) / 2.0,
                     (y + other.y) / 2.0);
}

Os pontos (2, 4) e (6, 10) resultam em (4,0; 7,0). O método calcula cada coordenada e passa ambas a um novo ponto; nenhum dos pontos originais muda.

Retorne um objeto já armazenado

O findByName de um cadastro deve retornar a pessoa armazenada correspondente se quem chama o método precisar trabalhar com essa entrada. Criar new Person(name, 0) retornaria dados substitutos inventados e desvincularia alterações posteriores do cadastro.

Uma busca pode retornar null quando não há correspondência:

Person p = register.findByName("Lin");
if (p != null) {
    System.out.println(p);
} else {
    System.out.println("Not found");
}

Se p for a pessoa armazenada, alterações por meio de p também alteram a pessoa alcançada pelo cadastro. Uma instrução return não copia o objeto.

Mantenha o estado interno mutável sob controle

Suponha que Person.getBirthday() retorne seu campo SimpleDate e que essa data tenha métodos modificadores. Quem chama o método pode então alterar a data de nascimento da pessoa sem chamar um método de Person. Isso é uma exposição de estado mutável.

Para proteger o campo, retorne uma nova data construída a partir de seus valores:

SimpleDate getBirthday() {
    return new SimpleDate(birthday.getDay(),
                          birthday.getMonth(),
                          birthday.getYear());
}

Essa versão exige que um objeto de data de nascimento tenha sido inicializado. Outros projetos usam uma data imutável ou oferecem consultas específicas sem um método de acesso à data. Escolha o comportamento de cada método: find normalmente compartilha uma entrada armazenada, enquanto copy cria independência. O nome curto de um método de acesso, por si só, não garante proteção.

Principais conclusões

  • O retorno de um objeto passa uma referência a quem chamou o método.
  • Métodos fábrica podem criar ou reutilizar objetos; um método de cópia cria um objeto separado.
  • Métodos de busca podem compartilhar entradas existentes e devem definir um resultado para a ausência de correspondência.
  • Retornar um campo mutável pode permitir que quem chamou o método contorne os métodos do proprietário.
3

Conecte objetos e listas

Um modelo maior é uma rede de objetos: uma pessoa tem uma data, uma playlist tem uma lista e a lista armazena músicas. Cada conexão é uma referência com consequências para o compartilhamento e as alterações. Construa essas conexões deliberadamente, delegue tarefas ao objeto que sabe realizá-las e mantenha as regras da coleção por trás de métodos úteis. Uma cópia do contêiner pode proteger a composição da lista e ainda compartilhar todos os objetos dentro dela.

3.1 Objetos com campos de tipo de referência

Composição constrói uma classe usando campos cujos tipos são outras classes. Ela expressa uma relação de posse: uma pessoa tem uma data de nascimento; um curso tem um professor. O objeto externo armazena uma referência ao objeto contido.

Crie e conecte os objetos

Um construtor pode receber uma data existente:

class Person {
    private String name;
    private SimpleDate birthday;

    Person(String name, SimpleDate birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    boolean olderThan(Person other) {
        return birthday.before(other.birthday);
    }

    public String toString() {
        return name + ", born " + birthday;
    }
}

Chamar new Person("Ana", new SimpleDate(14, 3, 2001)) primeiro cria uma data e depois fornece sua referência à pessoa. Outra opção é um construtor de pessoa receber dia, mês e ano e criar a data dentro de seu corpo. Com essa abordagem, quem chama o construtor não mantém uma referência à data recém-criada.

Delegue tarefas ao objeto contido

A chamada birthday.before(other.birthday) pede ao objeto de data que compare datas. Person não precisa repetir as comparações de ano, mês e dia. A concatenação em toString() também delega: Java chama o toString() do objeto de data de nascimento para obter seu texto.

Um objeto de data pode ajudar a calcular a idade. Comece com o ano atual menos o ano de nascimento. Depois, forme a data do aniversário deste ano e subtraia um se a data atual vier antes dela. Uma pessoa nascida em 14 de março de 2001 tem 24 anos em 1 de janeiro de 2026: a diferença entre os anos é 25, mas o aniversário ainda não chegou. Em 14 de março de 2026, o aniversário já chegou e a idade é 25. Manter a comparação de datas em SimpleDate evita reproduzir suas regras em cada método de pessoa.

Decida se o objeto interno é compartilhado

O construtor acima armazena seu argumento diretamente. Se duas pessoas receberem um SimpleDate mutável, ambos os campos de data de nascimento apontarão para a mesma data. Alterar essa data afeta as duas pessoas.

Escolha do construtorConsequência para as referências
Armazenar a referência à data recebidaQuem chamou o construtor e a pessoa podem compartilhar a data
Construir uma nova data a partir dos valores recebidosA pessoa começa com sua própria data
Armazenar uma data imutávelO compartilhamento não pode alterar os valores da data

Um construtor defensivo pode copiar o dia, o mês e o ano da data recebida. Uma data imutável não tem métodos que alterem seu estado e mantém seus valores fixos; campos primitivos simples podem ser final. Copiar na entrada é importante mesmo que um método de acesso retorne uma cópia depois, pois quem chamou o construtor originalmente ainda pode ter o objeto recebido.

Trate conexões opcionais

Um Course pode ainda não ter professor. Se esse for um estado significativo, teacherName() pode verificar o campo e retornar “ainda sem professor”. Caso contrário, exija um professor na construção.

Em course.getTeacher().getName(), cada chamada de instância precisa de um objeto: um curso nulo falha em getTeacher(), e um professor nulo falha em getName(). Um método no curso pode manter essa verificação de ausência em um só lugar e evitar que todos os chamadores precisem conhecer a mesma cadeia.

Principais conclusões

  • A composição conecta um objeto externo a outros objetos por meio de campos.
  • Delegue operações ao objeto que contém os dados relevantes.
  • Armazenar uma referência mutável recebida pode compartilhar o estado com quem chamou o construtor.
  • Cópias ou objetos internos imutáveis podem proteger um estado independente.
  • Campos de objetos opcionais precisam de uma política clara de tratamento de null.

3.2 Listas armazenam referências a seus elementos

Um ArrayList<Person> é um objeto cujos elementos armazenam referências a pessoas. A lista controla sua sequência de entradas; cada pessoa controla seus próprios campos. São tipos separados de alteração.

Adicione um objeto e alcance-o novamente
ArrayList<Person> people = new ArrayList<>();
people.add(ana);
people.get(0).growOlder();

Esse código precisa de import java.util.ArrayList; e de uma pessoa existente e não nula ana. add copia a referência. A chamada growOlder() alcança a mesma pessoa que ana, então a alteração da idade fica visível pelos dois caminhos. Aqui, a lista nunca cria uma segunda pessoa.

Você pode criar um objeto ao adicioná-lo: people.add(new Person("Lin", 1998));, usando um construtor de pessoa que aceita um nome e um ano de nascimento. A lista fornece então ao programa um caminho até essa nova pessoa por meio de get ou de um laço. Argumentos de tipo de listas devem ser tipos de referência, então use ArrayList<Integer> para números inteiros.

Calcule a partir de objetos e selecione um objeto

Suponha que uma lista armazene pacotes com pesos de 2,5 kg, 4,0 kg e 1,5 kg. Um laço soma o peso de cada pacote:

double total = 0;
for (Parcel p : parcels) {
    total += p.getWeight();
}

As somas acumuladas são 2,5, 6,5 e 8,0 kg. Selecionar o pacote mais pesado exige uma referência e uma comparação:

Parcel heaviest = parcels.get(0);
for (Parcel p : parcels) {
    if (p.getWeight() > heaviest.getWeight()) {
        heaviest = p;
    }
}

Essa seleção exige uma lista não vazia. O resultado é o objeto do pacote de 4,0 kg, então quem chama o método também pode usar seus outros métodos. Para uma lista vazia, decida qual será o resultado de ausência antes de ler o elemento 0.

Busque em todas as entradas

Uma busca por nome em uma classe que armazena people pode usar:

Person findByName(String name) {
    for (Person p : people) {
        if (p.getName().equals(name)) {
            return p;
        }
    }
    return null;
}

Essa versão espera pessoas não nulas com nomes não nulos. A primeira correspondência é retornada imediatamente. O return null final pertence ao trecho após o laço: uma comparação sem correspondência na primeira pessoa não exclui correspondências posteriores.

contains, indexOf e remove(Object) fazem buscas usando equals. Se Person herdar a igualdade baseada em identidade, uma pessoa recém-criada não corresponderá a uma existente só porque seus campos coincidem.

Remova uma entrada sem destruir um objeto

Um objeto pode aparecer duas vezes em uma lista ou em várias listas. Remover uma entrada não destrói o objeto nem remove outras referências a ele.

Não remova entradas diretamente da mesma lista durante um percurso for-each. Isso pode lançar ConcurrentModificationException. Reúna os candidatos primeiro e depois remova-os:

ArrayList<Person> toRemove = new ArrayList<>();
for (Person p : people) {
    if (p.getName().equals("Lin")) {
        toRemove.add(p);
    }
}
people.removeAll(toRemove);

O percurso termina antes que a composição da lista mude. removeAll usa igualdade, então remove entradas iguais aos candidatos reunidos.

Principais conclusões

  • Adicionar um objeto a uma lista compartilha uma referência a ele.
  • Um laço pode calcular um total ou manter uma referência a um objeto selecionado.
  • Uma busca sem correspondência só retorna após todos os candidatos serem verificados.
  • Operações de pertinência em listas dependem de equals.
  • Remover uma entrada deixa outras referências intactas.
  • Reúna primeiro os elementos a remover e altere a lista após o percurso.

3.3 Uma classe proprietária de uma lista

Uma classe pode manter uma lista em um campo privado e oferecer operações que descrevam sua finalidade. Uma playlist armazena músicas, conta-as e calcula sua duração. Quem chama seus métodos não deve precisar gerenciar a lista diretamente.

Inicialize e encapsule a coleção

Esta playlist espera objetos Song não nulos com um método seconds():

import java.util.ArrayList;

class Playlist {
    private String name;
    private ArrayList<Song> songs;

    Playlist(String name) {
        this.name = name;
        this.songs = new ArrayList<>();
    }

    void add(Song song) { songs.add(song); }
    int count() { return songs.size(); }

    int totalSeconds() {
        int total = 0;
        for (Song song : songs) {
            total += song.seconds();
        }
        return total;
    }
}

Sem new ArrayList<>(), o campo de referência permanece null; a primeira chamada a add falha. Uma lista vazia já é um objeto utilizável, com tamanho 0.

Para músicas com duração de 210 s, 185 s e 247 s, count() retorna 3. O total cresce de 210 para 395 e depois para 642 s. A divisão inteira por 60 resulta em 10 minutos, com resto de 42 segundos. Um método longest() pode selecionar e retornar a música de 247 s, usando o mesmo padrão de seleção de referência do pacote mais pesado. Defina um resultado de ausência para uma playlist vazia.

Proteja a composição da lista por meio de métodos

Manter songs privado bloqueia o acesso direto ao campo, mas return songs; expõe essa própria lista. Quem chama o método poderia chamar clear() e contornar todas as regras da playlist.

Operações específicas como add, count e totalSeconds mantêm a responsabilidade com a playlist. Seu método add poderia impor um tamanho máximo ou recusar duplicatas com songs.contains(song). Verificações de duplicidade baseadas em conteúdo precisam que o equals da classe de música defina quais campos tornam duas músicas iguais.

Quando um construtor receber uma lista inicial, copie sua estrutura com this.songs = new ArrayList<>(initialSongs);. Armazenar o parâmetro diretamente permitiria que quem chamou o construtor mantivesse um apelido de referência à lista interna da playlist.

Uma nova lista ainda pode compartilhar músicas

Uma consulta pode retornar new ArrayList<>(songs). Isso é uma cópia superficial: um novo objeto de lista que contém as referências existentes às músicas.

Ação de quem chamou o método na cópia retornadaEfeito na playlist
Remover uma entradaA composição da playlist permanece igual
Esvaziar a cópiaA composição da playlist permanece igual
Alterar uma música compartilhadaA playlist vê o estado alterado dessa música

Uma consulta songsLongerThan(200) cria uma nova lista de resultados, percorre as músicas armazenadas e adiciona aquelas cuja duração excede 200 s. Para as três músicas acima, ela retorna as músicas de 210 s e 247 s. Quando os valores das músicas também precisarem ser independentes, copie as músicas mutáveis ou torne Song imutável.

Principais conclusões

  • Um objeto proprietário, sua lista e os elementos dela são objetos separados.
  • Inicialize a lista antes de usá-la.
  • Ofereça operações que imponham as regras da coleção do proprietário.
  • Copie listas recebidas para evitar um apelido externo à lista interna.
  • Uma cópia superficial de lista compartilha os objetos de seus elementos.
4

Defina o que torna objetos iguais

Dois objetos podem descrever a mesma coisa e ainda ser objetos separados. Java oferece testes diferentes para identidade e conteúdo, e as coleções dependem da escolha feita por sua classe. Comece escolhendo os campos que definem a igualdade e depois implemente equals e hashCode em conjunto. Seus contratos explicam por que uma lista pode encontrar um objeto igual, por que uma colisão de hash é permitida e por que alterar uma chave armazenada pode prejudicar uma consulta.

4.1 Identidade e conteúdo correspondente

Identidade verifica se duas referências apontam para o mesmo objeto. Igualdade de conteúdo verifica se objetos têm os mesmos dados relevantes, de acordo com as regras da classe. Dois objetos de livro separados podem descrever o mesmo título e ano de publicação sem serem idênticos.

Escolha a comparação adequada
Valores comparadosSignificado de ==Significado de equals
PrimitivosCompara valores primitivosUm tipo primitivo não tem método de instância
ReferênciasMesmo objeto, ou ambos nullRegra de igualdade da classe
StringsMesmo objeto de cadeia de caracteresConteúdo de caracteres correspondente
Classe que herda Object.equalsMesmo objetoMesmo objeto

Toda classe herda equals(Object) de Object. Essa versão padrão verifica a identidade. Java não inspeciona automaticamente todos os campos de uma nova classe.

String a = "java";
String b = new String("java");
System.out.println(a == b);      // false
System.out.println(a.equals(b)); // true

new String cria um objeto separado, enquanto String.equals compara seus caracteres. A comparação de texto distingue maiúsculas e minúsculas: "Java".equals("java") é falso. equalsIgnoreCase faz uma comparação sem essa distinção quando essa é a regra desejada.

Por que a identidade às vezes parece comparar conteúdo

Literais de string iguais compartilham um objeto de cadeia de caracteres, então "java" == "java" é verdadeiro. Isso pode ocultar um erro em um teste pequeno. Um texto obtido de uma entrada ou construído durante a execução de um programa não precisa compartilhar esse objeto. Use equals para comparar conteúdo, independentemente de como o texto foi obtido.

Comparações de objetos empacotadores têm uma armadilha semelhante:

Integer a = 127, b = 127;
Integer c = 1000, d = 1000;
System.out.println(a == b);      // true
System.out.println(c.equals(d)); // true

O empacotamento automático reutiliza objetos Integer para valores de -128 a 127. O fato de valores maiores compartilharem ou não um objeto não é uma regra portátil de comparação numérica. Em uma configuração padrão, os dois objetos empacotadores de 1000 são separados, mas um cache maior é permitido. Use o equals do tipo empacotador para comparar seus valores ou compare valores primitivos. Se um lado de == for um int, Java desempacota o objeto empacotador e compara valores numéricos; desempacotar um objeto empacotador nulo falha.

Decida quais campos definem a igualdade

Para um catálogo de livros, título e ano de publicação podem definir a igualdade, enquanto quem está com o livro emprestado é ignorado. Esses campos selecionados devem orientar o método de igualdade. Outro programa poderia escolher um identificador único; a regra útil depende do que seus objetos representam.

Um ArrayList usa equals em contains, indexOf e remove(Object). Suponha que Book apenas armazene campos e herde equals. Adicionar um new Book("Dune", 1965) não torna contains(new Book("Dune", 1965)) verdadeiro: a segunda expressão cria um objeto diferente. Sobrescrever a igualdade por título e ano permite que uma busca encontre esse conteúdo igual.

HashSet e HashMap também usam hashCode para reduzir o conjunto de objetos candidatos antes de verificar a igualdade. Portanto, definir igualdade de conteúdo também exige uma regra correspondente de código de dispersão.

Principais conclusões

  • Para referências, == testa identidade, enquanto equals segue a regra da classe.
  • Object.equals compara identidade até ser sobrescrito.
  • Use String.equals para comparar conteúdo textual.
  • Literais compartilhados e o cache de objetos empacotadores podem tornar testes de identidade enganosos.
  • Escolha deliberadamente os campos de igualdade; as coleções dependem dessa escolha.

4.2 Implementação correta de equals

Um método de igualdade deve substituir o equals(Object) herdado e comparar os campos que definem o significado do objeto. A assinatura correta é tão importante quanto as comparações dentro do método.

Um padrão completo de igualdade

Esta classe usa título e ano, permite um título nulo e mantém fixos os campos de igualdade:

import java.util.Objects;

final class Book {
    private final String title;
    private final int year;

    Book(String title, int year) {
        this.title = title;
        this.year = year;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof Book)) {
            return false;
        }
        Book book = (Book) other;
        return year == book.year
            && Objects.equals(title, book.title);
    }

    @Override
    public int hashCode() {
        return Objects.hash(title, year);
    }
}

Leia equals em quatro etapas. A verificação de identidade aceita imediatamente o mesmo objeto. A verificação de tipo rejeita tudo que não é um livro, incluindo null. A conversão explícita fornece uma variável Book pela qual os campos podem ser lidos. A expressão final verifica os dois campos de igualdade.

O tipo do parâmetro é Object, não Book. Escrever equals(Book other) faz uma sobrecarga do nome com outro método e deixa o equals(Object) herdado inalterado. Uma chamada direta pode então parecer funcionar enquanto ArrayList.contains ainda usa identidade. @Override faz o compilador detectar esse erro de assinatura.

A partir do Java 16, a verificação de tipo e a conversão explícita podem ser combinadas: if (!(other instanceof Book book)) { return false; }. Após a verificação ser bem-sucedida, book fica disponível para comparar os campos.

Adeque as comparações aos tipos dos campos

Use == para campos de tipos inteiros, de caracteres e booleanos. Use o equals de um objeto para seu conteúdo. Para um campo de objeto que pode ser nulo, Objects.equals(a, b) retorna verdadeiro quando ambos são nulos, falso quando apenas um é nulo e, caso contrário, usa igualdade.

Para campos double, Double.compare(x, other.x) == 0 fornece tratamento consistente de valores especiais: dois valores NaN são comparados como iguais, e zero positivo e negativo são distinguidos. Combine essa escolha com uma regra de hash compatível. O == direto de ponto flutuante trata NaN de forma diferente.

Verifique o contrato de igualdade

Para referências não nulas, a igualdade deve ser:

  • Reflexiva: x.equals(x) é verdadeiro.
  • Simétrica: x.equals(y) e y.equals(x) concordam.
  • Transitiva: se x é igual a y e y é igual a z, x é igual a z.
  • Consistente: chamadas repetidas concordam enquanto os dados comparados permanecem inalterados.
  • Falsa para null: x.equals(null) é falso.

Dois livros para “Dune”, 1965, são comparados como iguais; alterar um dos anos para 1984 os torna desiguais. Um livro não deve ser comparado como igual a uma cadeia de caracteres que contém seu título: a cadeia rejeitaria o livro, violando a simetria.

Algumas implementações usam other == null || getClass() != other.getClass() para verificar o tipo. Isso rejeita classes diferentes em tempo de execução. instanceof pode aceitar subclasses; para a classe final acima, não existe subclasse e as duas verificações concordam.

Principais conclusões

  • Sobrescreva com public boolean equals(Object other) e @Override.
  • Verifique a identidade e o tipo, converta a referência e compare os campos de igualdade.
  • Use comparações de objetos seguras para valores nulos quando campos puderem estar ausentes.
  • Respeite a reflexividade, a simetria, a transitividade, a consistência e a rejeição de null.
  • equals(Book) sobrecarrega o método e não substitui equals(Object).
  • As escolhas de verificação de tipo importam quando subclasses são permitidas.

4.3 Códigos de dispersão correspondentes e chaves estáveis

Um código de dispersão é um inteiro usado para restringir uma busca. HashSet e HashMap o usam para encontrar objetos candidatos e depois usam equals para confirmar uma correspondência. Ele não é um identificador único de objeto.

O contrato vale em uma direção

Objetos iguais devem retornar o mesmo código de dispersão. Objetos desiguais também podem compartilhar um código; isso é uma colisão. Uma colisão não os torna iguais, pois a igualdade ainda é verificada.

Um código de dispersão deve permanecer consistente enquanto as informações usadas por equals não mudarem. Em uma implementação correta, códigos diferentes excluem a igualdade, mas códigos iguais não a estabelecem.

Quando uma classe sobrescreve equals, mas herda Object.hashCode, objetos separados e iguais geralmente recebem códigos diferentes. Um conjunto pode então manter ambos, e uma consulta em um mapa usando uma nova chave igual pode não encontrar a entrada. A falha exata não é garantida, pois objetos diferentes podem ter colisões nos códigos herdados. Corrija o contrato em vez de depender de uma falha específica de consulta.

Calcule a partir dos campos de igualdade

Para a classe de livro que compara título e ano:

@Override
public int hashCode() {
    return Objects.hash(title, year);
}

Importe java.util.Objects. Os mesmos valores de campo agora produzem o mesmo resultado. Um conjunto de livros armazena uma entrada após a adição de dois livros iguais, e um mapa pode recuperar um valor usando uma nova chave de livro igual.

Objects.hash combina os hashes dos campos usando 31. Para “Dune”, o hash da cadeia de caracteres é 2141736. Combinar esse título e o ano 1965 resulta em 31 multiplicado pela soma de 31 e 2141736, mais 1965: o resultado é 66396742.

O cálculo de hash de strings também usa 31. Para “ab”, os códigos de caractere 97 e 98 resultam em 97 multiplicado por 31 mais 98, ou 3105. Cálculos mais longos podem ultrapassar o intervalo de int e dar a volta, então um código de dispersão negativo é normal.

Regra de hashConsequência para o contrato
Usar os mesmos campos de igualdadeObjetos iguais recebem códigos correspondentes
Adicionar um campo que a igualdade ignoraObjetos iguais podem receber códigos diferentes
Usar menos campos de igualdadePermitido, mas pode criar mais colisões
Sempre retornar 1Permitido, mas prejudica o desempenho da busca
Não altere a chave durante o armazenamento

Suponha que o ano de um livro seja mutável e afete seu código de dispersão. Após a inserção em um conjunto, alterar o ano pode fazer contains(book) falhar mesmo com a mesma referência: a consulta agora busca usando um código diferente. O conjunto ainda pode informar tamanho 1.

Mantenha estáveis os campos de igualdade enquanto um objeto for elemento de um conjunto ou chave de um mapa. Se eles precisarem mudar, remova o objeto enquanto seus valores antigos ainda estiverem em vigor, altere-o e insira-o novamente. Tentar removê-lo somente após a alteração pode causar o mesmo problema de consulta.

A igualdade gerada ainda precisa de uma escolha adequada de dados

Java 16 e versões posteriores oferecem records, como record Book(String title, int year) {}. Um record gera os métodos de acesso title() e year(), além de igualdade, hash e uma representação textual legível a partir de todos os seus componentes. Os campos dos componentes são final. Esse exemplo é um valor imutável útil porque seus componentes são uma cadeia de caracteres e um inteiro; um record que armazenasse uma lista mutável ainda compartilharia o estado dessa lista.

Para classes comuns, uma IDE pode gerar equals e hashCode juntos. Selecione os campos que definem a igualdade desejada e mantenha os dois métodos alinhados quando essa escolha mudar.

Principais conclusões

  • Objetos iguais exigem códigos de dispersão iguais; colisões entre objetos desiguais são permitidas.
  • Sobrescreva hashCode sempre que sobrescrever equals.
  • Calcule o hash apenas de dados compatíveis com a regra de igualdade.
  • Mantenha estáveis os campos relevantes para o hash enquanto o objeto pertencer a um conjunto ou for usado como chave de mapa.
  • Remova uma chave antes de alterá-la e depois insira-a novamente.
  • Records geram igualdade a partir de seus componentes; componentes mutáveis ainda exigem cuidado.
5

Atribua uma tarefa clara a cada classe

Um programa textual tem dois tipos de tarefa: conversar com o usuário e gerenciar os dados e as regras. Manter essas tarefas em classes separadas facilita a verificação e a reutilização das regras. Conecte as classes por meio de parâmetros de construtor e pequenos métodos públicos. Depois, adicione um recurso primeiro onde sua regra pertence, verifique o resultado com valores conhecidos e forneça à interface um comando que o chame e exiba a resposta.

5.1 Entrada, saída e regras do programa

A interface do usuário lê entradas e exibe saídas. A lógica do programa armazena dados e aplica regras. Um projeto separado passa valores para a lógica e recebe resultados; suas classes de lógica não usam Scanner nem System.out.

Conecte os objetos em main
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    TemperatureLog log = new TemperatureLog();
    UserInterface ui = new UserInterface(scanner, log);
    ui.start();
}

Com import java.util.Scanner;, essa montagem cria um leitor de entrada, um registro e uma interface. Ela pertence à classe de entrada do programa. UserInterface armazena os objetos fornecidos em campos para que todos os comandos trabalhem com o mesmo registro.

TarefaClasse responsável
Ler texto de comandoInterface do usuário
Armazenar uma leituraRegistro de temperaturas
Calcular média ou máximoRegistro de temperaturas
Arredondar e exibir um resultadoInterface do usuário
Rejeitar uma temperatura impossívelRegistro de temperaturas
Explicar uma entrada inválida ao usuárioInterface do usuário
Retorne valores que qualquer chamador possa usar

Um registro pode fornecer add(double celsius), count(), average() e max(). Leituras de 18,5, 21,0 e 19,5 °C têm soma 59,0, contagem 3, máximo 21,0 e média de aproximadamente 19,6667 °C. A interface pode arredondar a média retornada com Math.round(average * 10) / 10.0 e exibir 19,7 °C.

Retornar o número facilita verificações diretas. Um método chamado printAverage() que apenas exibe o resultado acopla um cálculo à saída no console, o que dificulta a reutilização.

Para um laço simples de comandos, suponha que add aceite temperaturas válidas e count retorne sua quantidade:

class UserInterface {
    private Scanner scanner;
    private TemperatureLog log;

    UserInterface(Scanner scanner, TemperatureLog log) {
        this.scanner = scanner;
        this.log = log;
    }

    void start() {
        while (true) {
            String command = scanner.nextLine();
            if ("quit".equals(command)) {
                break;
            }
            if ("count".equals(command)) {
                System.out.println(log.count());
            }
        }
    }
}

O laço cuida da interação. Ele solicita um resultado ao registro e o exibe sem saber como as leituras são armazenadas. Um comando de média deve tratar um registro vazio de acordo com o resultado documentado da lógica ou verificar count() antes de chamar um método que exige leituras.

Valide no ponto adequado

A lógica pode fazer add retornar falso sem armazenar uma leitura abaixo de -273,15 °C. Assim, todos os chamadores recebem a mesma regra de dados. A interface decide como informar essa rejeição e solicitar a entrada novamente. Converter uma entrada não numérica em uma mensagem de erro útil também é tarefa da interface.

Use um Scanner para System.in e passe-o aos leitores. Vários scanners podem disputar entradas armazenadas em buffer, e fechar um deles fecha o fluxo de entrada subjacente. Mantenha o tempo de vida do scanner compartilhado sob controle do código responsável pela entrada do programa.

A lógica pode mais tarde atender a outra interface por meio dos mesmos métodos públicos. A reutilização vem de manter cálculos e regras de validade disponíveis como resultados de métodos, independentemente de um laço de comandos específico.

Principais conclusões

  • A interface lê e exibe; a lógica armazena dados, aplica regras e retorna resultados.
  • main constrói os objetos compartilhados e inicia a interface.
  • Passe um scanner e o objeto de lógica ao construtor da interface.
  • Regras de dados pertencem à lógica; mensagens de entrada e formatação de exibição pertencem à interface.
  • Retornar resultados facilita verificações diretas e a reutilização da interface.

5.2 Adicione um recurso e verifique seu comportamento

Um programa separado pode crescer com uma pequena alteração por vez. Dê à lógica um método útil, verifique-o diretamente e depois conecte um comando que reúna suas entradas e exiba seu resultado.

Adicione uma consulta à lógica

Um TemperatureLog que armazena um ArrayList<Double> readings inicializado pode contar leituras acima de um limite:

int countAbove(double limit) {
    int count = 0;
    for (double reading : readings) {
        if (reading > limit) {
            count++;
        }
    }
    return count;
}

A comparação estrita exclui leituras exatamente iguais ao limite. Para 18,5, 21,0, 19,5 e 23,0 °C, countAbove(20.0) retorna 2. Coloque a comparação no registro para que todos os chamadores recebam a mesma regra.

O comando da interface lê um limite, chama log.countAbove(limit) e exibe a contagem. Se cada comando ficar longo, divida start() em métodos auxiliares privados como addReading() e showStatistics(). Os métodos auxiliares organizam a interação; eles não retiram as regras de dados do registro.

Verifique sem digitar comandos

Crie o objeto de lógica e chame seus métodos com valores cujas respostas você conhece:

TemperatureLog log = new TemperatureLog();
log.add(18.5);
log.add(21.0);
log.add(19.5);
log.add(23.0);
System.out.println(log.count() == 4);
System.out.println(log.average() == 20.5);
System.out.println(log.max() == 23.0);
System.out.println(log.countAbove(20.0) == 2);

A soma é 82,0, então a média esperada é 20,5. Esses valores específicos têm representações binárias exatas, tornando essas verificações diretas de igualdade adequadas para o exemplo. Verifique também um registro vazio de acordo com seu comportamento documentado, um limite igual a uma leitura e a rejeição de uma entrada inválida sem alterar a contagem. Ferramentas de teste automatizado se baseiam na mesma ideia de uma entrada conhecida e um resultado esperado.

Altere o armazenamento apenas quando ele ainda atender às consultas

Uma interface que chama apenas average, count e max não precisa saber se o registro armazena uma lista. Campos de soma acumulada, contagem e máximo podem responder a essas três consultas sem manter todas as leituras.

Uma consulta arbitrária countAbove(limit) precisa de mais informações. As amostras 18, 22, 23 e 20, 20, 23 têm a mesma soma 63, contagem 3 e máximo 23, mas suas contagens acima de 20 são 2 e 1. Manter apenas essas três medidas agregadas perde a distinção. Os métodos públicos descrevem o que deve continuar funcionando após uma alteração de armazenamento.

Combine as ideias de projeto

Um livro de receitas pode ser proprietário de uma lista privada de receitas. Cada receita pode conter ingredientes medidos em gramas e um tempo de preparo em minutos. A igualdade pode ajudar nas verificações de duplicidade, enquanto uma interface adiciona, lista e busca receitas por meio dos métodos do livro. Uma lista de resultados copiada pode proteger sua composição e ainda compartilhar objetos de receita; decida se os chamadores podem alterar essas receitas.

Trabalhe em pequenas etapas: implemente um método, verifique-o e depois adicione seu comando. Se uma alteração prejudicar um resultado existente, volte a um estado funcional e isole essa alteração antes de adicionar outro recurso.

Principais conclusões

  • Adicione e verifique o método de lógica antes de conectar seu comando na interface.
  • Use entradas conhecidas e resultados esperados para verificar a lógica diretamente.
  • Inclua casos de valor-limite, como uma coleção vazia ou um limiar exato.
  • Uma alteração de armazenamento deve preservar informações suficientes para cada consulta pública.

Exercícios resolvidos de Objetos e referências em Java

5 Lições Interativas

1

Acompanhe variáveis sem perder os objetos

15 passos
Acompanhe cópias de valores primitivos, referências compartilhadas, reatribuição e null em um programa curto.
  1. 1
    IntroduçãoDesenhe variáveis e objetos

    Uma variável pode armazenar um número diretamente ou uma referência que aponta para um objeto. Essa diferença explica por que copiar uma variável às vezes fornece dados independentes e às vezes compartilha uma alteração.

    Vamos desenhar variáveis separadamente dos objetos e depois acompanhar cada instrução. Mantenha o número de setas separado do número de objetos: várias setas podem terminar em um único objeto.

  2. 2
    ExplicaçãoUm valor pode ser um dado ou uma referência

    Java tem oito tipos primitivos: byte, short, int, long, float, double, char e boolean. Suas variáveis armazenam dados diretamente. Classes e vetores são tipos de referência; suas variáveis armazenam referências ou null.

    Em int count = 13;, o valor é 13. Em Tally t = new Tally(13);, o valor em t é uma referência a um objeto que armazena seus próprios dados.

  3. 3
    ExemploCopie um valor primitivo

    Acompanhe as atribuições em ordem:

    int start = 13;
    int next = start;
    next += 2;
    

    A segunda instrução copia 13 para next. A terceira lê essa cópia e a substitui por 15. start permanece 13 porque nenhuma instrução lhe atribui um novo valor. Desenhe duas caixas contendo números separados; não há objeto compartilhado envolvido.

  4. 4
    ExemploCopie um valor de referência

    Suponha que Tally armazene um inteiro, addOne() o incremente e value() o leia.

    Tally first = new Tally(13);
    Tally next = first;
    next.addOne();
    

    Um new cria um contador. A atribuição fornece a next outro caminho até ele. Chamar addOne() altera seu valor armazenado para 14, que first.value() agora também lê. A segunda variável não é um segundo contador.

  5. 5
    DesafioSepare um objeto de seu apelido

    Com os mesmos métodos de Tally, o que a.value() retorna?

    Tally a = new Tally(20);
    Tally b = new Tally(a.value());
    b.addOne();
    

    ---reveal---

    Ele retorna 20. O segundo new cria um contador independente inicializado com 20; seu incremento altera apenas esse segundo objeto para 21. Ler um valor de campo e usá-lo em um construtor é diferente de copiar a referência ao objeto original.

  6. 6
    ConexãoAlguns objetos compartilhados não podem ser alterados

    Strings e tipos empacotadores como Integer são tipos de referência. O empacotamento automático converte valores primitivos em objetos empacotadores para usos como ArrayList<Integer>.

    Strings são imutáveis: se left e right compartilham “cedar”, atribuir right = right.toUpperCase() não altera os caracteres originais. left continua sendo “cedar”. O resultado em maiúsculas retornado é atribuído apenas a right.

  7. 7
    AnalogiaUse setas para acompanhar alterações

    Pense em cada variável como um cartão que mostra um caminho até um objeto. Copiar o cartão fornece outro caminho para o mesmo destino. Escrever um caminho diferente em um cartão não reescreve os outros.

    Em um diagrama de memória, desenhe uma seta de cada variável de referência até seu objeto. Um método modificador altera os valores dos campos do objeto; uma atribuição altera a seta de uma variável. Mantenha essas duas alterações visualmente distintas.

  8. 8
    ExemploAltere primeiro o estado compartilhado

    Considere que Crate armazene uma massa em quilogramas, com métodos setMass e mass.

    Crate a = new Crate(3);
    Crate b = a;
    b.setMass(6);
    

    Desenhe a e b apontando para uma caixa. O método modificador atualiza a massa dentro dessa caixa de 3 para 6. Tanto a.mass() quanto b.mass() agora retornam 6; o método modificador não redirecionou nenhuma das variáveis.

  9. 9
    ExemploDepois, redirecione uma variável

    Continue o rastreamento da caixa:

    b = new Crate(9);
    

    Crie um segundo objeto com massa 9 e redirecione apenas b. A variável a ainda aponta para a caixa original, cuja massa permanece 6. Agora há dois objetos de caixa alcançáveis. Isso é uma reatribuição, então nada na massa armazenada da primeira caixa é reiniciado.

  10. 10
    DesafioMantenha o caminho restante

    Continue com Crate c = a; a = null;. Por qual variável a caixa de 6 kg ainda pode ser alcançada e ela está disponível para coleta?

    ---reveal---

    Ela ainda está alcançável por meio de c, então não está disponível para coleta. A atribuição anterior copiou a referência para c; atribuir null a a removeu apenas um caminho. A caixa separada de 9 kg permanece alcançável por meio de b.

  11. 11
    Ideia-chaveUm campo ou uma lista pode preservar um caminho

    Quando o último caminho utilizável até um objeto desaparece, ele se torna inalcançável e fica disponível para coleta de lixo. Java escolhe o momento da coleta; perder uma referência não garante recuperação imediata da memória.

    Um caminho pode passar por outro objeto ou lista. Antes de considerar um objeto inalcançável, verifique essas conexões e também as variáveis locais. Uma entrada de lista pode preservar um objeto depois que sua variável original sair de escopo.

  12. 12
    ExplicaçãoNull significa que o destino está ausente

    null não é um objeto vazio. Uma String nula não tem um objeto no qual chamar length(), enquanto "" é uma cadeia de caracteres utilizável de comprimento zero. Campos de referência têm null como padrão; variáveis locais precisam receber um valor antes de serem lidas.

    Tipos empacotadores também podem ser null. Integer n = null; int count = n; lança NullPointerException quando o desempacotamento tenta obter um valor primitivo do objeto empacotador ausente.

  13. 13
    DicaLeia o erro na chamada que falhou

    Para um erro de null, localize o número da linha e depois a chamada de instância, o acesso a campo ou o desempacotamento nessa linha. Mensagens explicativas, ativadas por padrão a partir do Java 15, podem identificar o campo ausente.

    Acompanhe o código para trás até o construtor, o resultado de busca ou a atribuição que deveria ter fornecido um objeto. Ver “null” no texto exibido é uma pista: exibir uma cadeia nula pode funcionar mesmo que uma chamada de instância posterior falhe.

  14. 14
    DesafioProteja o rastreamento combinado

    Uma busca atribui Crate found = findCrate();, retornando uma caixa ou null. Qual condição verifica com segurança se a massa é maior que 5 kg? Por que seus testes devem estar nessa ordem?

    ---reveal---

    Use found != null && found.mass() > 5. O teste de null é executado primeiro, e && ignora mass() quando não existe uma caixa. Colocar a chamada de método primeiro desreferenciaria null antes de chegar à verificação. Uma referência retornada segue as mesmas regras dos apelidos de referência acompanhados anteriormente.

  15. 15
    RecapitulaçãoVerifique um rastreamento de referências

    Para um novo rastreamento, marque cada new, copie o valor em cada atribuição e atualize apenas o campo afetado por uma mutação. Depois, acompanhe todos os caminhos restantes antes de decidir que um objeto está inalcançável.

    Em uma linha que falha, identifique a operação exata que precisa de um objeto. Verifique null antes dessa operação ou inicialize o campo com o objeto de que o programa realmente precisa, como uma lista vazia.

2

Acompanhe um objeto na entrada e na saída de um método

12 passos
Acompanhe a mutação do objeto de um parâmetro e a reatribuição do parâmetro, depois escolha entre valores de retorno compartilhados e independentes.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoAcompanhe uma chamada e seu resultado
  2. 2
    ExplicaçãoUm parâmetro recebe seu próprio valor
  3. 3
    ExemploAltere o objeto compartilhado
  4. 4
    ExemploReatribua o caminho local
  5. 5
    DesafioUm método pode trocar as variáveis de quem o chamou?
  6. 6
    ConexãoCompare o objeto receptor com seu argumento
  7. 7
    ExplicaçãoO tipo de retorno não promete uma cópia
  8. 8
    ExemploConstrua um controle independente para retornar
  9. 9
    DesafioEncontre uma entrada que possa ser atualizada
  10. 10
    DicaUm método de acesso pode revelar um apelido oculto
  11. 11
    DesafioCombine um retorno com uma chamada que recebe um parâmetro
  12. 12
    RecapitulaçãoExamine as duas fronteiras de um método
3

Construa e proteja uma coleção de objetos

16 passos
Conecte campos de objetos e listas, busque entradas armazenadas e distinga cópias do contêiner de cópias dos elementos.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoSepare as camadas de estado
  2. 2
    ExplicaçãoModele uma relação de posse
  3. 3
    ExemploDelegue a regra de datas
  4. 4
    DesafioProteja a data recebida
  5. 5
    ExplicaçãoUma entrada é outro caminho
  6. 6
    ExemploCalcule um total por meio de um laço
  7. 7
    ExemploMantenha o objeto selecionado
  8. 8
    DesafioRetorne a ausência no momento certo
  9. 9
    DicaSepare a remoção do percurso
  10. 10
    ExplicaçãoDê ao proprietário uma lista inicializada
  11. 11
    ExemploOfereça operações úteis
  12. 12
    DesafioDistinga uma cópia da lista de seus elementos
  13. 13
    ConexãoProteja as duas direções
  14. 14
    ExemploFiltre para um novo resultado
  15. 15
    DesafioAcompanhe uma cópia por meio de outro objeto
  16. 16
    RecapitulaçãoRevise as fronteiras da coleção
4

Construa uma igualdade confiável para coleções

16 passos
Escolha entre comparação de identidade e de conteúdo, implemente equals e hashCode em conjunto e analise chaves estáveis.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoDecida o que significa uma correspondência
  2. 2
    ExemploTexto igual em objetos separados
  3. 3
    DicaNão deixe o cache escolher a regra
  4. 4
    DesafioDê igualdade de conteúdo a uma classe
  5. 5
    ExplicaçãoEscolha os campos e a assinatura
  6. 6
    ExemploAceite a identidade e rejeite tipos incorretos
  7. 7
    ExemploCompare os campos selecionados
  8. 8
    DesafioDetecte uma violação do contrato
  9. 9
    Ideia-chaveVerifique mais de um par correspondente
  10. 10
    ExplicaçãoCódigos de dispersão restringem os candidatos
  11. 11
    ExemploGere o hash correspondente
  12. 12
    DesafioDistinga colisão de igualdade
  13. 13
    ExemploObserve o efeito de uma chave alterada
  14. 14
    ConexãoUm record simples fornece os dois métodos
  15. 15
    DesafioAcompanhe o conjunto após a igualdade gerada
  16. 16
    RecapitulaçãoRevise uma classe de valor
5

Adicione um recurso sem misturar entrada e regras

11 passos
Conecte uma interface de console à lógica reutilizável, verifique uma nova consulta diretamente e preserve seus requisitos de armazenamento.

Salve esta aula para seguir cada Lição Interativa passo a passo.

  1. 1
    IntroduçãoConecte regras a uma conversa
  2. 2
    ExplicaçãoNomeie as duas tarefas
  3. 3
    ExemploConecte um conjunto de objetos compartilhados
  4. 4
    ExemploRetorne o resultado para exibição
  5. 5
    DesafioColoque uma rejeição na classe adequada
  6. 6
    ExplicaçãoDê à nova consulta um método de lógica
  7. 7
    ExemploVerifique com entradas conhecidas
  8. 8
    DesafioAvalie a proposta de armazenamento
  9. 9
    DicaAmplie o programa uma etapa verificada por vez
  10. 10
    DesafioPasse para outra interface
  11. 11
    RecapitulaçãoRevise um novo recurso em sua fronteira

Questões de Objetos e referências em Java

40 Questões de Prática em 4 conjuntos

Conjunto 1: Acompanhe valores, chamadas e comparações simples10 Questões de Prática

Questão 1 de 10

label pode ser null. Qual condição verifica com segurança se ele tem pelo menos três caracteres?
  1. label != null && label.length() >= 3
  2. label.length() >= 3 && label != null
  3. label != null & label.length() >= 3
  4. label.equals(null) && label.length() >= 3
Mostrar respostaOcultar resposta
Resposta: A
A verificação de null é executada primeiro, e && ignora a chamada de length quando essa verificação é falsa. Colocar a chamada de length primeiro já desreferencia o objeto ausente. Um único & também avalia os dois lados, portanto não fornece a proteção de curto-circuito necessária.

Questão 2 de 10

O que original armazena após este código?

int original = 8;
int duplicate = original;
duplicate = 11;
  1. 11
  2. 8
  3. 19
  4. 0
Mostrar respostaOcultar resposta
Resposta: B
A primeira atribuição armazena 8 em cada variável primitiva. A atribuição posterior altera apenas duplicate, então original ainda armazena 8. Tratar as variáveis como apelidos de referência resultaria em 11, mas uma atribuição de int copia o próprio número e não cria um objeto compartilhado.

Questão 3 de 10

Lamp tem um campo booleano inicialmente falso; turnOn() define-o como verdadeiro e isOn() o lê. O que a.isOn() e b.isOn() retornam, nessa ordem?

Lamp a = new Lamp();
Lamp b = a;
b.turnOn();
b = new Lamp();
  1. false, false
  2. true, true
  3. false, true
  4. true, false
Mostrar respostaOcultar resposta
Resposta: D
A chamada acende a lâmpada compartilhada por a e b. A reatribuição de b faz com que ele aponte para uma nova lâmpada, cujo campo começa como falso, enquanto a mantém a lâmpada acesa. Prever dois resultados verdadeiros deixa de considerar o novo objeto criado pela instrução final.

Questão 4 de 10

Um método recebe um Robot r não nulo. Ele executa apenas r = new Robot();. O que acontece com a variável de quem chamou o método que foi passada como argumento?
  1. Ela passa a se referir ao novo robô.
  2. Ela se torna nula quando o método retorna.
  3. Ela continua se referindo ao robô original.
  4. Ela se refere a uma cópia do robô original.
Mostrar respostaOcultar resposta
Resposta: C
O parâmetro recebe uma cópia da referência de quem chamou o método. Atribuir uma nova referência a esse parâmetro altera apenas a variável local do método. Substituir a referência de quem chamou exigiria uma atribuição no código do chamador; esse método também não altera o robô original.

Questão 5 de 10

Gauge.copy() retorna new Gauge(value). Um medidor começa em 6, e raise() soma 1. Quais valores first e second informam?

Gauge first = new Gauge(6);
Gauge second = first.copy();
second.raise();
  1. 7 and 7
  2. 6 and 7
  3. 6 and 6
  4. 7 and 6
Mostrar respostaOcultar resposta
Resposta: B
O método de cópia cria um segundo medidor inicializado com o valor do primeiro. Incrementar esse novo objeto altera seu valor para 7 e deixa o primeiro em 6. Dois valores 7 resultariam do compartilhamento da referência original, mas o método constrói explicitamente um objeto separado.

Questão 6 de 10

Um construtor de Device armazena diretamente seu argumento Settings mutável. Seu método de acesso retorna uma cópia das configurações armazenadas. Dois dispositivos recebem o mesmo objeto de configurações, e o chamador altera esse objeto original depois. Qual alteração de projeto torna cada dispositivo independente dessa alteração externa e do outro dispositivo?
  1. Armazenar as configurações diretamente; retornar o objeto armazenado no método de acesso.
  2. Manter cada construtor; copiar as configurações apenas no método de acesso.
  3. Copiar as configurações em cada construtor; manter o método de acesso que copia.
  4. Criar uma cópia compartilhada das configurações; fornecê-la aos dois construtores.
Mostrar respostaOcultar resposta
Resposta: C
Os dois dispositivos atualmente compartilham o objeto recebido, então o chamador original pode alterar seu estado sem usar um método de acesso. Cada construtor deve copiar o estado recebido para dar a cada dispositivo seu próprio objeto. Um método de acesso que copia protege o acesso externo, mas não pode eliminar um apelido de referência mantido durante a construção.

Questão 7 de 10

Um Sensor s não nulo é adicionado a duas listas. Uma lista remove sua entrada de s. O que acontece com o sensor na outra lista?
  1. Ele continua sendo o mesmo objeto sensor.
  2. Ele é removido da outra lista.
  3. Ele é substituído por um sensor copiado.
  4. Ele se torna um elemento nulo da lista.
Mostrar respostaOcultar resposta
Resposta: A
Remover uma entrada altera a composição dessa única lista. A outra lista ainda armazena sua própria referência ao sensor, então mantém o mesmo objeto. A remoção não destrói o sensor nem procura referências a ele em todos os outros contêineres.

Questão 8 de 10

O que estas duas expressões retornam?

String word = new String("maple");
word == "maple"
word.equals("maple")
  1. true, true
  2. false, false
  3. true, false
  4. false, true
Mostrar respostaOcultar resposta
Resposta: D
A construção explícita cria um objeto de cadeia de caracteres diferente do literal, então o teste de identidade é falso. String.equals compara os caracteres e retorna verdadeiro. Textos iguais não tornam idênticas strings criadas separadamente, embora literais iguais possam compartilhar um objeto.

Questão 9 de 10

Qual declaração sobrescreve o método de igualdade herdado de Object?
  1. public boolean equals(Note other)
  2. public int equals(Object other)
  3. public boolean equals(Object other)
  4. public boolean equal(Object other)
Mostrar respostaOcultar resposta
Resposta: C
O método herdado tem o nome equals, o tipo de retorno boolean e o tipo de parâmetro Object. Corresponder a essa declaração o sobrescreve; adicionar @Override permite que o compilador verifique a correspondência. Um parâmetro do tipo Note cria uma sobrecarga e mantém o método herdado.

Questão 10 de 10

Um aplicativo de console acompanha durações de leitura. A mesma lógica atenderá posteriormente a uma página web. Onde devem ficar o cálculo do total de segundos e a exibição de “Total”?
  1. Cálculo na lógica; exibição na interface de console.
  2. Cálculo e exibição na interface de console.
  3. Cálculo e exibição na classe de lógica.
  4. Exibição na lógica; cálculo na interface de console.
Mostrar respostaOcultar resposta
Resposta: A
O total é um cálculo de dados necessário às duas interfaces, então a lógica deve retorná-lo. A interface de console escolhe seu rótulo e exibe o resultado. Colocar o cálculo na interface de console obrigaria a futura interface web a duplicá-lo ou depender de código específico do console.
Conjunto 2: Aplique compartilhamento, coleções e fronteiras do programa10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

Meter armazena um inteiro e tem set(int) e get(). O que m.get() retorna após a chamada?

static void adjust(Meter x) {
    x.set(14);
    x = new Meter(22);
    x.set(25);
}
Meter m = new Meter(9);
adjust(m);
  1. 9
  2. 22
  3. 25
  4. 14
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

O que acontece com a após a última linha?

String a = "river";
String b = a;
b = b.toUpperCase();
  1. Ela passa a se referir ao texto “RIVER”.
  2. Ela se torna nula após a atribuição.
  3. Ela continua se referindo ao texto “river”.
  4. Ela passa a se referir a uma cadeia vazia.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

O que acontece quando este código é executado?

Integer reading = null;
int value = reading;
  1. O desempacotamento lança NullPointerException.
  2. value recebe o valor primitivo 0.
  3. reading torna-se um objeto empacotador para 0.
  4. A compilação falha porque tipos empacotadores não podem armazenar null.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Bin.items() retorna diretamente sua lista privada de objetos Part mutáveis. Um chamador deve poder inspecionar e modificar a lista e as peças retornadas sem alterar a composição do recipiente nem o estado das peças armazenadas. O que o resultado deve conter?
  1. Uma nova lista com as referências às peças armazenadas.
  2. Uma nova lista com cópias independentes das peças.
  3. A lista armazenada após copiar cada peça nela.
  4. A lista armazenada com índices numéricos imutáveis.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Uma busca por nome verifica uma lista em ordem. Com os nomes “Iris”, “Noel” e “Sana”, ela retorna null quando busca “Sana”. Atualmente, ela tem return null em um else após o teste de nome dentro do laço. Onde esse retorno deve ficar?
  1. Antes de o laço começar a verificar as entradas.
  2. Depois que o laço terminar sem encontrar uma correspondência.
  3. Imediatamente após o primeiro teste de nome bem-sucedido.
  4. Dentro do laço, antes de cada novo nome ser lido.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

copy é criada com new ArrayList<>(original). Ambas as listas contêm inicialmente o mesmo Clip c mutável. O chamador altera c por meio de copy.get(0) e depois esvazia copy. O que permanece em original?
  1. Uma entrada para o clipe alterado.
  2. Uma entrada para o clipe inalterado.
  3. Nenhuma entrada; ambas as listas são esvaziadas.
  4. Uma entrada nula onde estava o clipe.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Badge tem campos e um construtor, mas não sobrescreve equals. Qual é o resultado?

new Badge("Q7").equals(new Badge("Q7"))
  1. true, porque os valores dos campos coincidem.
  2. true, porque construtores definem a igualdade.
  3. false, porque strings não podem ser campos de igualdade.
  4. false, porque estes são objetos separados.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Tag.equals compara apenas code. Tag.hashCode usa code e o campo mutável color. Duas etiquetas têm o mesmo código, mas cores diferentes. Uma pessoa desenvolvedora propõe retornar um código de dispersão constante para corrigir o comportamento. Como as duas regras devem ser avaliadas?
  1. A regra atual pode violar o contrato; uma constante é permitida, mas ineficiente.
  2. A regra atual é válida; uma constante viola o hash de objetos distintos.
  3. Ambas violam o contrato porque códigos de dispersão devem ser únicos.
  4. Ambas são igualmente eficientes porque a igualdade confirma correspondências.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Um programa de acompanhamento de durações rejeita durações negativas. Onde devem ser implementadas a rejeição e a mensagem que pede ao usuário para tentar novamente?
  1. Rejeição na interface; mensagem na lógica.
  2. Rejeição e mensagem apenas na interface.
  3. Rejeição na lógica; mensagem na interface.
  4. Rejeição e mensagem apenas na lógica.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Um registro oferece atualmente count(), average() e max(). A pessoa desenvolvedora adiciona countAbove(limit) para qualquer limite fornecido pelo chamador e depois substitui todas as leituras apenas por soma, contagem e máximo. Por que a nova consulta não pode mais ser implementada corretamente para todos os registros?
  1. A média fica incorreta sempre que as leituras incluem um valor de limiar.
  2. Listas de leituras diferentes podem ter as mesmas medidas agregadas, mas contagens diferentes acima do limiar.
  3. Um máximo só pode ser calculado mantendo todos os objetos de leitura originais.
  4. Alterar um campo privado sempre exige alterar todos os comandos da interface também.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 3: Depure apelidos de referência, igualdade e projeto de coleções10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

countAbove(limit) conta leituras estritamente maiores que seu argumento. Um registro contém 11,5, 15,0, 15,0 e 18,5 °C. O que uma verificação direta deve esperar de countAbove(15.0)?
  1. 0
  2. 1
  3. 2
  4. 3
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Existem apenas as referências mostradas, e todas as variáveis continuam em uso. Cell tem dados de valor mutáveis. Quantos objetos de célula permanecem alcançáveis?

Cell a = new Cell(3);
Cell b = new Cell(8);
Cell c = a;
a = b;
b = null;
c = a;
  1. Dois objetos de célula.
  2. Três objetos de célula.
  3. Nenhum objeto de célula.
  4. Um objeto de célula.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Uma classe declara private ArrayList<Note> notes;. Seu construtor nunca atribui um valor a notes, e add chama imediatamente notes.add(note). Qual alteração deixa uma instância recém-construída pronta para armazenar notas?
  1. Declarar uma lista local dentro de add e deixar o campo inalterado.
  2. Atribuir null a notes explicitamente no construtor.
  3. Inicializar notes com new ArrayList<>().
  4. Tornar notes público para que o chamador acesse o campo.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Box tem um campo inteiro lido por get() e alterado por set(int). Quais valores left.get() e right.get() informam após esta chamada?

static void revise(Box x, Box y) {
    x.set(10);
    x = y;
    x.set(16);
}
Box left = new Box(4);
Box right = new Box(7);
revise(left, right);
  1. 10 and 16
  2. 16 and 16
  3. 4 and 16
  4. 10 and 7
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Um Member armazena um SimpleDate joined não nulo e precisa decidir se entrou antes de outro membro. Onde a comparação de ano, mês e dia deve ser implementada?
  1. Em cada comando da interface que compara membros.
  2. Em SimpleDate.before, chamado pelo método do membro.
  3. Em Member.toString, lido como texto pelo chamador.
  4. Em cada chamador após expor publicamente todos os campos da data.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Um construtor de Queue armazena diretamente um ArrayList<Job> recebido, mas seu método de acesso retorna uma nova lista superficial. O chamador esvazia a lista de entrada original. A fila deve manter sua composição original, enquanto os objetos de tarefa podem continuar compartilhados. Qual correção atende a esse requisito?
  1. Copiar a lista recebida também no construtor.
  2. Manter o construtor e copiar cada tarefa no método de acesso.
  3. Retornar a lista interna para manter as duas listas sincronizadas.
  4. Alterar o método de acesso para retornar uma nova lista vazia a cada vez.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Token declara public boolean equals(Token other) sem @Override. Comparações diretas parecem corretas, mas um ArrayList<Token> não encontra um novo token com campos correspondentes. Qual é o problema de assinatura?
  1. O método sobrescreve equals, mas listas comparam apenas códigos de dispersão.
  2. O método não pode ler campos privados em outro objeto token.
  3. O método sobrecarrega equals e deixa equals(Object) inalterado.
  4. O método deve retornar um inteiro para que buscas em coleções funcionem.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Key.equals e hashCode usam serial. Uma chave mutável é adicionada a um HashSet, depois seu número de série muda e uma consulta falha. Qual sequência permite corretamente uma alteração planejada do número de série preservando a presença da chave no conjunto?
  1. Alterar o número de série, remover com o novo número e adicionar novamente.
  2. Alterar o número de série, chamar contains e deixar a entrada intacta.
  3. Adicionar a chave uma segunda vez, alterar o número de série e removê-la uma vez.
  4. Remover com o número de série antigo, alterá-lo e adicionar novamente.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Um método da interface precisa do número de tarefas armazenadas. O que o método de lógica normalmente deve fazer?
  1. Exibir a contagem no console.
  2. Retornar a contagem como um inteiro.
  3. Ler um comando, depois exibir um rótulo.
  4. Abrir um scanner para solicitar uma contagem.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Mark.equals compara code. Uma lista armazena marcas com códigos “A”, “B”, “A”; as duas marcas A são objetos separados. Uma segunda lista contém apenas uma referência à primeira marca A. O que permanece após marks.removeAll(second)?
  1. Apenas a marca com código “B”.
  2. A marca B e a segunda marca A.
  3. As duas marcas com código “A”.
  4. Nenhuma marca permanece na lista.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 4: Analise cópias, contratos e alterações de projeto10 Questões de Prática

Salve esta aula para ver todas as respostas e praticar cada conjunto.

Questão 1 de 10

record Stamp(String code, int batch) {} usa igualdade e hash gerados. Um conjunto recebe new Stamp("R", 4), um new Stamp("R", 4) separado e depois new Stamp("R", 5). Quantas entradas ele contém?
  1. 1
  2. 3
  3. 2
  4. 0
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Uma lista precisa de entradas de números inteiros que podem estar ausentes. Qual tipo de elemento permite armazenar um número ou null?
  1. int
  2. Integer
  3. double
  4. boolean
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Node contém dados mutáveis. Existem apenas as referências mostradas. Na última linha, quantos objetos de nó estão alcançáveis por meio de first ou da lista?

Node first = new Node();
ArrayList<Node> nodes = new ArrayList<>();
nodes.add(first);
first = new Node();
nodes.add(first);
nodes.remove(0);
  1. Um objeto de nó.
  2. Dois objetos de nó.
  3. Três objetos de nó.
  4. Nenhum objeto de nó.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Locker.find(id) retorna atualmente um Item mutável recém-construído com os mesmos valores de campo do item armazenado correspondente. Os chamadores usam o resultado para atualizar o rótulo do item armazenado. Qual comportamento o método deve oferecer para que essas atualizações afetem o armário?
  1. Retornar uma cópia superficial da lista do armário.
  2. Retornar um novo item com o rótulo atualizado.
  3. Retornar a referência ao item armazenado correspondente.
  4. Retornar outra cópia após cada alteração de rótulo.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Dois objetos Schedule armazenam um campo SimpleDate mutável e compartilhado. Uma pessoa desenvolvedora torna apenas os campos das agendas final. O que acontece se o objeto de data original for alterado por meio de seu método modificador?
  1. Apenas a agenda cujo método de acesso foi chamado a vê.
  2. Nenhuma das agendas observa a data alterada.
  3. Cada agenda mantém sua própria cópia da data antiga.
  4. Ambas as agendas continuam observando a data alterada.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

Uma biblioteca mantém uma lista privada de objetos Document mutáveis. matching(prefix) constrói uma nova lista contendo documentos existentes correspondentes. Um chamador remove um resultado e edita o título de outro. Que efeito isso pode ter na biblioteca?
  1. A composição muda; todos os títulos armazenados permanecem iguais.
  2. A composição da biblioteca e o título armazenado mudam.
  3. Nem a composição da biblioteca nem os títulos armazenados mudam.
  4. A composição permanece igual; um título armazenado muda.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Para referências a objetos não nulas x e y, o que x == y estabelece quando é verdadeiro?
  1. Seus resultados de toString() definem a igualdade.
  2. Elas se referem ao mesmo objeto.
  3. Suas classes sobrescrevem equals em conjunto.
  4. Seus campos nunca foram alterados.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

Um método Score.equals(Object) aceita outra pontuação quando a diferença absoluta entre seus valores inteiros é no máximo 2. As pontuações x, y e z armazenam 10, 12 e 14. Nenhum valor muda. Qual problema do contrato de igualdade aparece nesses três objetos?
  1. A transitividade falha: os dois primeiros pares correspondem, mas os extremos não.
  2. A simetria falha: inverter o mesmo par altera o resultado da comparação.
  3. A reflexividade falha: uma pontuação não pode ser igual à sua própria referência.
  4. A consistência falha: repetir um par produz um resultado diferente a cada vez.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Um aplicativo de tarefas precisa de um novo comando para retornar a maior duração armazenada. Qual ordem de implementação permite a verificação direta antes de adicionar a interação no console?
  1. Ler e exibir durações na interface e depois copiar esse código para a lógica.
  2. Adicionar uma consulta à lógica, verificar durações conhecidas e conectar o comando da interface.
  3. Adicionar mensagens de console à consulta de lógica e chamá-la de todos os comandos.
  4. Tornar pública a lista privada e deixar a interface pesquisá-la diretamente.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Uma classe de valor usa Double.compare(level, other.level) == 0 em equals. Um método de hash proposto sempre retorna 1. Outro retorna códigos diferentes para objetos distintos cujos níveis são ambos NaN. Como essas propostas se comparam ao contrato de igualdade?
  1. A constante é proibida; códigos diferentes para objetos com NaN são obrigatórios.
  2. Ambas são permitidas porque objetos diferentes podem ter quaisquer códigos de dispersão.
  3. A constante é permitida; códigos diferentes para valores NaN iguais são proibidos.
  4. Ambas são proibidas porque cada valor double precisa de um código único.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Travou em uma questão sua? Ajuda com Tarefas

Termos-Chave

61 Termos-Chave, cada um com sua definição

A

Alcançabilidade
Alcançabilidade significa que o programa em execução pode seguir uma cadeia utilizável de referências até um objeto, possivelmente por meio de campos ou listas.
Uma lista preserva a alcançabilidade de um pacote mesmo depois que a variável local que originalmente o armazenava se torna null.
Apelido de referência
Um apelido de referência é outra variável ou referência que alcança o mesmo objeto, fornecendo vários caminhos para um estado compartilhado.
Após Box second = first;, second é um apelido de referência e seu método modificador altera a caixa alcançada por first.
Argumento
Um argumento é um valor fornecido no local de uma chamada para inicializar um parâmetro correspondente no método ou construtor chamado.
Em gate.allowIn(ana), o argumento é o valor de referência lido de ana, que o parâmetro copia.
Avaliação de curto-circuito
A avaliação de curto-circuito ignora o lado direito de && quando seu lado esquerdo é falso, permitindo que uma verificação anterior proteja uma chamada posterior.
A avaliação de curto-circuito faz name != null && name.length() > 0 ignorar a chamada de método quando o valor é null.

C

Cache de objetos empacotadores
Um cache de objetos empacotadores reutiliza esses objetos para valores selecionados, fazendo a identidade de referência depender da reutilização em vez da igualdade numérica.
O cache de objetos Integer inclui valores pequenos, então use equals em vez de depender de == entre objetos empacotadores.
Campo de referência final
Um campo de referência final não pode receber uma referência diferente após a inicialização, embora o objeto referenciado ainda possa ser mutável.
Um campo de referência final que armazena uma lista impede a substituição da lista, mas não impede chamadas a add.
Campo de tipo de referência
Um campo de tipo de referência é um campo que conecta seu proprietário a outro objeto ou armazena null quando nenhum objeto está presente.
O campo de tipo de referência birthday em Person pode apontar para um SimpleDate que realiza comparações de datas.
Campos que definem a igualdade
Os campos que definem a igualdade são os dados selecionados para definir quando duas instâncias representam o mesmo valor para a finalidade de um programa.
Título e ano podem ser campos que definem a igualdade de livros, então alterar apenas quem tomou o livro emprestado deixa essa igualdade inalterada.
Chave de HashMap
Uma chave de HashMap identifica um valor associado por meio de hash e igualdade; seu estado relacionado à igualdade deve permanecer estável durante o armazenamento.
Uma chave de HashMap construída a partir de título e ano pode recuperar um rótulo de prateleira usando um objeto de livro separado e igual.
Classe ArrayList
Um ArrayList é um objeto de lista redimensionável com entradas ordenadas; entradas de objetos são referências, então adicionar uma delas não copia seus campos.
Um ArrayList<Parcel> pode armazenar três referências a pacotes e permitir que um laço some seus pesos em quilogramas.
Classe empacotadora
Uma classe empacotadora representa um valor primitivo como objeto, permitindo usar valores como inteiros onde tipos de referência são necessários.
A classe empacotadora Integer fornece o tipo de elemento para ArrayList<Integer>; o tipo primitivo int não pode fazê-lo.
Classe HashSet
Um HashSet armazena elementos distintos usando seus códigos de dispersão e sua igualdade, então adicionar um valor igual não adiciona outra entrada.
Um HashSet<Book> mantém uma entrada para dois livros iguais e separados quando sua igualdade e seus hashes concordam.
Código de dispersão
Um código de dispersão é um inteiro usado para restringir candidatos em uma coleção baseada em hash; objetos iguais devem produzir códigos iguais.
Um código de dispersão correspondente para livros pode ser calculado com Objects.hash(title, year) quando esses campos definem a igualdade.
Coleta de lixo
A coleta de lixo é a recuperação automática, por Java, da memória de objetos inalcançáveis, em um momento escolhido por seu ambiente de execução.
Um contador fica disponível para coleta de lixo depois que o programa em execução perde seu último caminho até ele.
Colisão de hash
Uma colisão de hash ocorre quando objetos desiguais produzem o mesmo código de dispersão, o que é permitido porque a igualdade ainda confirma uma correspondência.
Uma colisão de hash não une valores distintos em um conjunto quando equals os distingue corretamente.
Composição
Composição modela uma relação de posse ao fornecer a um objeto campos que se referem a outros objetos, como a data de nascimento de uma pessoa.
A composição permite que um curso armazene seu professor como um objeto de pessoa e obtenha o nome por meio de um método.
Composição da lista
A composição da lista descreve quais entradas uma lista armazena; alterar sua composição é diferente de alterar os campos de um objeto armazenado.
Remover uma música altera a composição de uma lista, enquanto outra playlist ainda pode se referir à mesma música.
Consistência
A consistência exige que verificações repetidas de igualdade concordem enquanto as informações comparadas permanecem inalteradas, mantendo os resultados confiáveis.
A consistência permite que um resultado mude depois que um campo de igualdade muda, mas não enquanto os dados comparados estão fixos.
Consulta à coleção
Uma consulta à coleção seleciona ou calcula informações a partir de elementos armazenados, frequentemente retornando um valor, um objeto ou uma nova lista de resultados.
A consulta à coleção songsLongerThan(200) constrói uma nova lista mantendo as referências às músicas correspondentes.
Contrato público
Um contrato público é o comportamento prometido em que os chamadores confiam ao usar os métodos públicos de uma classe, independentemente de seu armazenamento privado.
Uma alteração de armazenamento respeita o contrato público somente se todas as consultas de temperatura oferecidas ainda funcionarem.
Cópia defensiva
Uma cópia defensiva é um objeto separado criado para impedir que código externo altere um estado protegido por meio de uma referência mutável compartilhada.
Um método de acesso à data de nascimento retorna uma cópia defensiva para que um método modificador da data chamado pelo chamador deixe intacta a data de nascimento da pessoa.
Cópia superficial
Uma cópia superficial cria um contêiner externo separado, mas copia as referências de seus elementos, deixando os objetos contidos compartilhados.
Uma cópia superficial de uma lista de músicas pode perder uma entrada de forma independente e ainda compartilhar cada música mutável.

D

Delegação
Delegação significa pedir a outro objeto que execute uma tarefa por meio de seus métodos, mantendo uma regra com o objeto que contém seus dados.
Uma pessoa usa delegação quando olderThan chama birthday.before(other.birthday) para comparar datas.
Desempacotamento
O desempacotamento converte o valor armazenado em um objeto empacotador para seu tipo primitivo; ele falha com NullPointerException para um objeto empacotador nulo.
O desempacotamento ocorre em int count = boxed;, então boxed deve se referir a um objeto Integer real.

E

Empacotamento automático
O empacotamento automático é a conversão automática, em Java, de um valor primitivo para a referência ao objeto empacotador correspondente durante uma atribuição ou chamada.
O empacotamento automático permite que numbers.add(12) passe um valor Integer quando numbers é um ArrayList<Integer>.
Encapsulamento
O encapsulamento mantém o estado por trás dos métodos de uma classe para que ela possa controlar alterações e impor regras a seus dados armazenados.
O encapsulamento de uma playlist é enfraquecido se um método de acesso retorna a lista privada e os chamadores podem esvaziá-la diretamente.
Exceção ConcurrentModificationException
Uma ConcurrentModificationException pode sinalizar que uma coleção foi alterada estruturalmente durante um percurso baseado em iterador.
A remoção direta de uma lista durante um laço for-each pode causar ConcurrentModificationException antes do término do percurso.
Exceção NullPointerException
Uma NullPointerException é uma falha em tempo de execução causada por operações que precisam de um objeto ausente, como uma chamada de instância ou desempacotamento por meio de null.
A chamada name.length() lança NullPointerException quando a variável name armazena null.

I

Identidade
Identidade significa ser o mesmo objeto, o que é testado por == para duas referências; valores de campo correspondentes, por si só, não a estabelecem.
Dois livros construídos separadamente podem ter títulos e anos iguais e ainda ter identidades diferentes.
Igualdade de conteúdo
Igualdade de conteúdo significa que os campos escolhidos pela regra de igualdade de uma classe coincidem, mesmo quando os objetos têm identidades diferentes.
Um catálogo de livros pode definir igualdade de conteúdo usando título e ano e ignorando quem está com o livro emprestado.
Imutabilidade
Imutabilidade significa que o estado de um objeto não pode mudar após sua construção, então compartilhá-lo não permite mutações compartilhadas.
A imutabilidade de String significa que toUpperCase() não pode alterar os caracteres da cadeia original.
Injeção pelo construtor
A injeção pelo construtor fornece os colaboradores de um objeto como argumentos do construtor, permitindo que ele use objetos existentes compartilhados.
A injeção pelo construtor fornece a uma interface um scanner e um registro em vez de criar um registro separado para cada comando.
Interface do usuário
Uma interface do usuário é o código do programa que interage com o usuário, como um laço de comandos que lê texto e exibe resultados.
Uma interface do usuário lê uma temperatura, chama o método add de um registro de temperaturas e exibe qualquer mensagem de rejeição.
Internalização de strings
A internalização de strings compartilha objetos de cadeia de caracteres para literais correspondentes, permitindo que duas expressões literais idênticas tenham a mesma referência.
A internalização de strings pode fazer "oak" == "oak" ser verdadeiro, ocultando um erro que aparece quando um texto igual vem da entrada.

L

Laço for-each
Um laço for-each visita os elementos de uma coleção em sequência, atribuindo o valor de cada elemento a uma variável de laço para processamento.
Um laço for-each sobre pacotes pode somar cada resultado de getWeight() sem alterar a composição da lista.
Lógica do programa
A lógica do programa armazena dados e aplica as regras da aplicação, expondo métodos que podem funcionar independentemente da interação com o console.
A lógica do programa calcula a média de temperatura como um número que uma interface de console ou web pode exibir.

M

Medida agregada
Uma medida agregada é uma estatística combinada, como soma, contagem ou máximo, que resume valores e perde parte das informações sobre eles.
As mesmas medidas agregadas podem descrever listas com contagens diferentes acima de um limiar, então essa consulta precisa de mais dados.
Método de cópia
Um método de cópia retorna um novo objeto inicializado a partir dos dados de um objeto existente, fornecendo a independência prometida por seu projeto.
O método de cópia de um contador usa new Counter(value), então incrementar a cópia deixa o original inalterado.
Método fábrica
Um método fábrica fornece um objeto a seu chamador, criando uma nova instância ou reutilizando uma que já existe.
O método fábrica issue() de uma máquina de tíquetes constrói um tíquete com seu próximo número e o retorna.
Mutação
Uma mutação altera o estado de um objeto existente, tornando os valores alterados de seus campos visíveis por todas as referências a esse objeto.
Chamar parcel.setWeight(3.5) é uma mutação que uma lista contendo esse pacote também pode observar.

O

Objeto mutável
Um objeto mutável permite alterações em seu estado após a construção, então apelidos de referência podem observar alterações feitas por outra referência.
Uma data com métodos modificadores é um objeto mutável; compartilhá-la entre duas pessoas pode alterar as duas datas de nascimento armazenadas.
Objeto proprietário
Um objeto proprietário gerencia o estado por meio de seus próprios métodos, como uma playlist que controla adições à sua lista privada de músicas.
A playlist é o objeto proprietário responsável por impor uma contagem máxima em seu método add.
Objeto receptor
O objeto receptor é o objeto no qual um método de instância é chamado; dentro desse método, this se refere a ele.
Em earlier.before(later), o objeto receptor é earlier; a outra data é fornecida como argumento.

P

Parâmetro
Um parâmetro é uma variável declarada por um método ou construtor que recebe o valor copiado de um argumento quando uma chamada é executada.
Em allowIn(Person visitor), o parâmetro visitor recebe a referência à pessoa fornecida pelo chamador.
Passagem por valor
A passagem por valor fornece a um parâmetro de método sua própria cópia do valor de um argumento, incluindo uma referência copiada para um argumento que é um objeto.
A passagem por valor permite que um método redirecione seu parâmetro de pessoa sem redirecionar a variável do chamador.

R

Reatribuição
Uma reatribuição armazena um novo valor em uma variável existente; para uma variável de referência, ela altera qual objeto a variável alcança.
A reatribuição current = new Counter(); redireciona current, enquanto outros apelidos mantêm o contador antigo.
Referência
Uma referência é um valor que aponta para um objeto; copiá-la fornece a outra variável um caminho para esse mesmo objeto.
A referência atribuída por Counter second = first; permite que ambas as variáveis alcancem o contador original.
Referência nula
Uma referência nula é o valor null, que não aponta para nenhum objeto e pode ser armazenado por uma variável de referência, mas não por uma variável de tipo primitivo.
Uma referência nula em String name não pode receber length(), enquanto uma cadeia vazia pode retornar comprimento 0.
Reflexividade
A reflexividade exige que todo objeto não nulo seja comparado como igual a si mesmo, então um método de igualdade válido retorna verdadeiro para x.equals(x).
Uma verificação inicial de this == other em equals garante a reflexividade antes de comparar quaisquer valores de campo.
Registro (record)
Um record é uma classe de dados Java com campos de componentes final e métodos gerados de acesso, igualdade, hash e representação textual.
O record Book(String title, int year) compara ambos os componentes e fornece os métodos de acesso title() e year().
Resultado da busca
Um resultado da busca é o valor correspondente retornado por uma consulta; uma busca de objeto pode retornar a referência armazenada ou null quando não há correspondência.
O resultado da busca por um nome pode ser uma pessoa armazenada em um cadastro, permitindo atualizações por meio da referência retornada.

S

Simetria
A simetria exige que inverter dois objetos não nulos em uma comparação de igualdade preserve o resultado verdadeiro ou falso.
A simetria falha se um livro aceita sua cadeia de caracteres de título como igual, mas a cadeia rejeita o objeto de livro.
Sobrecarga
Uma sobrecarga é outro método com o mesmo nome, mas com tipos de parâmetros diferentes; ela não substitui a declaração original.
Escrever equals(Book other) cria uma sobrecarga e mantém equals(Object) disponível para as coleções.
Sobrescrita
Uma sobrescrita substitui o comportamento de um método de instância herdado por uma declaração correspondente, como equals(Object) em uma nova classe.
A anotação @Override permite que o compilador verifique se o método de igualdade de um livro é uma sobrescrita válida.

T

Tipo de referência
Um tipo de referência é um tipo de classe, vetor ou interface cujas variáveis podem armazenar referências a objetos ou o valor null.
String é um tipo de referência, então uma variável pode se referir a um objeto de texto ou não conter uma referência a objeto.
Tipo primitivo
Um tipo primitivo armazena dados diretamente, como um número ou valor booleano; Java tem oito tipos primitivos.
O tipo primitivo int permite que count armazene 12 diretamente, então atribuí-lo a outra variável copia 12.
Transitividade
A transitividade exige que, quando um objeto é igual a um segundo e o segundo é igual a um terceiro, o primeiro também seja igual ao terceiro.
A transitividade faz com que três livros com os mesmos campos de igualdade pertençam ao mesmo grupo de valores iguais.

V

Validação de dados
A validação de dados verifica se um valor fornecido atende às regras da aplicação antes de ser aceito no estado armazenado.
A validação de dados em um registro de durações pode rejeitar um valor negativo independentemente da interface que o chama.
Valor de retorno
Um valor de retorno é o valor que um método devolve a seu chamador; para um resultado que é um objeto, ele é uma referência ou null.
O valor de retorno do findByName de um cadastro pode apontar para uma pessoa armazenada que o chamador e o cadastro passam a compartilhar.
Valor padrão do campo
Um valor padrão do campo é o valor inicial que Java fornece a um campo, como null para referências ou false para booleanos.
O valor padrão de um campo ArrayList<Song> não inicializado é null, então ainda não existe uma lista.
Verificação direta
Uma verificação direta chama métodos de lógica com entradas conhecidas e compara seus resultados com respostas esperadas sem passar por uma interface do usuário.
Uma verificação direta pode adicionar quatro leituras e verificar a média sem ler nenhum texto de comando.

Sobre esta aula

Por StudeliRelatar um erro

Crie seus próprios Materiais de Estudo

Transforme suas anotações, slides e documentos em Guias de Estudo, Questões de Prática, Flashcards e mais 6 ferramentas.

Começar