Herança, interfaces e polimorfismo em Java

Aprenda como as classes Java se baseiam umas nas outras. Estenda uma classe com extends e super, sobrescreva métodos herdados e forneça às suas classes métodos toString, equals e hashCode úteis de Object. Defina comportamentos compartilhados com classes abstratas e interfaces, preveja qual método será executado por meio de polimorfismo e vinculação dinâmica e modele opções fixas e dados simples com enums e records. Exemplos de código comentados mostram cada etapa e os erros mais comuns de iniciantes.

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

O que você vai aprender

  1. Construa uma nova classe a partir de uma existente com extends, identifique o que a nova classe herda e garanta que a parte herdada de cada objeto seja inicializada corretamente com super.
    • Crie uma subclasse com extends
      Declare uma subclasse, identifique quais membros ela herda e pode usar e decida, com o teste “é um”, se a herança é adequada.
    • Inicialize objetos com construtores da superclasse
      Passe valores ao construtor da superclasse com super(...), preveja a ordem de execução dos construtores e evite os erros clássicos com construtores.
  2. Altere o comportamento herdado sobrescrevendo métodos, amplie a versão da superclasse com super e forneça às suas classes métodos toString, equals e hashCode significativos de Object.
    • Sobrescreva métodos herdados
      Substitua ou amplie um método herdado com uma sobrescrita correta, chame a versão da superclasse com super e distinga sobrescrita de sobrecarga.
    • Use e sobrescreva os métodos de Object
      Explique por que todo objeto tem toString, equals, hashCode e getClass, interprete seu comportamento padrão e escreva um toString útil.
    • Sobrescreva equals e hashCode corretamente
      Defina quando dois objetos são iguais com um equals(Object) correto, mantenha hashCode consistente com ele e trate a igualdade em uma hierarquia de classes.
  3. Use classes abstratas para compartilhar código entre classes estreitamente relacionadas, use interfaces para estabelecer um compromisso de comportamento entre classes sem relação entre si e escolha a ferramenta adequada para um projeto.
    • Escreva classes abstratas e métodos abstratos
      Declare classes e métodos abstratos, complete-os em subclasses concretas e use uma classe abstrata para compartilhar campos e código.
    • Defina e implemente interfaces
      Escreva uma interface como contrato, implemente-a em uma ou mais classes e combine interfaces com herança.
    • Adicione métodos padrão, estáticos e privados a interfaces
      Forneça comportamento compartilhado a interfaces com métodos padrão, acrescente métodos auxiliares com métodos estáticos e privados e resolva conflitos entre métodos padrão.
    • Escolha entre uma classe abstrata e uma interface
      Compare classes abstratas e interfaces e escolha a opção adequada, ou ambas em conjunto, para um projeto.
  4. Referencie objetos por tipos gerais, preveja qual método será executado em tempo de execução, converta tipos com segurança e escreva código que continue funcionando quando novas subclasses forem acrescentadas.
    • Referencie objetos por tipos de superclasse e de interface
      Distinga o tipo declarado de uma variável do tipo real do objeto e use tipos de superclasse e de interface para variáveis, parâmetros e listas.
    • Preveja qual método será executado com vinculação dinâmica
      Acompanhe chamadas de métodos em uma hierarquia de classes, distinguindo o que o compilador decide do que é decidido em tempo de execução.
    • Converta entre tipos com conversões explícitas e instanceof
      Faça conversões para subtipo com segurança usando instanceof e correspondência de padrões, reconheça ClassCastException e identifique quando um teste de tipo sinaliza a falta de um método sobrescrito.
    • Projete código flexível com polimorfismo
      Escreva métodos e coleções usando tipos gerais para que novas classes possam ser usadas sem alterações e torne classes ordenáveis com Comparable.
  5. Represente um conjunto fixo de opções com um enum e um dado simples com um record, usando os membros que o compilador gera para você.
    • Represente conjuntos fixos de valores com enums
      Declare enums, use seus métodos integrados e switch e forneça campos, construtores e comportamento às constantes de enumeração.
    • Modele dados com records
      Declare records para dados simples, use o construtor gerado, os métodos de acesso, equals, hashCode e toString e valide dados em um construtor compacto.

Guia de estudo de Herança, interfaces e polimorfismo em Java

5 capítulos

1

Estenda classes com herança

Uma subclasse começa com os campos e o comportamento de uma classe existente e acrescenta o que a diferencia. Essa relação afeta tanto a forma como outros códigos podem usar o objeto quanto a forma como Java o constrói. Acompanhe uma pessoa e um estudante em suas declarações e construtores: os dados privados permanecem sob o controle da superclasse, e as chamadas de construtores fornecem os valores necessários ao estado herdado.

1.1 Crie uma subclasse e proteja seus dados

A herança cria uma subclasse a partir de uma superclasse existente. Em class Student extends Person, todo estudante é uma pessoa. Um objeto estudante reúne seu estado de pessoa e seu estado adicional de estudante em um único objeto.

Acrescente as partes específicas do estudante

Aqui estão uma pequena superclasse e uma subclasse:

class Person {
    private String name;
    public Person(String name) { this.name = name; }
    public String getName() { return name; }
}

class Student extends Person {
    private int credits;
    public Student(String name) { super(name); }
    public void study() { credits += 5; }
    public int getCredits() { return credits; }
}

Student herda getName(). Ela acrescenta um campo de créditos e dois métodos. Após Student s = new Student("Ada Lovelace");, o campo de créditos começa em 0. Duas chamadas a s.study() elevam seu valor primeiro para 5 e depois para 10. s.getName() continua retornando “Ada Lovelace”: o método herdado lê o campo privado declarado em Person.

A instrução super(name) passa o nome ao construtor de Person. Construtores não são herdados, portanto Student declara seu próprio construtor. Uma subclasse pode acrescentar métodos e sobrescrever comportamento herdado, mas não pode remover um método público herdado de sua API pública.

Identifique quais membros são acessíveis
Modificador de acessoOnde o membro pode ser acessado
publicQualquer classe com acesso ao tipo que declara o membro
protectedO mesmo pacote, além do acesso por subclasses em outros pacotes
Sem modificadorO mesmo pacote
privateA classe que declara o membro

Membros privados não são herdados. O campo privado name ainda existe dentro de cada estudante, mas Student não pode lê-lo diretamente. Manter o campo privado e fornecer um método de acesso permite que Person controle seus próprios dados. Entre pacotes, o acesso a membros de instância protected tem uma restrição adicional: o código da subclasse acessa o membro por meio de si mesmo ou de uma referência do tipo da própria subclasse, não por meio de uma referência arbitrária da superclasse.

Teste a relação antes de estender

Java permite uma superclasse direta por classe. Uma cadeia como GraduateStudent extends Student e Student extends Person é válida; extends Student, Teacher não é. Uma classe sem superclasse explícita estende Object. A própria Object não tem superclasse.

Use o teste “é um”: todo objeto da subclasse pode ser usado onde se espera a superclasse? Um caminhão é um veículo. Um cliente tem um endereço, mas um cliente não é um endereço. Para essa segunda relação, use composição:

class Customer {
    private Address address;
}

Aqui Address é outra classe. O campo permite que um cliente use um endereço sem afirmar que é um. Estender uma classe apenas para aproveitar um método conveniente também implica herdar todo o seu contrato público. Por fim, uma classe final não pode ser estendida. Como String é final, class ShortString extends String não compila.

Principais conclusões

  • Um objeto de subclasse inclui o estado da superclasse e qualquer estado acrescentado pela subclasse.
  • Os campos privados da superclasse são acessados por meio dos métodos da superclasse.
  • Uma classe estende uma superclasse direta; construtores não são herdados.
  • Use herança para uma relação “é um” e composição para uma relação “tem um”.
  • Uma classe final não pode ser estendida.

1.2 Passe valores por uma cadeia de construtores

Um construtor de subclasse deve organizar a construção da parte da superclasse do mesmo objeto. Ele pode chamar um construtor da superclasse com super(...) ou delegar a outro construtor da própria classe com this(...).

Escolha o construtor da superclasse
class Person {
    private final String name;
    Person(String name) { this.name = name; }
}
class Student extends Person {
    private final String studentId;
    Student(String name, String studentId) {
        super(name);
        this.studentId = studentId;
    }
    Student(String name) {
        this(name, "no id yet");
    }
}

O construtor com dois argumentos passa o nome a Person e armazena o identificador. O construtor com um argumento delega a ele. Um construtor contém no máximo uma invocação explícita de construtor: this(...) ou super(...). Seguir a cadeia de this(...) leva, por fim, a um construtor que invoca super(...).

Se nenhuma das duas for escrita, Java insere super(). Aqui isso falha porque Person tem apenas um construtor que exige um nome. Declarar qualquer construtor impede que Java forneça um construtor padrão sem argumentos. Uma classe sem construtor declarado recebe um construtor padrão que chama super().

Escrever new Person(name) dentro de Student faz outra coisa: constrói um objeto separado. Isso não inicializa o estado de pessoa do estudante atual nem substitui a chamada obrigatória ao construtor da superclasse.

Acompanhe a inicialização em ordem
class Base {
    Base() { System.out.println("Base ready"); }
}
class Middle extends Base {
    Middle() { System.out.println("Middle ready"); }
}
class Top extends Middle {
    Top() { super(); System.out.println("Top ready"); }
}

new Top() entra na cadeia de construtores até Object. À medida que as chamadas retornam, a saída é “Base ready”, “Middle ready” e depois “Top ready”. Os inicializadores de campo de cada classe são executados após o retorno do construtor de sua superclasse e antes do restante do corpo do construtor dessa classe.

Um método que pode ser sobrescrito, quando chamado por um construtor da superclasse, já pode executar uma sobrescrita da subclasse. Se essa sobrescrita ler um campo de referência da subclasse cuja atribuição ainda não foi executada, ela encontrará null, mesmo que o campo tenha um inicializador na declaração. Limite o trabalho do construtor à inicialização do estado; evite chamadas a métodos que as subclasses possam sobrescrever.

Reconheça a mudança no Java 25

No Java 25, um construtor pode validar parâmetros antes de sua invocação explícita de construtor:

Student(String name, String studentId, int credits) {
    if (credits < 0) {
        throw new IllegalArgumentException("negative credits");
    }
    super(name);
    this.studentId = studentId;
}

O Java 25 também permite atribuições simples antecipadas a campos declarados na classe atual sem inicializadores na declaração. Ele ainda proíbe ler os campos do objeto atual, chamar seus métodos de instância ou passar this antes da invocação. Para níveis padrão da linguagem anteriores ao Java 25, mantenha super(...) ou this(...) em primeiro lugar. O Java 22–24 ofereceu formas anteriores como recursos de prévia.

Principais conclusões

  • super(...) constrói a parte da superclasse do objeto atual.
  • Um super() implícito precisa de um construtor sem argumentos acessível na superclasse.
  • this(...) delega dentro da mesma classe; sua cadeia deve chegar a um construtor da superclasse.
  • Inicializadores de campo são executados após o retorno do construtor da superclasse.
  • O Java 25 permite trabalho restrito antes de uma invocação explícita de construtor.
  • Uma sobrescrita chamada durante a construção pode observar campos da subclasse antes de suas atribuições.
2

Sobrescreva métodos e use a classe Object

Métodos herdados dão a objetos relacionados um ponto de partida comum. A sobrescrita permite que cada subclasse responda à mesma chamada à sua maneira, enquanto os métodos herdados de Object controlam como um objeto é exibido e comparado a outros. Pequenas diferenças de assinatura são importantes: um método pode parecer correto a quem o lê, mas não substituir o método que o código da biblioteca realmente chama. A igualdade também tem compromissos dos quais as coleções baseadas em hash dependem.

2.1 Substitua comportamento herdado com uma sobrescrita

Uma sobrescrita fornece a uma subclasse sua própria implementação de um método de instância herdado. Seu nome e seus tipos de parâmetros correspondem aos do método original, permitindo que quem o chama continue usando a mesma operação.

Amplie um método que já funciona

Suponha que a remuneração anual de um funcionário seja de doze pagamentos mensais. Um gerente também recebe um bônus anual:

class Employee {
    private final double monthlySalary;
    Employee(double monthlySalary) {
        this.monthlySalary = monthlySalary;
    }
    public double yearlyPay() { return 12 * monthlySalary; }
}
class Manager extends Employee {
    private final double bonus;
    Manager(double monthlySalary, double bonus) {
        super(monthlySalary);
        this.bonus = bonus;
    }
    @Override
    public double yearlyPay() {
        return super.yearlyPay() + bonus;
    }
}

Para new Manager(3000.0, 5000.0), a chamada à superclasse retorna 36.000,0. Somar 5000,0 resulta em 41.000,0. super.yearlyPay() seleciona deliberadamente a implementação da superclasse. O método da superclasse continua disponível dentro da subclasse, embora chamadas comuns no objeto gerente executem a sobrescrita.

Escrever return yearlyPay() + bonus; chama novamente o próprio método do gerente. Nenhuma chamada termina, portanto a execução acaba falhando com StackOverflowError. A ausência de super. muda o significado da chamada.

Deixe o compilador verificar a assinatura

Coloque @Override acima de cada método que sobrescreve outro. Um erro de digitação como yearlypay() ou um parâmetro adicional causa então um erro de compilação. Sem a anotação, Java pode aceitar um novo método que quem chama o método nunca usa.

Uma sobrescrita válida segue estas regras:

  • Seus tipos de parâmetros correspondem exatamente.
  • Seu tipo de retorno é o mesmo ou um subtipo quando retorna um tipo de objeto. Esse estreitamento permitido é um tipo de retorno covariante.
  • Seu acesso é pelo menos tão aberto quanto o do método herdado.
  • Ela não acrescenta exceções verificadas que a declaração herdada proíbe.

Por exemplo, public String toString() não pode ser substituído por String toString() com acesso de pacote. Quem já chama o método deve manter o acesso.

Distinga sobrescrita, sobrecarga e ocultação
DeclaraçãoO que ela faz
Mesma assinatura de método de instância em uma subclasseSobrescreve comportamento herdado
Mesmo nome, lista de parâmetros diferenteAcrescenta uma sobrecarga
Mesma assinatura de método estático em uma subclasseOculta um método estático

role() e role(String language) são sobrecargas; o segundo não substitui o primeiro. A escolha da sobrecarga usa os tipos declarados dos argumentos em tempo de compilação. Métodos estáticos também são selecionados pelo tipo usado em tempo de compilação, portanto não são sobrescritos dinamicamente.

Um método privado não é herdado; um método de mesmo nome na subclasse é independente. Um método final não pode ser sobrescrito, e construtores não podem ser sobrescritos. Além de compilar, o método ainda deve cumprir seu propósito original. Uma sobrescrita de gerente que retorne a remuneração mensal prejudicaria o código que totaliza a remuneração anual, mesmo que sua assinatura estivesse correta.

Principais conclusões

  • Uma sobrescrita corresponde ao nome e aos tipos de parâmetros de um método de instância herdado.
  • @Override detecta sobrecargas acidentais e erros de digitação.
  • super.method() reutiliza a implementação da superclasse.
  • Uma sobrescrita não pode reduzir o acesso nem ampliar as exceções verificadas permitidas.
  • A sobrecarga altera os parâmetros; métodos estáticos são ocultados, e métodos final não podem ser sobrescritos.

2.2 Dê aos objetos texto útil e identifique sua classe

Toda classe Java estende Object direta ou indiretamente. Vetores também são objetos. Essa raiz compartilhada fornece métodos como toString(), equals(Object), hashCode() e getClass(), mesmo quando uma classe não declara nenhum deles.

Substitua a descrição exibida por padrão

O Object.toString() herdado produz um nome de classe, @ e o código de dispersão do objeto em hexadecimal. Um texto como “school.Person@1b6d3586” identifica a classe, mas não explica os dados da pessoa. O número pode mudar entre execuções e não é um endereço de memória garantido por Java.

Uma sobrescrita útil mostra os campos de que os leitores precisam. Aqui um estudante herdou getName() e armazena um campo credits:

@Override
public String toString() {
    return getName() + " (" + credits + " credits)";
}

Para uma estudante chamada Ada Lovelace com 10 créditos, System.out.println(s) exibe “Ada Lovelace (10 credits)”. A chamada de impressão obtém automaticamente o texto do objeto. A concatenação de cadeias de caracteres também faz isso: "Student: " + s inclui a mesma descrição.

Para uma referência nula, a impressão e a concatenação produzem “null”. Uma chamada explícita s.toString() quando s é nulo lança NullPointerException, pois não há objeto para receber essa chamada.

Se Person.toString() já retorna o nome da pessoa, uma subclasse pode usá-lo como base:

@Override
public String toString() {
    return super.toString() + ", student id " + studentId;
}

Para o identificador “S-17”, isso produz “Ada Lovelace, student id S-17”. A utilidade de super.toString() depende do que a superclasse realmente retorna. Se ela ainda usa a versão de Object, contribui com o texto padrão de classe e código de dispersão.

Mantenha separadas a identidade e as informações de classe

Object.equals(Object) compara a identidade: retorna verdadeiro quando ambas as referências se referem ao mesmo objeto. Dois objetos separados new Person("Ada") não são iguais segundo essa implementação herdada, apesar de terem nomes iguais. Classes como String, Integer e ArrayList fornecem sobrescritas baseadas em valor.

getClass() informa a classe real em tempo de execução:

Person p = new Student("Ada Lovelace");
System.out.println(p.getClass().getSimpleName());

Isso exibe “Student” quando Student tem o construtor mostrado. O tipo declarado da variável não altera a classe do objeto. getClass() é final e não pode ser sobrescrito.

hashCode() dá suporte a coleções baseadas em hash. Outros métodos de Object incluem o método protegido clone() e os métodos de coordenação de threads wait(), notify() e notifyAll().

Verifique a grafia exata

Java distingue toString() de tostring(). Um método toString(String prefix) é uma sobrecarga, portanto a impressão não o chama. Ambos os erros são detectados por @Override. Uma representação textual também não define igualdade: dois objetos podem exibir o mesmo texto e ainda assim ser considerados diferentes.

Principais conclusões

  • Object fornece métodos comuns a todos os objetos Java, incluindo vetores.
  • A impressão e a concatenação de cadeias de caracteres usam toString() e representam uma referência nula como “null”.
  • Object.equals compara identidade até que uma classe o sobrescreva.
  • getClass() retorna a classe real e é final.

2.3 Mantenha a igualdade e o cálculo do código de dispersão consistentes

Dois objetos podem representar o mesmo valor sem ser o mesmo objeto. Um catálogo pode considerar dois objetos de livro iguais quando seus códigos de catálogo e suas edições correspondem. Defina essa regra sobrescrevendo equals(Object) e mantenha o cálculo do código de dispersão consistente com ela.

Compare os campos que definem o valor
import java.util.Objects;

final class Book {
    private final String code;
    private final int edition;
    Book(String code, int edition) {
        this.code = code;
        this.edition = edition;
    }
    @Override
    public boolean equals(Object other) {
        if (this == other) return true;
        if (!(other instanceof Book b)) return false;
        return edition == b.edition && Objects.equals(code, b.code);
    }
    @Override
    public int hashCode() {
        return Objects.hash(code, edition);
    }
}

A verificação de identidade aceita rapidamente o mesmo objeto. A verificação de tipo rejeita um objeto sem relação com a classe ou uma referência nula e então apresenta b como um Book. Edições primitivas usam ==; Objects.equals compara códigos com segurança mesmo quando um código é nulo. Dois livros com código “B-1042” e edição 2 são considerados iguais. A edição 3 representa um valor diferente.

O parâmetro deve ser Object. Escrever equals(Book other) acrescenta uma sobrecarga; isso mantém o equals(Object) herdado. Código da biblioteca que chama essa assinatura continuaria comparando a identidade. @Override faz a declaração incorreta falhar em tempo de compilação.

Siga os compromissos da igualdade

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

  • Reflexiva: um objeto é igual a si mesmo.
  • Simétrica: inverter os dois objetos produz a mesma resposta.
  • Transitiva: se um objeto é igual a um segundo e o segundo é igual a um terceiro, o primeiro é igual ao terceiro.
  • Consistente: comparações repetidas concordam enquanto os dados comparados permanecem inalterados.

Um objeto também deve ser considerado diferente de null. Objetos iguais devem retornar códigos de dispersão iguais. Objetos diferentes podem compartilhar um código de dispersão: uma colisão de hash não estabelece igualdade. Derive o código de dispersão dos mesmos campos usados na igualdade, como Objects.hash(code, edition) faz aqui.

Uma coleção baseada em hash usa o código de dispersão para localizar candidatos antes de verificar a igualdade. Sobrescrever apenas equals pode fazer um conjunto manter livros iguais separados ou fazer um mapa não encontrar uma chave igual. Alterar um campo usado no cálculo do código de dispersão enquanto seu objeto está armazenado como chave pode deixar esse objeto armazenado sob o código antigo.

Proteja a simetria entre subclasses

Suponha que uma Book extensível aceite qualquer instanceof Book, enquanto Ebook acrescente o tamanho do arquivo à igualdade e aceite apenas ebooks. Um livro pode aceitar um ebook com o mesmo código e a mesma edição, mas o ebook rejeita o livro. A comparação perdeu a simetria.

Uma abordagem exige classes correspondentes em tempo de execução, verificando se a referência é nula antes de getClass(). Outra torna a igualdade da superclasse final para que as subclasses não possam ampliar a regra de igualdade. Uma classe de valor final, como acima, evita completamente a igualdade entre subclasses. Records geram igualdade e códigos de dispersão a partir de seus componentes; constantes de enumeração já têm igualdade baseada em identidade, adequada a seus valores fixos.

Principais conclusões

  • Sobrescreva equals(Object), não uma sobrecarga com a classe como parâmetro.
  • A igualdade deve ser reflexiva, simétrica, transitiva e consistente; a comparação com null é falsa.
  • Objetos iguais precisam de códigos de dispersão iguais; um código compartilhado não estabelece igualdade.
  • Mantenha estáveis os campos de igualdade enquanto um objeto for chave de uma coleção baseada em hash ou elemento de um conjunto.
  • Uma superclasse e uma subclasse não devem usar regras de igualdade que discordem quando os operandos são invertidos.
  • Records e enums fornecem comportamento de igualdade integrado adequado.
3

Defina contratos com classes abstratas e interfaces

Algumas classes relacionadas compartilham campos e métodos que já funcionam; outras classes compartilham apenas uma capacidade. Classes abstratas e interfaces expressam essas relações diferentes. Um programa de desenho pode manter dados comuns de formas em uma classe abstrata, enquanto um programa de carregamento trabalha com dispositivos sem relação entre si por meio de uma interface. Ambas as abordagens permitem que o compilador verifique o comportamento exigido antes da execução do programa, e Java fornece regras precisas quando implementações herdadas competem.

3.1 Compartilhe estado e exija comportamento das subclasses

Uma classe abstrata é uma classe base que não pode ser instanciada diretamente. Ela pode conter campos comuns e métodos que já funcionam, deixando operações selecionadas para as subclasses completarem.

Deixe uma operação obrigatória abstrata
abstract class Shape {
    private final String name;
    Shape(String name) { this.name = name; }
    public abstract double area();
    public String describe() {
        return name + " with area " + area();
    }
}
class Rectangle extends Shape {
    private final double width;
    private final double height;
    Rectangle(double width, double height) {
        super("rectangle");
        this.width = width;
        this.height = height;
    }
    @Override
    public double area() { return width * height; }
}

area() tem uma assinatura e um ponto e vírgula, sem corpo. Ele estabelece o que toda forma concreta deve fornecer. Como Shape contém um método abstrato, a classe também deve ser abstrata. Rectangle fornece a implementação que falta e pode ser instanciada.

Para new Rectangle(3.0, 4.0), o construtor da superclasse armazena “rectangle”. O construtor da subclasse armazena as dimensões. Sua área é de 12,0 metros quadrados quando as dimensões estão em metros. Chamar describe() retorna “rectangle with area 12.0”. A chamada a area() no método herdado executa a implementação do retângulo.

Use construtores e métodos concretos normalmente

Uma classe abstrata pode ter campos, construtores e métodos com corpo, com os níveis de acesso usuais. Seu construtor é executado como parte da criação de um objeto de subclasse. new Shape() é proibido mesmo que exista um construtor correspondente; new Rectangle(3.0, 4.0) é válido.

Uma segunda subclasse pode usar a mesma descrição herdada enquanto calcula a área de outra forma. Uma implementação de círculo retorna Math.PI * radius * radius. Para um raio de 1,5 m, o cálculo é π vezes 2,25, aproximadamente 7,07 metros quadrados. O método compartilhado describe() não precisa de um ramo específico para círculos.

Uma variável ainda pode ter o tipo abstrato: Shape s = new Rectangle(3.0, 4.0);. Java proíbe construir a classe abstrata, não referenciar um objeto concreto por meio dela.

Faça o trabalho ausente causar um erro de compilação

Toda subclasse concreta deve fornecer ou herdar implementações de todos os métodos abstratos. Uma subclasse que deixa alguns incompletos também deve ser declarada abstrata. Retornar 0 de um area() provisório permitiria que uma subclasse concreta incompleta compilasse e produzisse silenciosamente resultados errados. Declarar area() abstrato torna esse trabalho ausente visível ao compilador.

Métodos abstratos não podem ser privados, estáticos ou final: cada um desses modificadores impediria a sobrescrita da subclasse que fornece a operação. Uma classe abstrata pode não conter métodos abstratos; nesse caso, o modificador apenas impede a construção direta. Use esse tipo de classe base quando suas subclasses passarem no teste “é um” e se beneficiarem de estado ou código compartilhado.

Principais conclusões

  • Uma classe abstrata não pode ser construída diretamente, mas seu construtor é executado para objetos de subclasses.
  • Um método abstrato não tem corpo e exige uma implementação concreta.
  • Uma subclasse concreta deve completar todo método abstrato herdado.
  • Um tipo abstrato pode ser usado para variáveis, parâmetros e listas.
  • Classes abstratas podem combinar campos comuns, métodos que funcionam e operações obrigatórias.

3.2 Dê um contrato compartilhado a classes sem relação entre si

Uma interface nomeia uma capacidade que várias classes podem fornecer. Ela estabelece os métodos disponíveis por meio dessa capacidade sem exigir que as classes compartilhem os mesmos campos de instância ou a mesma superclasse.

Declare e implemente os métodos
interface Chargeable {
    int batteryPercent();
    void charge(int minutes);
}
class Phone implements Chargeable {
    private int battery;
    Phone(int battery) { this.battery = battery; }
    @Override
    public int batteryPercent() { return battery; }
    @Override
    public void charge(int minutes) {
        battery = Math.min(100, battery + 2 * minutes);
    }
}

Os métodos abstratos da interface são implicitamente públicos. Os métodos que os implementam devem ser explicitamente públicos; omitir esse modificador reduziria o acesso e causaria uma falha de compilação. Uma classe concreta deve fornecer ou herdar implementações públicas adequadas de todos os métodos abstratos. Uma classe abstrata que implementa a interface pode deixar alguns para suas subclasses.

Para um telefone com 35 por cento de carga, charge(20) acrescenta 40 pontos percentuais e deixa a carga em 75 por cento. Mais 20 minutos levariam a 115 antes da limitação, portanto Math.min armazena 100. Uma ElectricScooter pode implementar a mesma interface acrescentando apenas um ponto por minuto: começando com 50 por cento, ela chega a 70 após 20 minutos. A interface define a operação disponível, enquanto a classe fornece seu comportamento.

Use a interface como tipo
Chargeable device = new Phone(35);
device.charge(20);
System.out.println(device.batteryPercent());

Isso exibe 75. Quem faz a chamada pode usar os métodos da interface e os métodos públicos disponíveis em todos os objetos. Operações exclusivas de Phone não estão disponíveis por meio de device, a menos que a interface declarada as exponha.

Interfaces também podem ser tipos de parâmetros, de retorno e de elementos de listas. Um método que aceita Chargeable pode trabalhar com um telefone ou uma scooter sem conhecer a organização de seus campos. A biblioteca Java usa a mesma abordagem: List tem implementações como ArrayList e LinkedList; Runnable declara run(); Comparable declara uma comparação natural.

Combine mais de uma capacidade

Uma classe estende uma classe e pode implementar várias interfaces. A ordem é class Phone extends Device implements Chargeable, Connectable. Uma interface pode estender várias interfaces: interface SmartDevice extends Chargeable, Connectable. As classes que a implementam devem então fornecer todas as operações abstratas herdadas.

Uma interface não tem construtores nem campos de instância. new Chargeable() não compila. Um campo declarado como int FULL = 100; é implicitamente uma constante public static final, lida como Chargeable.FULL.

Um método abstrato de interface não fornece código funcional. Sua implementação pode vir da própria classe ou de um método público herdado da superclasse. Métodos padrão são os métodos de interface que podem fornecer comportamento de instância herdado.

Principais conclusões

  • Uma interface é um tipo que descreve uma capacidade compartilhada.
  • Uma classe concreta que implementa uma interface deve fornecer ou herdar implementações públicas adequadas.
  • Uma classe pode implementar várias interfaces enquanto estende uma classe.
  • Interfaces não têm construtores nem campos de instância; seus campos são constantes.
  • O tipo de interface permite que quem faz a chamada use seu contrato com classes sem relação entre si.

3.3 Use comportamento compartilhado de interfaces e resolva conflitos

Interfaces podem fornecer código funcional além de declarações de métodos abstratos. O modificador do método determina se os objetos das classes que implementam a interface o herdam, se quem o chama usa o nome da interface ou se apenas código dentro da interface pode usá-lo.

Construa comportamento padrão a partir do contrato
interface Chargeable {
    int batteryPercent();
    void charge(int minutes);
    default boolean needsCharge() {
        return batteryPercent() < 20;
    }
    static int clamp(int percent) {
        return Math.max(0, Math.min(100, percent));
    }
}

Um método padrão é um método público de instância com corpo. Um telefone que informa 15 por cento herda needsCharge() e retorna verdadeiro. Com 20 por cento, retorna falso. O método padrão chama a operação abstrata batteryPercent(); ele não lê um campo privado do telefone. Uma classe pode sobrescrever o método com sua própria implementação pública.

Um método estático de interface pertence à interface: Chargeable.clamp(130) retorna 100 e Chargeable.clamp(-5) retorna 0. As classes que implementam a interface não o herdam, portanto Phone.clamp(130) não é uma chamada válida a esse método. Métodos padrão e estáticos de interface foram introduzidos no Java 8.

Desde o Java 9, uma interface também pode declarar métodos privados para compartilhar código auxiliar entre seus próprios métodos. Um método auxiliar privado de instância dá suporte a métodos padrão; um método auxiliar privado estático também pode dar suporte a métodos estáticos. As classes que implementam a interface não podem chamar esses métodos auxiliares diretamente.

Resolva conflitos entre métodos padrão

Duas interfaces sem relação entre si podem fornecer a mesma assinatura:

interface Lamp {
    default String describe() { return "lamp"; }
}
interface Speaker {
    default String describe() { return "speaker"; }
}
class SmartLamp implements Lamp, Speaker {
    @Override
    public String describe() {
        return Lamp.super.describe() + " and "
             + Speaker.super.describe();
    }
}

Sem a sobrescrita, SmartLamp tem um conflito e não compila. As chamadas qualificadas InterfaceName.super.method() selecionam os métodos padrão individuais de suas superinterfaces diretas. Essa implementação retorna “lamp and speaker”.

Implementações em conflitoRegra
Método público concreto herdado da superclasse e método padrão de interfaceO método da classe prevalece
Método padrão de uma interface e de outra que a estendeA interface mais específica prevalece
Métodos padrão de interfaces sem relação entre siA classe deve resolver o conflito

Uma declaração abstrata na superclasse também pode exigir que uma subclasse concreta implemente o método; um método padrão de interface não elimina essa obrigação.

Considere as classes que já implementam a interface

Acrescentar um método abstrato exige que cada classe concreta que implementa a interface forneça ou herde uma implementação correspondente. Um método padrão muitas vezes evita essa alteração ao fornecer comportamento funcional, mas pode introduzir um conflito com outro método herdado. Métodos padrão ajudam uma interface a evoluir; as regras de herança ainda determinam se as classes resultantes compilam.

Principais conclusões

  • Um método padrão fornece comportamento público de instância que pode ser sobrescrito.
  • Chame um método estático de interface por meio do nome da interface.
  • Métodos privados de interface são auxiliares visíveis apenas dentro da interface.
  • Um método concreto de classe tem prioridade sobre um método padrão de interface.
  • Métodos padrão em conflito de interfaces sem relação entre si exigem uma sobrescrita; caso contrário, prevalece o método padrão da interface mais específica.
  • Acrescentar um método padrão pode introduzir conflitos entre métodos herdados.

3.4 Escolha uma base compartilhada, uma capacidade ou ambas

Uma classe abstrata e uma interface podem definir métodos obrigatórios. A escolha depende da relação entre os objetos e de a implementação precisar ou não de estado de instância compartilhado.

Compare o que cada uma oferece
PerguntaClasse abstrataInterface
Pode armazenar campos por objeto?SimNão
Pode ter construtores?SimNão
Pode fornecer métodos de instância funcionais?SimSim, com métodos padrão
Quantas uma classe pode usar diretamente?Uma superclasseVárias interfaces implementadas
Quais níveis de acesso estão disponíveis?Todos os níveis usuaisMétodos públicos e auxiliares privados

Campos de interface são constantes public static final, não dados de cada objeto. Métodos auxiliares privados de interface dão suporte aos próprios métodos funcionais da interface; eles não criam estado privado para cada objeto que a implementa.

Coloque estado estreitamente relacionado em uma classe abstrata

Um programa de desenho tem círculos, retângulos e triângulos. Cada forma armazena um nome e uma posição, e cada uma calcula sua área de modo diferente. Uma Shape abstrata pode manter os campos comuns e o construtor, fornecer métodos funcionais de movimento ou descrição e declarar area() como abstrato.

Todas as formas concretas são tipos de Shape, portanto outro código pode pedir uma forma e receber qualquer uma delas. Da mesma forma, uma Enemy abstrata pode manter pontos de vida e um método compartilhado takeDamage(int points), deixando o movimento para classes específicas de inimigos. O estado compartilhado dá a essas classes base uma tarefa útil.

Coloque uma capacidade transversal em uma interface

O mesmo programa de desenho pode exportar formas, rótulos de texto e imagens. Exportar não torna um rótulo de texto uma forma, e esses objetos já podem precisar de superclasses diferentes. Uma interface Exportable pode declarar String exportText(). Cada classe implementa a operação usando seus próprios dados.

Uma classe pode combinar as duas funções: class Circle extends Shape implements Exportable. A superclasse fornece o estado da forma e código compartilhado; a interface permite que o círculo seja usado pelo código de exportação. Uma classe que implementa várias interfaces se compromete a oferecer várias capacidades sem herdar delas campos de instância.

Combine um contrato com uma implementação parcial

Uma classe abstrata pode implementar uma interface e completar apenas alguns métodos. As subclasses concretas completam as operações restantes. List e AbstractList do Java usam essa organização; ArrayList estende AbstractList. Quem faz as chamadas pode depender da interface enquanto quem implementa reutiliza uma classe base quando ela é adequada.

Por exemplo, List<String> names = new ArrayList<>(); permite que quem faz as chamadas acrescente, leia e remova nomes por meio de List. Substituir o inicializador por new LinkedList<>() preserva essas chamadas. Um parâmetro declarado como ArrayList<String> rejeitaria um LinkedList<String>, mesmo que precisasse apenas de operações de lista.

Uma classe abstrata que contém apenas métodos abstratos ainda ocupa a única posição de superclasse da subclasse. Ela não é intercambiável com uma interface. Escolha a interface quando apenas o contrato comum for necessário e acrescente uma classe base quando implementações relacionadas se beneficiarem de estado ou código compartilhado.

Principais conclusões

  • Use uma classe abstrata para tipos relacionados de objetos que compartilham estado ou código.
  • Use uma interface para uma capacidade que classes sem relação entre si possam fornecer.
  • Uma classe pode estender uma base abstrata e implementar interfaces ao mesmo tempo.
  • Uma implementação abstrata parcial pode dar suporte a uma interface sem restringir quem faz as chamadas a essa classe base.
4

Escreva programas flexíveis com polimorfismo

O tipo de uma variável e a classe de um objeto respondem a perguntas diferentes. O tipo declarado da variável controla quais chamadas compilam; a classe real do objeto controla qual sobrescrita é executada. Manter essas decisões separadas permite acompanhar código desconhecido, usar conversões explícitas com segurança e escrever laços que funcionam com novas implementações. A mesma abordagem dá suporte a interfaces da biblioteca, como Comparable, que fornece aos objetos uma ordem natural de classificação.

4.1 Distinga o tipo da referência da classe do objeto

O polimorfismo permite que uma referência geral aponte para objetos de diferentes subtipos. O objeto mantém todo o seu estado e comportamento; a referência oferece a quem faz chamadas uma visão específica das operações disponíveis.

Identifique os dois tipos
Person p = new Student("Ada Lovelace", "S-17");

O tipo declarado, também chamado de tipo estático, é Person: ele aparece na declaração da variável. O tipo real, ou tipo em tempo de execução, é Student: essa é a classe instanciada por new. A classe real do objeto não muda após a construção.

Atribuir uma referência de estudante a uma referência de pessoa é uma conversão para supertipo. Ela é automática e segura porque todo estudante é uma pessoa. O mesmo estudante também pode ser referenciado por uma variável Object. Um objeto que implementa uma interface pode ser convertido para o tipo dessa interface, como em Chargeable c = new Phone(35);.

Nenhum dado do estudante é apagado pela atribuição. Continua existindo um único objeto estudante com seu identificador de estudante, dados de pessoa herdados e métodos sobrescritos. Atribuir a ele uma referência mais geral muda o que o compilador permite acessar por meio dessa referência.

Verifique as chamadas pelo tipo declarado

Se Person declara getName(), então p.getName() compila. Se apenas Student declara getStudentId(), então p.getStudentId() não compila. O compilador verifica a API de Person mesmo quando o inicializador claramente cria um estudante.

A atribuição inversa Student s = p; não é aceita sem uma conversão explícita de tipo. Uma referência de pessoa pode apontar para uma pessoa que não é estudante. Essa restrição protege o código de tratar um objeto como um subtipo ao qual ele não pertence.

Tipo declaradoChamadas disponíveis por meio da referência
ObjectOperações públicas fornecidas por Object
PersonOperações acessíveis de Person, incluindo as herdadas
StudentOperações acessíveis de estudante e operações herdadas
Permita que parâmetros e listas aceitem vários subtipos

Um método static void printCard(Person person) pode receber um objeto Person, Student ou Teacher, supondo que os dois últimos estendam Person. Seu código pode depender do contrato de pessoa. Um tipo de retorno também pode ser geral, por exemplo, um método que retorna um Shape.

Uma lista com um tipo geral de elemento pode armazenar diferentes classes de formas:

List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle(1.5));
shapes.add(new Rectangle(3.0, 4.0));
for (Shape shape : shapes) {
    System.out.println(shape.area());
}

Aqui List e ArrayList vêm de java.util, e cada forma concreta fornece area(). O laço usa apenas essa operação comum.

Uma List<Circle> não é uma List<Shape>. Se fosse, um código que a recebesse como lista de formas poderia acrescentar um retângulo a uma lista que deveria conter apenas círculos. Quando um método exige List<Shape>, crie a lista com esse tipo de elemento desde o início.

Principais conclusões

  • O tipo declarado controla quais operações uma referência pode acessar.
  • O tipo real é a classe do objeto e não muda após a construção.
  • A conversão para supertipo, para uma superclasse ou interface implementada, é automática e segura.
  • Um tipo geral de parâmetro ou de elemento de lista pode aceitar vários subtipos concretos.
  • List<Circle> não pode ser usada como List<Shape>.

4.2 Acompanhe a seleção de sobrecarga e a vinculação dinâmica

Uma chamada de método é resolvida em duas etapas. Em tempo de compilação, Java verifica os tipos declarados e escolhe uma assinatura de método. Em tempo de execução, a vinculação dinâmica escolhe a implementação sobrescrita do método de instância para a classe real do objeto.

Acompanhe uma chamada dentro de um método herdado
class A {
    String who() { return "A"; }
    String hello() { return "Hello from " + who(); }
}
class B extends A {
    @Override String who() { return "B"; }
}
class C extends B { }

Para A x = new C();, x.hello() é permitido porque A declara hello(). A classe C não tem uma sobrescrita de hello(), nem B, portanto Java executa a implementação em A.

Dentro desse método, who() significa this.who(). O objeto receptor continua sendo o objeto C. Java procura a partir de C e encontra a implementação de who() mais próxima em B. O resultado é “Hello from B”. Entrar em um método herdado não transforma o objeto receptor em um objeto da superclasse.

Escolha uma sobrecarga antes de escolher uma sobrescrita
static String kind(Object value) { return "object"; }
static String kind(String value) { return "string"; }

Dentro de uma classe que contém esses métodos, Object value = "hi"; kind(value) seleciona kind(Object) e retorna “object”. O tipo declarado da variável é Object; Java não reconsidera as sobrecargas usando a cadeia de caracteres real em tempo de execução.

Para uma chamada de instância, o despacho em tempo de execução ainda pode escolher uma sobrescrita da assinatura já selecionada. Ele não pode mudar para outra sobrecarga que apenas pareça mais específica para o argumento real. Primeiro identifique a assinatura, depois procure sua sobrescrita.

Reconheça chamadas e membros que não usam esse despacho
Membro ou chamadaO que o seleciona
Método de instância sobrescritoClasse real do objeto receptor, após a seleção da assinatura
Método estáticoTipo usado para a chamada em tempo de compilação
Campo ocultadoTipo declarado da referência
Método privadoCódigo da classe que o declara
super.method()Implementação da superclasse

Se P declara String label = "P"; e Q extends P declara seu próprio String label = "Q";, então P p = new Q(); p.label lê “P”. Campos são ocultados, não sobrescritos. Ambos os campos existem no objeto. Evite reutilizar nomes de campos herdados, pois o tipo da referência pode fazer acessos aparentemente idênticos lerem estados diferentes.

super.method() seleciona a implementação da superclasse, incluindo uma que a superclasse herde. Se o próprio método selecionado fizer uma chamada comum em this, essa chamada interna ainda poderá ser despachada para uma sobrescrita da subclasse.

Principais conclusões

  • O tempo de compilação determina a assinatura que pode ser chamada, incluindo a escolha da sobrecarga.
  • A vinculação dinâmica seleciona a sobrescrita mais próxima dessa assinatura, buscando a partir da classe real para cima.
  • Uma chamada comum dentro de um método herdado ainda usa o objeto receptor real.
  • Campos, métodos estáticos, métodos privados e chamadas explícitas a super não usam o despacho comum de sobrescritas.

4.3 Verifique um subtipo antes de usar seus métodos específicos

Uma referência geral às vezes precisa acessar uma operação que pertence apenas a um subtipo. Uma conversão para subtipo informa ao compilador que deve tratar a referência como esse subtipo. Em tempo de execução, Java verifica se o objeto já tem esse tipo.

Entenda o que uma conversão explícita muda

Suponha que Person p se refira a um estudante. Student s = (Student) p; permite s.getStudentId(). Ambas as variáveis continuam se referindo ao mesmo objeto. A conversão não o copia, não altera sua classe real nem acrescenta campos de estudante a uma pessoa comum.

Se p de fato se referir a new Person("Grace Hopper"), essa conversão lança ClassCastException. O compilador aceita a possível relação de pessoa para estudante, mas o objeto em tempo de execução ainda deve satisfazê-la. Uma conversão explícita entre classes que não podem ter relação, como uma referência Person convertida para a classe final Integer, falha em tempo de compilação.

Isso difere de uma conversão de tipo primitivo: (int) 3.9 cria o valor inteiro 3. Uma conversão explícita de referência apenas verifica e altera o tipo pelo qual uma referência existente é usada.

Combine o teste e a nova variável

Desde o Java 16, a correspondência de padrões torna o caso seguro mais conciso:

static void showStudentId(Person person) {
    if (person instanceof Student student) {
        System.out.println(student.getStudentId());
    }
}

O teste é verdadeiro para um estudante ou um objeto de uma subclasse de estudante. A variável student está disponível dentro do bloco, onde se sabe que o teste teve sucesso. Uma pessoa comum pula o bloco. Uma referência nula também torna o teste falso, portanto o bloco nunca tenta chamar um método em null.

A forma antiga testa person instanceof Student e depois declara Student student = (Student) person; dentro do ramo em que o teste teve sucesso. Ambas as formas protegem a conversão. Testar um objeto sem relação em outro ponto não protege uma conversão de person; o teste e a conversão devem envolver a mesma referência.

Uma conversão para supertipo não precisa de conversão explícita. Person person = student; já compila e não pode falhar por incompatibilidade de tipos. Uma conversão explícita de null para um tipo de referência compatível produz null; usar esse resultado para chamar um método de instância ainda falha com NullPointerException.

Decida se um teste de tipo é necessário

Testes de tipo são adequados para código que recebe um Object, como equals(Object other), ou precisa de uma operação exclusiva de um subtipo, como um identificador de estudante. Eles também podem ajudar ao trabalhar com classes que não podem ser alteradas.

Uma cadeia repetida de testes de tipo para uma operação compartilhada é um problema diferente. Se o código verifica cada classe de forma para calcular sua área, cada forma acrescentada exige outro ramo. Declarar area() em Shape e deixar que as subclasses o implementem permite que quem faz a chamada use shape.area().

Uma conversão explícita não pode tornar uma operação comum ausente disponível em todos os subtipos. Coloque o comportamento compartilhado no contrato comum e reserve um teste de subtipo para o trabalho que realmente exige esse subtipo específico.

Principais conclusões

  • Uma conversão para subtipo verifica um objeto existente; ela não transforma o objeto.
  • Uma conversão possível pode compilar e ainda lançar ClassCastException em tempo de execução.
  • instanceof é falso para null e verdadeiro para instâncias do tipo ou de seus subtipos.
  • Um padrão instanceof introduz uma variável com tipo seguro onde se sabe que o teste é verdadeiro.
  • Operações compartilhadas pertencem ao tipo comum; operações exclusivas de um subtipo podem precisar de um teste de tipo.

4.4 Escreva laços gerais e defina uma ordem natural

Um método continua útil com novas classes quando seu parâmetro expõe exatamente as operações de que ele precisa. Quem faz a chamada fornece os objetos; a vinculação dinâmica fornece seu comportamento individual.

Some áreas sem ramos específicos por classe
static double totalArea(List<Shape> shapes) {
    double total = 0;
    for (Shape shape : shapes) {
        total += shape.area();
    }
    return total;
}

Com um círculo de raio 1,5 m, um retângulo de dimensões 3,0 m e 4,0 m e um triângulo de base 4,0 m e altura 2,5 m, as áreas são aproximadamente 7,06858, 12,0 e 5,0 metros quadrados. Somar a área não arredondada do círculo produz aproximadamente 24,07 metros quadrados. Cada objeto fornece seu próprio area().

Acrescentar uma classe Triangle não exige editar o laço. A nova classe implementa a operação de forma existente. Um resultado negativo violaria o contrato da área mesmo que a sobrescrita compilasse; o código geral depende tanto do significado da operação quanto de sua assinatura.

A mesma abordagem funciona com uma interface. Um método chargeAll(List<Chargeable> devices, int minutes) percorre os dispositivos e chama device.charge(minutes). Após 20 minutos, o telefone que começa com 35 por cento chega a 75 por cento e a scooter que começa com 50 por cento chega a 70 por cento. O laço nunca seleciona uma regra de carregamento por conta própria.

Deixe quem chama escolher uma implementação de lista

Um parâmetro List<Shape> aceita tanto um ArrayList<Shape> quanto um LinkedList<Shape>. Um parâmetro ArrayList<Shape> rejeita a lista encadeada, apesar de precisar apenas de operações de lista. Use o tipo mais geral que forneça os métodos necessários; declarar tudo como Object seria geral demais, pois ele não tem area() nem operações de lista.

Torne objetos ordenáveis com Comparable

Comparable<T> define a ordem natural de um objeto. O resultado de seu compareTo é negativo quando este objeto vem antes do outro, zero para um empate na ordenação e positivo quando vem depois. O sinal do resultado é o que importa; ele não precisa ser exatamente −1 ou 1.

record Runner(String name, int seconds)
        implements Comparable<Runner> {
    @Override
    public int compareTo(Runner other) {
        return Integer.compare(seconds, other.seconds);
    }
}

Uma lista modificável com Mia em 3605 s, Ken em 3490 s e Ana em 3720 s passa a ser Ken, Mia, Ana após Collections.sort(runners). list.sort(null) também usa a ordem natural. Construa uma lista modificável com new ArrayList<>(List.of(...)); tentar ordenar o resultado não modificável de List.of(...) lança UnsupportedOperationException.

Use Integer.compare em vez de subtrair os valores. A subtração pode sofrer estouro para valores grandes com sinais opostos e retornar o sinal errado. A mesma ideia se aplica a Double.compare para campos decimais. Neste exemplo de corredores, tempos iguais empatam na ordenação mesmo que os nomes sejam diferentes, portanto um empate na comparação e a igualdade de objetos não precisam significar a mesma coisa. Prefira uma ordem natural cujo resultado zero concorde com a igualdade; documente uma diferença intencional.

Principais conclusões

  • Código geral chama uma operação compartilhada e permite que cada objeto forneça sua implementação.
  • Um parâmetro de interface pode aceitar diferentes implementações do mesmo contrato.
  • compareTo usa um resultado negativo, zero ou positivo para expressar a ordenação.
  • Integer.compare evita o risco de estouro causado pela subtração de campos inteiros.
  • A ordenação exige uma lista modificável; List.of(...) sozinho produz uma lista não modificável.
  • Um empate na ordenação pode diferir da igualdade por valor quando a comparação usa menos campos.
5

Modele valores fixos e dados com enums e records

Alguns programas precisam de um conjunto fixo de opções, enquanto outros precisam de um pequeno grupo de valores que pertencem uns aos outros. Enums dão às opções um tipo próprio; records fornecem grande parte do código rotineiro para objetos de dados. Ambos se encaixam no sistema de tipos do Java e podem implementar interfaces. Seu comportamento integrado poupa trabalho, mas a escolha entre eles e uma classe comum ainda depende do que pode mudar e do que torna dois valores iguais.

5.1 Represente opções fixas com um enum

Um enum dá a um conjunto fixo de opções nomeadas um tipo próprio. enum Size { SMALL, MEDIUM, LARGE } permite Size size = Size.MEDIUM;. Ele impede que quem chama um método que exige Size passe a cadeia de caracteres escrita incorretamente “MEDUIM” ou o inteiro 7.

Use nomes, constantes e switch

Cada constante de enumeração é um único objeto, portanto size == Size.LARGE é uma comparação adequada. Uma variável de enum também pode ser nula. Size.values() retorna todas as constantes na ordem de declaração; Size.valueOf("LARGE") encontra o nome exato. A busca distingue maiúsculas de minúsculas: “large” causa IllegalArgumentException.

name() retorna o nome declarado. ordinal() retorna a posição a partir de zero, portanto Size.MEDIUM.ordinal() é 1. Inserir uma constante altera as posições posteriores, portanto uma posição ordinal é uma má escolha como código armazenado ou valor de negócio. Por padrão, toString() também retorna o nome.

Uma expressão switch, padrão desde o Java 14, pode retornar um valor:

String label = switch (size) {
    case SMALL -> "S";
    case MEDIUM -> "M";
    case LARGE -> "L";
};

Nomear todas as constantes torna a expressão exaustiva sem um ramo default. Esses casos pressupõem um size não nulo; listar as constantes não trata null.

Dê dados significativos a cada constante
enum CupSize {
    SMALL(250), MEDIUM(350), LARGE(500);
    private final int milliliters;
    CupSize(int milliliters) { this.milliliters = milliliters; }
    public int milliliters() { return milliliters; }
}

O ponto e vírgula separa a lista de constantes dos membros. O construtor é executado uma vez para cada constante quando o enum é inicializado. Ele é implicitamente privado, e quem usa o enum não pode criar novas constantes com new CupSize(400).

Dois copos grandes e um pequeno comportam 1250 mL, ou 1,25 L. Esse cálculo usa o campo milliliters, não as posições das constantes. Percorrer CupSize.values() e exibir cada constante e sua capacidade produz “SMALL: 250 mL”, “MEDIUM: 350 mL” e “LARGE: 500 mL”.

Dê às constantes seu próprio comportamento

Um enum pode implementar uma interface e pode dar às constantes individuais corpos de métodos diferentes:

enum Operation {
    ADD { int apply(int a, int b) { return a + b; } },
    MULTIPLY { int apply(int a, int b) { return a * b; } };
    abstract int apply(int a, int b);
}

Operation.MULTIPLY.apply(6, 7) retorna 42. Cada constante completa a operação abstrata. Enums estendem implicitamente java.lang.Enum; eles não podem estender outra classe, e classes comuns não podem estendê-los.

Use um enum para opções fixas quando o programa é escrito, como direções ou estados de pedidos. A lista de produtos de uma loja, que muda com o tempo, pertence aos dados: acrescentar produtos não deve exigir reescrever e recompilar uma declaração de enum.

Principais conclusões

  • Um enum dá a um conjunto fixo de constantes um tipo distinto.
  • values() preserva a ordem de declaração e valueOf corresponde ao nome exato.
  • Armazene números significativos em campos, pois as posições ordinais podem mudar.
  • Construtores de enum inicializam constantes; quem usa o enum não pode usar new para acrescentar constantes.
  • Enums podem implementar interfaces e dar comportamentos diferentes às constantes.

5.2 Transporte valores com um record e valide-os

Um record é uma classe de dados concisa, padrão desde o Java 16. Seu cabeçalho lista os valores, chamados componentes, que definem cada instância: record Point(int x, int y) { }.

Use os membros gerados

O compilador fornece um campo privado final para cada componente, um construtor canônico com esses componentes em ordem e métodos de acesso públicos com os nomes deles. Ele também fornece equals, hashCode e toString baseados nos componentes.

Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a.x());
System.out.println(a.equals(b));
System.out.println(a == b);

Isso exibe 3, true e false em linhas separadas. Valores de componentes correspondentes tornam os records iguais, mas as duas expressões new criam objetos separados. Records iguais têm códigos de dispersão iguais. Seu texto gerado inclui os nomes do record e dos componentes; JDKs atuais mostram “Point[x=3, y=4]”. Esse formato exato de texto não é garantido, portanto não o analise como formato de dados.

Os métodos de acesso são x() e y(), não getX() e getY(). Um método modificador que atribui this.x = x não compila porque o campo do componente é final. Um método pode retornar um novo valor: Point moveRight(int dx) { return new Point(x + dx, y); }.

Valide em um construtor compacto
record Temperature(double celsius) {
    Temperature {
        if (celsius < -273.15) {
            throw new IllegalArgumentException("below absolute zero");
        }
    }
}

Um construtor compacto repete o nome do record sem uma lista de parâmetros. Ele verifica ou normaliza os parâmetros recebidos; Java atribui valores aos campos dos componentes após seu corpo. Essa verificação rejeita −300 °C e aceita 21,5 °C. Um componente de texto poderia ser normalizado com name = name.trim(); antes da atribuição automática.

Um record público precisa de um construtor compacto público. Records também podem declarar campos estáticos, métodos estáticos e métodos de instância. Um construtor adicional delega ao construtor canônico com this(...). Campos de instância adicionais além dos componentes são proibidos.

Proteja objetos mutáveis dos componentes

Campos de componentes final tornam a imutabilidade superficial. Um record que contém uma lista mutável continua se referindo a essa lista:

record Team(String name, List<String> members) {
    Team {
        members = List.copyOf(members);
    }
}

Com java.util.List importado, isso armazena uma cópia não modificável. Acréscimos posteriores à lista original não afetam a composição da equipe, e team.members().add("Lee") lança UnsupportedOperationException. A cópia protege a estrutura da lista; ela não torna profundamente imutáveis os objetos mutáveis dos elementos.

Records estendem implicitamente java.lang.Record e são final. Eles não podem estender outra classe nem ser estendidos, mas podem implementar interfaces como Comparable. Use-os para dados definidos por seus valores; use uma classe comum para estado variável, campos de instância ocultos adicionais ou uma hierarquia de classes extensível.

Principais conclusões

  • O cabeçalho de um record define os componentes e o construtor, os métodos de acesso e os métodos de valor gerados.
  • Métodos de acesso de record usam nomes dos componentes, como x().
  • Um construtor compacto verifica ou normaliza parâmetros antes da atribuição automática aos campos.
  • As referências dos componentes de um record são final, mas os objetos referenciados podem continuar mutáveis.
  • Uma cópia não modificável da lista protege a estrutura de lista de um record.
  • Um record é final, não pode estender uma classe explicitamente e pode implementar interfaces.

Exercícios resolvidos de Herança, interfaces e polimorfismo em Java

5 Lições Interativas

1

Construa um dispositivo com herança passo a passo

12 passos
Crie um dispositivo com rótulo, acrescente um subtipo de sensor e acompanhe a construção no mesmo objeto.
  1. 1
    IntroduçãoUm objeto com uma função geral e outra específica

    Um sensor é um tipo de dispositivo. Todo dispositivo precisa de um rótulo, enquanto um sensor também precisa de uma unidade de medida. A herança permite que o dispositivo seja responsável por seu rótulo e o sensor acrescente sua unidade.

    Vamos construir essa relação, usar um método herdado e acompanhar como as chamadas de construtores inicializam cada parte. A pergunta principal é a qual classe pertence cada campo e, portanto, qual construtor deve atribuir seu valor.

  2. 2
    ExemploComece com o estado de que todo dispositivo precisa
    class Device {
        private final String label;
        Device(String label) { this.label = label; }
        public String label() { return label; }
    }
    

    Device controla seu rótulo privado. Outros códigos o leem por meio de label(). Uma subclasse herdará esse método público, mas não poderá atribuir um valor ao campo privado diretamente. Isso mantém a inicialização do rótulo em um só lugar.

  3. 3
    ExemploAcrescente apenas o estado específico do sensor
    class Sensor extends Device {
        private final String unit;
        Sensor(String label, String unit) {
            super(label);
            this.unit = unit;
        }
        public String unit() { return unit; }
    }
    

    Sensor estende uma classe e acrescenta um campo. Sua invocação super(label) passa a responsabilidade pelo rótulo a Device; o próprio construtor do sensor armazena a unidade.

  4. 4
    Ideia-chaveLeia ambas as partes por meio do sensor

    Após Sensor s = new Sensor("Room", "C");, s.label() retorna “Room” e s.unit() retorna “C”. O primeiro método é herdado; o segundo pertence a Sensor.

    Ambas as chamadas dizem respeito ao mesmo objeto. Estender Device não criou um segundo objeto dispositivo ao lado do sensor. O rótulo privado está presente, embora o código do sensor o acesse por meio de um método.

  5. 5
    DesafioEscolha a relação para um laboratório

    Um Lab contém vários sensores. Lab deve estender Sensor ou manter referências a sensores? Explique a relação.

    ---reveal---

    Mantenha referências a sensores em campos ou em uma coleção. Um laboratório tem sensores, mas não é um sensor, portanto a composição é adequada. Estender Sensor prometeria que um laboratório pode ser usado onde quer que um sensor seja necessário, inclusive em operações que descrevem a unidade de um sensor.

  6. 6
    ExplicaçãoEntre no construtor da superclasse

    Chamar new Sensor("Room", "C") seleciona o construtor de sensor com dois argumentos. Sua chamada super(label) invoca Device(String) para esse mesmo objeto.

    Device primeiro chega à sua própria superclasse, Object, e depois atribui o rótulo. Quando Device retorna, a inicialização dos campos do sensor e o restante do corpo do construtor do sensor são executados. Por fim, a nova referência fica disponível para quem fez a chamada.

  7. 7
    DicaEncontre o construtor sem argumentos ausente

    Remova super(label) do construtor do sensor e Java insere super(). Essa chamada não tem correspondência: Device declara apenas Device(String).

    O erro do construtor diz respeito aos argumentos, não ao campo privado. Substituir a invocação por new Device(label) cria um objeto sem relação e ainda deixa o problema do super() implícito. Passe o rótulo ao construtor da superclasse.

  8. 8
    ExemploDelegue uma unidade padrão opcional

    Acrescente este construtor dentro de Sensor:

    Sensor(String label) {
        this(label, "unknown");
    }
    

    new Sensor("Hall") delega ao construtor existente com dois argumentos, que invoca super(label). O objeto resultante tem o rótulo “Hall” e a unidade “unknown”. Não escreva também super(...) no construtor que delega: um construtor tem no máximo uma invocação explícita de construtor.

  9. 9
    ConexãoValide um parâmetro antes da construção

    O Java 25 padrão permite trabalho restrito antes de uma invocação explícita de construtor. Por exemplo, um construtor de sensor pode rejeitar um rótulo recebido vazio antes de chamar super(label).

    Esse código antecipado pode usar parâmetros, mas não pode ler os campos do objeto nem chamar seus métodos de instância. Atribuições simples restritas a campos da classe atual sem inicializadores na declaração também são permitidas. Para níveis padrão mais antigos da linguagem, mantenha a invocação em primeiro lugar.

  10. 10
    DesafioPreveja uma mensagem do construtor

    Em uma versão separada, Device exibe “device” após sua própria chamada à superclasse. Sensor exibe “sensor” após super(label). Qual mensagem aparece primeiro ao criar um sensor e por quê?

    ---reveal---

    “device” aparece primeiro. O construtor do sensor invoca o construtor do dispositivo antes de continuar até sua própria instrução de impressão. O objeto é um sensor durante toda a construção; essas mensagens mostram a ordem do trabalho dos construtores, não mudanças em sua classe.

  11. 11
    DesafioCorrija uma atribuição a campo herdado

    Uma nova subclasse tenta inicializar o rótulo privado do dispositivo com this.label = label; e omite super(label). Quais dois problemas relacionados isso cria?

    ---reveal---

    A subclasse não pode atribuir um valor diretamente ao campo privado. O super() implícito também não tem um construtor de dispositivo correspondente. Use super(label) para que Device atribua seu próprio estado privado e depois atribua valores apenas aos campos declarados na subclasse.

  12. 12
    RecapitulaçãoLocalize uma falha de construção

    Quando um subtipo não puder ser construído, localize a primeira chamada de construtor sem um destino acessível correspondente. Depois verifique se cada classe inicializa os campos pelos quais é responsável.

    Mantenha o teste de relação junto desse rastreamento: um subtipo pode ser usado como seu tipo base; um objeto contido pertence a um campo. Essas verificações tratam de tipos diferentes de erros de projeto e de compilação.

2

Exiba e compare entradas de catálogo corretamente

14 passos
Sobrescreva comportamento, construa um método de igualdade por valor e conecte-o ao cálculo do código de dispersão.

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

  1. 1
    IntroduçãoEscolha o que uma entrada de catálogo deve representar
  2. 2
    ExemploAmplie uma descrição básica
  3. 3
    DicaEvite chamar a sobrescrita de dentro dela mesma
  4. 4
    DesafioVerifique uma substituição aparente
  5. 5
    ExemploForneça à impressão um método exato para chamar
  6. 6
    Ideia-chaveIdentifique a classe real sem alterá-la
  7. 7
    DesafioDiagnostique texto exibido com aparência padrão
  8. 8
    ExemploDeclare os dados de igualdade
  9. 9
    ExemploRejeite o objeto errado antes de comparar campos
  10. 10
    ExemploCalcule o código de dispersão dos mesmos campos de igualdade
  11. 11
    DesafioIdentifique a sobrecarga que uma coleção não usa
  12. 12
    Ideia-chaveProteja comparações nas duas direções
  13. 13
    DesafioDistinga texto igual de valores iguais
  14. 14
    RecapitulaçãoVerifique uma classe de valor em sua interface pública
3

Construa um contrato e compartilhe sua implementação

18 passos
Use uma fonte abstrata, acrescente uma interface e resolva um conflito entre métodos padrão herdados.

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

  1. 1
    IntroduçãoSepare os dados comuns da capacidade comum
  2. 2
    ExemploComece uma fonte abstrata de mensagens
  3. 3
    ExemploComplete a operação ausente
  4. 4
    DesafioDecida se uma subclasse pode ser concreta
  5. 5
    ExemploNomeie a leitura como uma capacidade
  6. 6
    ConexãoFaça a base existente cumprir a interface
  7. 7
    ExemploEscreva uma chamada usando o contrato
  8. 8
    DesafioCorrija uma implementação com acesso mais restrito
  9. 9
    ExemploAcrescente uma operação padrão útil
  10. 10
    ExplicaçãoMantenha os métodos auxiliares estáticos e privados em suas funções
  11. 11
    ExemploCrie um conflito real entre métodos padrão
  12. 12
    ExemploEscolha um método padrão explicitamente
  13. 13
    DesafioDê prioridade à implementação da classe
  14. 14
    Ideia-chaveOfereça uma base sem exigir que toda implementação a use
  15. 15
    DesafioColoque um rótulo e um construtor no tipo correto
  16. 16
    DicaVerifique novos métodos padrão em relação aos métodos herdados
  17. 17
    DesafioAcrescente um novo leitor sem mudar sua superclasse existente
  18. 18
    RecapitulaçãoAvalie uma proposta de tipo comum
4

Acompanhe uma chamada de relatório por tipos gerais

17 passos
Acompanhe um objeto receptor pela escolha de sobrecarga, pelo despacho em tempo de execução e por um teste seguro de subtipo, depois construa um laço geral.

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

  1. 1
    IntroduçãoAcompanhe o que a variável promete e o que o objeto faz
  2. 2
    ExemploDê uma referência geral a um relatório especializado
  3. 3
    ExplicaçãoVerifique o que a referência expõe
  4. 4
    DesafioEscolha tipos para uma lista mista de relatórios
  5. 5
    ExemploResolva primeiro a chamada externa
  6. 6
    ExemploResolva a chamada interna no mesmo objeto receptor
  7. 7
    ExemploEscolha uma sobrecarga pelo argumento declarado
  8. 8
    DesafioSepare duas decisões em uma chamada
  9. 9
    DicaMarque campos e chamadas estáticas separadamente em um rastreamento
  10. 10
    ExemploAcesse com segurança a operação exclusiva de vendas
  11. 11
    DesafioPreveja uma conversão explícita quando o objeto muda
  12. 12
    ExemploReúna cabeçalhos por meio da operação comum
  13. 13
    ConexãoUse comparação como outra operação compartilhada
  14. 14
    DesafioOrdene valores escolhendo uma implementação
  15. 15
    Ideia-chaveReserve verificações de subtipo para tarefas do subtipo
  16. 16
    DesafioOfereça um cabeçalho de auditoria sem um novo ramo
  17. 17
    RecapitulaçãoAcompanhe uma chamada desconhecida em ordem
5

Construa uma opção fixa e um valor de dados protegido

15 passos
Acrescente dados a um enum, crie um record e impeça que componentes de lista mutáveis mudem sem que ele saiba.

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

  1. 1
    IntroduçãoEscolha entre opções nomeadas e dados agrupados
  2. 2
    ExemploDê às opções de serviço um tipo próprio
  3. 3
    ExemploAssocie números úteis às opções
  4. 4
    DicaMantenha os dados de serviço independentes da ordem de declaração
  5. 5
    DesafioPreveja uma busca e um campo útil
  6. 6
    ConexãoMantenha o comportamento com a opção fixa
  7. 7
    ExemploDeclare os componentes de uma solicitação de entrega
  8. 8
    ExemploNormalize os dados recebidos antes da atribuição
  9. 9
    DesafioCompare as solicitações normalizadas
  10. 10
    ExemploEncontre o objeto mutável dentro de um componente final
  11. 11
    ExemploArmazene uma cópia não modificável
  12. 12
    DicaRetorne um novo record quando os valores precisarem mudar
  13. 13
    DesafioEscolha a correção adequada para uma rota que muda
  14. 14
    DesafioModele novas opções de serviço e solicitações de clientes
  15. 15
    RecapitulaçãoExamine o comportamento gerado antes de acrescentar código

Questões de Herança, interfaces e polimorfismo em Java

50 Questões de Prática em 5 conjuntos

Conjunto 1: Use contratos e tipos gerais de referência10 Questões de Prática

Questão 1 de 10

Uma TextDocument e uma classe Image sem relação entre si devem oferecer exportText(). Cada uma já estende uma classe diferente. Qual tipo comum melhor expressa essa capacidade?
  1. Uma classe Exportable estendida por ambas como segunda superclasse.
  2. Uma superclasse Image que também contém documentos de texto.
  3. Uma interface Exportable implementada por ambas.
  4. Uma variável Object que expõe diretamente exportText().
Mostrar respostaOcultar resposta
Resposta: C
Uma interface fornece uma capacidade comum a classes sem relação entre si, preservando suas superclasses existentes. Uma classe não pode acrescentar uma segunda superclasse apenas para compartilhar a operação de exportação. Declarar uma referência como Object não exporia exportText(), pois essa operação não existe em Object.

Questão 2 de 10

Este código compila? Por quê?

interface Named { String name(); }
class Entity {
    public String name() { return "entity"; }
}
class Asset extends Entity implements Named { }
  1. Sim; o método público herdado cumpre o contrato da interface.
  2. Não; cada classe que implementa a interface deve redeclarar todos os seus métodos.
  3. Não; a superclasse deve implementar explicitamente a mesma interface.
  4. Sim; Java gera um corpo vazio para métodos de interface ausentes.
Mostrar respostaOcultar resposta
Resposta: A
Asset herda um name() público com a assinatura exigida por Named, portanto a classe concreta já cumpre o contrato. Sua superclasse não precisa nomear a interface. Exigir outra declaração em Asset ignoraria a implementação adequada que ela herda.

Questão 3 de 10

O que new Screen().label() retorna?

interface Tagged {
    default String label() { return "tagged"; }
}
class Panel {
    public String label() { return "panel"; }
}
class Screen extends Panel implements Tagged { }
  1. “tagged”
  2. “paneltagged”
  3. As declarações causam um erro de compilação.
  4. “panel”
Mostrar respostaOcultar resposta
Resposta: D
O método público concreto herdado de Panel tem prioridade sobre o método padrão correspondente da interface. Portanto, a tela retorna “panel”. A classe não precisa resolver esse caso como se fosse um conflito entre métodos padrão de duas interfaces sem relação entre si.

Questão 4 de 10

Fast e Safe são interfaces sem relação entre si, cada uma com default String mode(). Uma Service concreta implementa ambas e não herda nenhuma implementação de mode() de uma classe. O que Service deve fazer?
  1. Usar a interface listada primeiro após implements.
  2. Sobrescrever mode() para resolver o conflito herdado.
  3. Renomear ambos os métodos de interface no corpo da classe.
  4. Chamar Service.super.mode() a partir de seu construtor.
Mostrar respostaOcultar resposta
Resposta: B
Nenhuma das interfaces é mais específica, portanto Java não tem um único método padrão para herdar. Service deve fornecer uma sobrescrita, que pode chamar um método padrão escolhido de uma interface direta com InterfaceName.super.mode(). Listar uma interface primeiro não dá prioridade ao seu método padrão.

Questão 5 de 10

Robot implementa Movable. Em Movable unit = new Robot();, qual é o tipo declarado de unit?
  1. Robot
  2. Movable
  3. Object
  4. Tanto Movable quanto Robot
Mostrar respostaOcultar resposta
Resposta: B
O tipo declarado é o tipo escrito na declaração da variável, Movable. Robot é a classe real do objeto criado. Esses tipos têm funções diferentes: o tipo declarado limita as chamadas disponíveis, enquanto o objeto continua sendo um robô com suas implementações e seu estado.

Questão 6 de 10

Vehicle tem move(), e apenas Bicycle tem ringBell(). Bicycle extends Vehicle. O que acontece com v.ringBell() após Vehicle v = new Bicycle();?
  1. Não compila porque Vehicle não tem o método ringBell().
  2. Compila e executa a implementação de ringBell() da bicicleta.
  3. Compila, mas lança uma exceção porque o objeto virou um Vehicle.
  4. Compila e executa um método vazio herdado de Object.
Mostrar respostaOcultar resposta
Resposta: A
O compilador verifica os métodos disponíveis usando o tipo declarado Vehicle, que não tem ringBell(). O objeto bicicleta não perdeu seu método de campainha nem mudou de classe. Uma referência adequada de subtipo poderia acessá-lo, mas a vinculação dinâmica não pode fazer uma assinatura indisponível compilar.

Questão 7 de 10

interface Percent declara static int cap(int n), e Gauge implements Percent não declara nenhum método cap. Como o código deve chamar o método auxiliar da interface com 120?
  1. Gauge.cap(120)
  2. new Gauge().cap(120)
  3. Percent.super.cap(120)
  4. Percent.cap(120)
Mostrar respostaOcultar resposta
Resposta: D
Um método estático de interface pertence à interface e é chamado por meio de seu nome. As classes que a implementam não o herdam, portanto nem Gauge.cap(120) nem uma chamada de instância alcança esse método auxiliar. Chamadas qualificadas com super de interface selecionam métodos padrão de instância, não métodos auxiliares estáticos.

Questão 8 de 10

Um método apenas percorre formas e chama area(). Ele deve aceitar tanto ArrayList<Shape> quanto LinkedList<Shape>. Qual tipo de parâmetro expõe as operações de lista necessárias?
  1. ArrayList<Shape>
  2. LinkedList<Shape>
  3. List<Shape>
  4. Object
Mostrar respostaOcultar resposta
Resposta: C
Ambas as implementações concretas de lista atendem a List<Shape>, que permite percorrer elementos de forma. Escolher qualquer uma das implementações concretas rejeita quem usa a outra sem ajudar o laço. Object é geral demais porque não expõe as operações de lista de que o método precisa.

Questão 9 de 10

O que acontece ao compilar e executar new Row().render()?

interface Renderable { String render(); }
abstract class Base implements Renderable {
    public String render() { return "[" + body() + "]"; }
    protected abstract String body();
}
class Row extends Base {
    @Override protected String body() { return "row"; }
}
  1. Falha porque Row deve redeclarar render().
  2. Compila e retorna “[row]”.
  3. Falha porque body() deve ser público.
  4. Compila e retorna “[]”.
Mostrar respostaOcultar resposta
Resposta: B
Base fornece a implementação pública da interface, mas deixa seu método auxiliar protegido abstrato. Row completa esse método auxiliar, portanto é concreta; o render() herdado chama a sobrescrita de Row e acrescenta os caracteres ao redor. O método auxiliar não precisa ser público porque não é a operação pública declarada por Renderable.

Questão 10 de 10

Uma estrutura de relatórios tem uma Report abstrata com estado privado de título, um construtor que define esse estado e body() abstrato. Relatórios e objetos de gráficos sem relação com eles também devem poder ser impressos. Qual estrutura preserva ambos os requisitos?
  1. Substituir Report por uma interface apenas com métodos padrão que armazene cada título.
  2. Fazer gráficos estenderem Report mesmo quando não são objetos de relatório.
  3. Manter Report e acrescentar uma interface Printable às classes relevantes.
  4. Fazer classes de relatório estenderem Report e uma Printable abstrata.
Mostrar respostaOcultar resposta
Resposta: C
A base abstrata de relatório pode continuar responsável pelo estado do título e pela construção, enquanto uma interface separada expressa a capacidade de impressão entre diferentes famílias de classes. Substituir a base com estado por uma interface faz perder campos por objeto e construtores. Uma segunda superclasse abstrata também violaria a regra de herança simples de classes do Java.
Conjunto 2: Construa classes e acompanhe comportamento herdado10 Questões de Prática

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

Questão 1 de 10

Uma Library armazena vários objetos Shelf, e cada estante armazena livros. Como Library deve representar sua relação com Shelf?
  1. Estender Shelf para herdar métodos de estante.
  2. Armazenar estantes em campos ou em uma coleção.
  3. Tornar Shelf uma subclasse de Library.
  4. Tornar Library uma subclasse final de Shelf.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Account declara um campo privado balance e um método público getBalance(). SavingsAccount extends Account. Como um método em SavingsAccount pode ler o estado herdado da conta?
  1. Ler o campo como this.balance.
  2. Ler o campo como super.balance.
  3. Criar uma conta e ler seu saldo.
  4. Chamar o método herdado getBalance().
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

O que é exibido por new Leaf();?

class Root {
    Root() { System.out.print("R"); }
}
class Branch extends Root {
    Branch() { System.out.print("B"); }
}
class Leaf extends Branch {
    Leaf() { this(7); System.out.print("L"); }
    Leaf(int n) { System.out.print(n); }
}
  1. L7BR
  2. RBL7
  3. RB7L
  4. RRB7L
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

O que acontece quando este código é compilado?

class Machine {
    Machine(int serial) { }
}
class Printer extends Machine {
    Printer() { }
}
  1. A compilação falha porque super() não tem construtor correspondente.
  2. A compilação é bem-sucedida e usa Machine(0) por padrão.
  3. A compilação é bem-sucedida sem construir a parte da máquina.
  4. A compilação falha porque subclasses não podem ter construtores sem argumentos.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Meter declara public int read(). Uma subclasse escreve @Override public int read(int offset). Por que a compilação falha?
  1. O tipo de retorno deve mudar em toda sobrescrita da subclasse.
  2. A anotação impede que métodos públicos sejam herdados.
  3. A lista de parâmetros muda, portanto o método é uma sobrecarga.
  4. A subclasse deve redeclarar todos os campos antes de sobrescrever.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

O que a última instrução exibe?

class Parcel {
    public String toString() { return "parcel"; }
}
class ChilledParcel extends Parcel {
    @Override public String toString() {
        return super.toString() + " cold";
    }
}
Parcel item = new ChilledParcel();
System.out.println("Send " + item);
  1. “Send parcel”
  2. “Send parcel cold”
  3. “Send ChilledParcel cold”
  4. “Send null cold”
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Uma classe final Label tem apenas public boolean equals(Label other), que compara seu texto, e herda equals(Object). Duas etiquetas distintas têm o mesmo texto. Ambas as referências são armazenadas em variáveis declaradas como Object. O que a.equals(b) retorna?
  1. false, pois o método selecionado ainda compara identidade.
  2. true, pois o texto igual seleciona a sobrecarga de Label.
  3. true, pois Object compara cada campo automaticamente.
  4. A compilação falha porque Object não tem método de igualdade.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

abstract class Sensor declara public abstract double reading();. O que uma LightSensor extends Sensor concreta deve fornecer ou herdar?
  1. Um construtor público chamado Sensor.
  2. Uma implementação estática de double reading().
  3. Uma implementação privada de double reading().
  4. Uma implementação pública de double reading().
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Por que esta classe não compila?

interface Resettable { void reset(); }
class Counter implements Resettable {
    void reset() { }
}
  1. reset() deve ser estático em Counter.
  2. reset() deve ser público em Counter.
  3. Resettable precisa de um corpo de construtor.
  4. Counter deve estender Resettable.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

O que new TimedJob().cost() retorna?

class Job {
    public int rate() { return 3; }
    public int cost() { return 4 * rate(); }
}
class TimedJob extends Job {
    @Override public int rate() { return 5; }
    @Override public int cost() { return super.cost() + 2; }
}
  1. 22
  2. 14
  3. 20
  4. 28
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 3: Acompanhe chamadas, membros ocultados e conversões verificadas10 Questões de Prática

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

Questão 1 de 10

Shape s = null; é seguido por s instanceof Circle. Circle extends Shape. Qual é o resultado do teste?
  1. false
  2. true
  3. Uma ClassCastException é lançada.
  4. Uma NullPointerException é lançada.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

O que acontece com a última instrução?

class Animal { }
class Fox extends Animal { }
Animal a = new Animal();
Fox f = (Fox) a;
  1. Não compila porque conversões para subtipo nunca são permitidas.
  2. Cria uma Fox com o estado original de Animal.
  3. Compila e depois lança ClassCastException.
  4. Tem sucesso e dá a f o mesmo objeto Animal comum.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

O que pick(value) retorna?

static String pick(Object x) { return "object"; }
static String pick(CharSequence x) { return "sequence"; }
// Inside a method of the class containing these overloads:
Object value = "notes";
  1. “sequence”
  2. “notes”
  3. A chamada lança ClassCastException.
  4. “object”
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Qual texto é produzido por p.label + ":" + p.show()?

class Parent {
    String label = "P";
    String show() { return label; }
}
class Child extends Parent {
    String label = "C";
    @Override String show() { return label; }
}
Parent p = new Child();
  1. “P:P”
  2. “P:C”
  3. “C:C”
  4. “C:P”
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Um método aceita List<Vehicle> e pode acrescentar um ônibus. Bicycle extends Vehicle e Bus extends Vehicle. Por que uma List<Bicycle> não pode ser passada a ele?
  1. Um objeto Bicycle não pode ser atribuído a nenhuma referência Vehicle.
  2. Listas impedem a herança sempre que seus elementos são objetos mutáveis.
  3. O método poderia inserir um Bus em uma lista que deve conter bicicletas.
  4. O método converteria cada objeto bicicleta em um Vehicle comum.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

O que b.test() retorna?

class Base {
    String use(Object x) { return "BO"; }
    String use(String x) { return "BS"; }
}
class Sub extends Base {
    @Override String use(Object x) { return "SO"; }
    @Override String use(String x) { return "SS"; }
    String test() {
        Object text = "x";
        return super.use(text) + ":" + use(text);
    }
}
Sub b = new Sub();
  1. “BO:SO”
  2. “BS:SS”
  3. “BO:SS”
  4. “SO:SO”
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

O que new Sub(); exibe com o Java 25 padrão?

class Base {
    Base() { System.out.print(value()); }
    int value() { return -1; }
}
class Sub extends Base {
    int n = 8;
    Sub() { super(); n = 11; }
    @Override int value() { return n; }
}
  1. -1
  2. 8
  3. 11
  4. 0
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

O que este código exibe? Tool não declara métodos.

class Tool { }
class Drill extends Tool {
    int speed() { return 6; }
}
Tool tool = new Drill();
Object copy = tool;
if (copy instanceof Drill d) {
    System.out.print(d.speed());
}
System.out.print(":" + (copy == tool));
  1. “6:false”
  2. “6:true”
  3. “0:true”
  4. A chamada de velocidade não compila.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Creator tem public Object create(). TextCreator extends Creator o sobrescreve. Qual combinação de tipo de retorno e acesso é válida?
  1. protected String create()
  2. public String create()
  3. public int create()
  4. private Object create()
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Um sistema de plugins tem muitas subclasses de Document. Seu método de prévia usa ramos instanceof para calcular a contagem de palavras de cada documento. Novas classes de documento são acrescentadas com frequência. Qual mudança permite que novas classes ofereçam prévia sem editar essa cadeia de testes de tipo?
  1. Converter cada Document explicitamente para Object e manter os mesmos ramos de subtipo.
  2. Mover os ramos de subtipo para um método estático na classe de prévia.
  3. Dar a cada subtipo um campo de mesmo nome e lê-lo por meio de Document.
  4. Declarar wordCount() em Document e implementá-lo em cada subtipo concreto.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 4: Modele valores, compare-os e ordene-os10 Questões de Prática

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

Questão 1 de 10

Uma aplicação usa enum Status { NEW, SENT, DONE }. O que Status.valueOf("sent") faz?
  1. Retorna Status.SENT.
  2. Retorna a cadeia de caracteres “sent”.
  3. Retorna Status.NEW.
  4. Lança IllegalArgumentException.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

Para record Reading(int count, String unit) { }, qual chamada gerada lê a contagem de uma variável r?
  1. r.getCount()
  2. r.count()
  3. r.count
  4. r.readCount()
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

enum Band { LOW(2), HIGH(9); ... } armazena o argumento do construtor em um campo code imutável. Uma nova constante MID(5) é inserida entre LOW e HIGH. Como mudam o ordinal() e o código armazenado de HIGH?
  1. Sua posição ordinal passa a 2; seu código permanece 9.
  2. Sua posição ordinal permanece 1; seu código passa a 2.
  3. Sua posição ordinal passa a 2; seu código passa a 2.
  4. Sua posição ordinal permanece 1; seu código permanece 9.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

Dois objetos Token diferentes retornam o mesmo código de dispersão. Suas implementações de equals(Object) comparam corretamente os campos que definem cada token. O código de dispersão compartilhado viola o contrato de igualdade e de código de dispersão?
  1. Sim; todo objeto deve ter um código de dispersão único.
  2. Sim; compartilhar um código de dispersão torna os tokens iguais.
  3. Não; objetos diferentes podem compartilhar um código de dispersão.
  4. Não; coleções baseadas em hash ignoram a igualdade após calcular o código.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

record Result(int seconds) implements Comparable<Result> retorna Integer.compare(seconds, other.seconds). Qual é o sinal de new Result(418).compareTo(new Result(405))?
  1. Negativo.
  2. Positivo.
  3. Zero.
  4. Não há resultado porque records não podem implementar interfaces.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

O que a última instrução exibe?

record Tag(String text) {
    Tag { text = text.trim(); }
}
Tag a = new Tag(" north ");
Tag b = new Tag("north");
System.out.println(a.equals(b));
  1. true
  2. false
  3. “north”
  4. A compilação falha porque parâmetros não podem receber outra atribuição.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

Um Ticket.equals da classe base aceita qualquer Ticket com o mesmo código. PriorityTicket extends Ticket aceita apenas objetos PriorityTicket e também compara a prioridade. Um bilhete comum t e um bilhete prioritário p têm o mesmo código. O que t.equals(p) e p.equals(t) retornam, e isso respeita a simetria?
  1. Ambos retornam falso; o par respeita a simetria.
  2. Ambos retornam verdadeiro; o par respeita a simetria.
  3. Retornam verdadeiro e falso; o par viola a simetria.
  4. Retornam falso e verdadeiro; o par viola a simetria.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

O que é exibido? List e ArrayList estão importados.

record Batch(List<String> names) { }
var original = new ArrayList<String>(List.of("Rin"));
Batch first = new Batch(original);
Batch second = new Batch(new ArrayList<>(original));
original.add("Sol");
System.out.println(first.equals(second) + ":"
    + first.names().size() + ":" + second.names().size());
  1. “true:2:1”
  2. “false:1:1”
  3. “true:1:1”
  4. “false:2:1”
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

O que acontece quando Collections.sort(list) é executado? Os tipos necessários de java.util estão importados.

record Rank(int score) implements Comparable<Rank> {
    public int compareTo(Rank other) {
        return Integer.compare(score, other.score);
    }
}
List<Rank> list = List.of(new Rank(8), new Rank(3));
  1. Ordena a lista com as pontuações 3 e depois 8.
  2. Não compila porque Rank não tem método de comparação.
  3. Lança UnsupportedOperationException.
  4. Mantém as pontuações 8 e depois 3 porque a ordem dos records é fixa.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

O que Rule.DOUBLE.apply(Rule.NEXT.apply(4)) retorna?

enum Rule {
    NEXT {
        int apply(int n) {
            return switch (n) { case 4 -> 6; default -> n + 1; };
        }
    },
    DOUBLE { int apply(int n) { return 2 * n; } };
    abstract int apply(int n);
}
  1. 12
  2. 10
  3. 9
  4. 16
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Conjunto 5: Combine regras Java e diagnostique falhas10 Questões de Prática

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

Questão 1 de 10

Qual tipo Java é adequado para um conjunto fixo de opções de exportação chamadas PDF, TEXT e HTML?
  1. Um record com três campos mutáveis de opção.
  2. Um enum que declara essas três constantes.
  3. Um inteiro cujos valores válidos não são documentados.
  4. Uma superclasse com uma subclasse para cada arquivo.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 2 de 10

O que new Terminal().name() retorna?

interface Basic {
    default String name() { return "basic"; }
}
interface Detailed extends Basic {
    default String name() { return "detail"; }
}
class Terminal implements Basic, Detailed { }
  1. “basic”
  2. “basicdetail”
  3. “detail”
  4. A classe tem um conflito não resolvido entre métodos padrão.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 3 de 10

Uma List<Notifier> contém objetos de várias classes sem relação entre si que implementam Notifier.send(). Um laço chama n.send() em cada elemento. Uma nova PushNotifier implementa o mesmo contrato corretamente. Qual mudança é necessária no laço para aceitar suas instâncias?
  1. Nenhuma mudança; a chamada de interface existente despacha para cada implementação.
  2. Acrescentar uma conversão explícita para PushNotifier antes de cada chamada a send().
  3. Substituir a variável de interface por uma variável Object para cada elemento.
  4. Acrescentar um ramo para que todos os objetos antigos chamem a implementação da nova classe.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 4 de 10

O que é exibido com o Java 25 padrão?

class Base {
    Base(int n) { System.out.print("B" + n); }
}
class Sub extends Base {
    Sub(int n) {
        if (n < 0) throw new IllegalArgumentException();
        super(n + 1);
        System.out.print("S" + n);
    }
}
new Sub(2);
  1. “S2B3”
  2. “B2S2”
  3. A compilação falha porque super não está em primeiro lugar.
  4. “B3S2”
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 5 de 10

Uma classe mutável Code define igualdade e cálculo de código de dispersão usando seu campo de texto. Um HashSet<Code> contém c com o texto “red”. O programa deve mudar seu texto para “blue”, manter o mesmo objeto e preservar uma busca confiável no conjunto. Em que ordem ele deve atualizar o objeto e o conjunto?
  1. Alterar o texto, remover o objeto e acrescentá-lo novamente.
  2. Acrescentar o objeto novamente, alterar seu texto e removê-lo.
  3. Remover o objeto, acrescentá-lo novamente e alterar seu texto.
  4. Remover o objeto, alterar seu texto e acrescentá-lo novamente.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 6 de 10

O que x.call(arg) + ":" + x.call("x") produz?

class Alpha {
    String call(Object x) { return "AO"; }
    String call(String x) { return "AS"; }
}
class Beta extends Alpha {
    @Override String call(Object x) { return "BO"; }
    @Override String call(String x) { return "BS"; }
}
Alpha x = new Beta();
Object arg = "data";
  1. “AO:AS”
  2. “BO:BS”
  3. “BO:AS”
  4. “BS:BS”
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 7 de 10

O que a última instrução exibe? List e ArrayList estão importados.

record Group(List<String> names) {
    Group { names = List.copyOf(names); }
}
var original = new ArrayList<String>();
original.add("Uma");
Group group = new Group(original);
original.add("Zed");
System.out.println(group.names().size() + ":" + original.size());
  1. “1:2”
  2. “2:2”
  3. “1:1”
  4. O construtor do record não compila.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 8 de 10

O que acontece quando este código é compilado?

interface Ready {
    default String state() { return "ready"; }
}
abstract class Base {
    public abstract String state();
}
class Console extends Base implements Ready { }
  1. A compilação é bem-sucedida; Console herda a implementação padrão de Ready.
  2. A compilação é bem-sucedida; o método de Base retorna null até ser sobrescrito.
  3. A compilação falha; Console deve implementar o método abstrato herdado.
  4. A compilação falha; classes abstratas não podem ser combinadas com interfaces.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 9 de 10

Dois objetos record Player(String name, int points) têm nomes diferentes, mas a mesma pontuação. Seu compareTo personalizado compara apenas a pontuação usando Integer.compare. O que equals e compareTo informam para esses dois objetos?
  1. equals é verdadeiro; compareTo é zero.
  2. equals é falso; compareTo deve ser diferente de zero.
  3. equals é verdadeiro; compareTo deve ser diferente de zero.
  4. equals é falso; compareTo é zero.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Questão 10 de 10

Uma biblioteca já expõe interface Storable { String saveText(); }. Muitas novas implementações compartilham um campo privado de caminho, validação no construtor e código auxiliar, enquanto algumas implementações existentes estendem outras classes. Como a biblioteca pode oferecer reutilização preservando a API comum?
  1. Acrescentar uma implementação abstrata de Storable que as classes possam estender.
  2. Substituir Storable por uma classe que toda implementação deva estender.
  3. Mover o campo privado de instância para caminho para a interface Storable.
  4. Acrescentar uma segunda superclasse às implementações existentes para armazenamento compartilhado.
Mostrar respostaOcultar resposta

Salve esta aula para ver esta resposta.

Travou em uma questão sua? Ajuda com Tarefas

Termos-Chave

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

A

Acesso protegido
Acesso protegido permite acesso dentro de um pacote e acesso restrito por subclasses em outros pacotes, sem tornar um membro público.
O acesso protegido permite que uma subclasse use um método auxiliar da superclasse, mantendo-o fora da API pública.
Anotação @Override
A anotação @Override, escrita @Override, pede ao compilador que verifique se um método sobrescreve ou implementa um método herdado.
A anotação Override detecta tostring() quando quem programou pretendia substituir toString().
Assinatura do método
Uma assinatura do método identifica um método por seu nome e tipos de parâmetros para seleção de sobrecarga e sobrescrita; o tipo de retorno sozinho não basta.
Mudar um parâmetro de Object para String altera a assinatura do método e cria uma sobrecarga.

C

Cadeia de construtores
Uma cadeia de construtores é uma sequência de invocações de construtores da mesma classe e da superclasse que inicializa as partes de um novo objeto.
Uma cadeia de construtores de uma classe final da hierarquia chega aos construtores das classes base antes da execução do restante de seu corpo.
Capacidade
Uma capacidade é uma operação ou um conjunto de operações que um objeto se compromete a oferecer, frequentemente expresso por uma interface entre classes sem relação entre si.
Exportar é uma capacidade que um rótulo de texto e um círculo podem oferecer enquanto mantêm superclasses diferentes.
Classe abstrata
Uma classe abstrata é uma classe que não pode ser instanciada diretamente e pode combinar campos e código compartilhados com operações incompletas.
Uma classe abstrata Shape pode armazenar um nome enquanto exige que círculos e retângulos concretos forneçam uma área.
Classe concreta
Uma classe concreta é uma classe não abstrata que fornece ou herda implementações de todos os métodos abstratos obrigatórios e pode ter instâncias.
Rectangle é uma classe concreta quando completa a operação de área exigida por sua base abstrata de forma.
Classe em tempo de execução
A classe em tempo de execução é a classe real de um objeto, informada por getClass() independentemente do tipo declarado de sua referência.
A classe em tempo de execução de um estudante continua sendo Student mesmo quando um método o recebe por meio de um parâmetro Person.
Código de dispersão
Um código de dispersão é um inteiro usado para localizar candidatos em uma coleção baseada em hash; objetos considerados iguais devem retornar o mesmo código de dispersão.
O código de dispersão de um livro pode ser construído a partir de seu código de catálogo e de sua edição quando esses campos também definem a igualdade.
Colisão de hash
Uma colisão de hash ocorre quando objetos diferentes retornam o mesmo código de dispersão, o que é permitido porque a igualdade pode distingui-los.
Uma colisão de hash entre dois tokens diferentes não exige que seu método de igualdade retorne verdadeiro.
Componente de record
Um componente de record é um valor declarado no cabeçalho de um record que determina um campo private final, um parâmetro de construtor e um método de acesso público.
O componente de record seconds pode ser lido de um corredor usando o método gerado runner.seconds().
Composição
Composição modela uma relação “tem um” armazenando referências a outros objetos em campos, sem afirmar que o objeto é um desses outros objetos.
A composição permite que um Customer contenha um Address sem fazer com que todo cliente possa ser usado como endereço.
Conflito entre métodos padrão
Um conflito entre métodos padrão surge quando interfaces sem relação entre si fornecem métodos padrão herdados concorrentes que a classe que as implementa deve resolver.
Um conflito entre métodos padrão exige que SmartLamp sobrescreva as descrições correspondentes de Lamp e Speaker.
Constante de enumeração
Uma constante de enumeração é um valor nomeado declarado em um enum, com uma única instância que pode conter dados e fornecer comportamento específico da constante.
A constante de enumeração CupSize.LARGE pode armazenar uma capacidade de 500 mL e ser comparada por igualdade de identidade.
Constante de interface
Uma constante de interface é um campo implicitamente declarado public, static e final, que pertence à interface, não a objetos individuais.
A constante de interface Chargeable.FULL pode conter 100 enquanto cada dispositivo armazena seu próprio campo variável de bateria.
Construtor canônico
Um construtor canônico é um construtor de record cujos parâmetros correspondem à lista de componentes e inicializam os campos dos componentes do record.
O construtor canônico de Point(int x, int y) aceita a coordenada x seguida da coordenada y.
Construtor compacto
Um construtor compacto é um corpo de construtor de record escrito sem repetir seus parâmetros, com atribuição automática aos componentes posteriormente.
Um construtor compacto pode remover os espaços de um nome recebido antes que esse valor normalizado seja atribuído ao campo final do componente.
Construtor padrão
Um construtor padrão é um construtor sem argumentos que Java fornece quando uma classe não declara nenhum, com uma chamada a super() em seu corpo.
Acrescentar Person(String name) remove o construtor padrão do qual uma subclasse pode ter dependido.
Contrato de igualdade
O contrato de igualdade exige comparações reflexivas, simétricas, transitivas e consistentes, com todo objeto não nulo considerado diferente de null.
O contrato de igualdade não permite que um livro aceite um ebook quando o ebook rejeita esse mesmo livro na comparação inversa.
Conversão para subtipo
Conversão para subtipo trata uma referência como um tipo mais específico após uma verificação de compatibilidade em tempo de execução, sem converter nem substituir seu objeto.
Converter uma referência de pessoa para o subtipo estudante funciona somente quando o objeto referenciado já tem o tipo de estudante.
Conversão para supertipo
Conversão para supertipo trata uma referência como um tipo de superclasse ou de interface implementada, ampliando sua visão com segurança sem alterar o próprio objeto.
Converter um telefone para o supertipo Chargeable mantém seus dados de bateria, mas expõe apenas as operações disponíveis por meio desse tipo.
Cópia não modificável
Uma cópia não modificável preserva o conteúdo de uma coleção contra alterações estruturais posteriores feitas por meio da referência à coleção original ou à retornada.
Uma cópia não modificável de List.copyOf protege a lista de membros de uma equipe contra acréscimos à lista mutável de quem fez a chamada.
Corpo específico de constante
Um corpo específico de constante é o próprio corpo de classe de uma constante de enumeração, usado para fornecer comportamento específico desse valor nomeado do enum.
Um corpo específico de constante permite que MULTIPLY implemente uma operação de modo diferente da constante ADD do enum.
Corpo flexível de construtor
Um corpo flexível de construtor permite instruções restritas antes de uma invocação explícita de construtor, um recurso padrão no Java 25.
Um corpo flexível de construtor pode rejeitar um parâmetro negativo antes de passá-lo ao construtor da superclasse.

E

Empate na ordenação
Um empate na ordenação ocorre quando compareTo retorna zero, indicando que dois objetos ocupam a mesma posição de comparação segundo a regra de ordenação escolhida.
Um empate na ordenação por tempo de corrida pode ocorrer entre corredores com nomes diferentes, mesmo quando seus records são diferentes.
Estado compartilhado
Estado compartilhado em uma superclasse são os dados comuns de cada objeto que subclasses relacionadas obtêm como parte da porção de classe base de cada objeto.
O nome e a posição de uma forma são estado compartilhado quando uma superclasse abstrata os define para toda forma.
Estouro de inteiro
Estouro de inteiro ocorre quando um cálculo inteiro excede o intervalo representável, podendo fazer uma subtração informar um sinal de ordenação incorreto.
O estouro de inteiro pode prejudicar uma comparação baseada em subtração; Integer.compare evita esse erro de comparação.
Exceção ClassCastException
Uma ClassCastException é uma falha em tempo de execução causada por uma conversão explícita de referência cujo objeto não nulo é incompatível com o tipo de destino solicitado.
Uma ClassCastException ocorre quando um objeto Animal comum é convertido explicitamente para sua subclasse mais específica Fox.
Expressão switch
Uma expressão switch escolhe um resultado entre casos correspondentes e deve cobrir as entradas permitidas, com constantes de enumeração permitindo casos concisos.
Uma expressão switch que lista as três constantes de um enum de tamanho pode produzir “S”, “M” ou “L” sem um ramo default.

H

Herança
Herança é uma relação na qual uma subclasse se baseia em uma superclasse e oferece as operações disponíveis por meio dessa superclasse.
A herança permite que Student reutilize Person.getName() enquanto acrescenta seus próprios créditos e comportamento de estudo.
Herança simples
Herança simples significa que uma classe Java tem no máximo uma superclasse direta, embora possa pertencer a uma cadeia mais longa de subclasses.
A herança simples impede que uma classe estenda tanto Student quanto Teacher como superclasses diretas.

I

Identidade
Identidade significa que referências apontam para o mesmo objeto, independentemente de objetos separados conterem ou não valores iguais.
Dois livros construídos separadamente podem ter códigos iguais, enquanto suas identidades distintas fazem a == b ser falso.
Igualdade por valor
Igualdade por valor considera objetos iguais quando seus dados selecionados correspondem, mesmo que as referências apontem para instâncias distintas de objetos.
A igualdade por valor pode tornar iguais dois livros criados separadamente quando seus códigos de catálogo e suas edições correspondem.
Implementação
Uma implementação é código funcional que cumpre um contrato de método, fornecido por uma classe ou herdado por meio de uma declaração de método adequada.
Uma implementação pública herdada de Entity pode cumprir Named.name() para uma subclasse Asset.
Implementação parcial
Uma implementação parcial fornece parte do comportamento de uma interface em uma classe abstrata, deixando que subclasses concretas completem as operações obrigatórias.
Uma implementação parcial de Storable pode compartilhar a validação de caminho enquanto classes concretas fornecem o texto salvo.
Imutabilidade superficial
Imutabilidade superficial fixa as referências ou valores dos próprios campos de um objeto, mas permite que objetos mutáveis acessados por essas referências mudem.
A imutabilidade superficial permite que um record mantenha a mesma referência de lista mesmo quando outra referência acrescenta um nome a essa lista.
Inicializador de campo
Um inicializador de campo é uma expressão na declaração de um campo que atribui seu valor inicial quando a etapa de inicialização desse campo é executada.
Um inicializador de campo de instância em uma subclasse é executado após o retorno do construtor da superclasse desse objeto.
Interface
Uma interface é um contrato de tipo nomeado que permite que classes forneçam uma capacidade comum sem compartilhar uma superclasse com estado.
A interface Chargeable permite que um telefone e uma scooter ofereçam as mesmas chamadas enquanto carregam em ritmos diferentes.
Interface Comparable
Comparable é a interface Java pela qual um tipo define a ordenação natural comparando uma instância com outra por meio de compareTo.
Um corredor implementa Comparable<Runner> para que um método de ordenação possa organizar corredores por tempo de chegada.
Interface mais específica
Uma interface mais específica estende outra interface e tem prioridade quando ambas fornecem um método padrão correspondente para uma classe que as implementa.
Uma interface mais específica Detailed fornece o name() escolhido quando estende Basic e substitui seu método padrão.
Invocação do construtor da superclasse
Uma invocação do construtor da superclasse usa super(...) para passar argumentos a um construtor da superclasse direta para o objeto atual.
Uma invocação do construtor da superclasse passa o nome de um estudante a Person, responsável pelo campo privado de nome.

M

Método abstrato
Um método abstrato declara uma operação obrigatória sem corpo, deixando que subclasses concretas forneçam ou herdem sua implementação.
O método abstrato area() impede que uma forma concreta incompleta use silenciosamente um método provisório que retorna área zero.
Método concreto
Um método concreto tem um corpo de implementação, permitindo que quem o chama execute seu código em vez de depender de uma subclasse para completar uma declaração.
O método concreto Shape.describe() pode reutilizar o nome e chamar a implementação de area() de cada subclasse.
Método de acesso
Um método de acesso retorna um valor armazenado; um método de acesso gerado de record usa o nome exato de seu componente sem o prefixo get.
O método de acesso de um componente de record count é count(), portanto getCount() não está disponível a menos que seja declarado explicitamente.
Método estático de interface
Um método estático de interface é um método que pertence à própria interface, é chamado por meio de seu nome e não é herdado pelas classes que a implementam.
O método estático de interface Chargeable.clamp(130) pode retornar 100 sem criar um objeto de dispositivo.
Método final
Um método final é um método cuja implementação não pode ser sobrescrita por uma subclasse, preservando essa implementação nas classes descendentes.
Um método final para calcular um valor verificado impede que uma subclasse substitua esse cálculo.
Método padrão
Um método padrão é um método público de instância de interface com corpo, que as classes que a implementam herdam e podem substituir por uma sobrescrita.
Um método padrão needsCharge() pode usar batteryPercent() sem conhecer a organização dos campos privados de nenhum dispositivo.
Método privado de interface
Um método privado de interface é um método auxiliar com corpo visível apenas ao código dentro dessa interface, permitindo que seus métodos funcionais compartilhem código.
Um método privado de interface pode eliminar verificações duplicadas de dois métodos padrão sem se tornar parte do contrato público.

O

Object
Object é a classe raiz da hierarquia de classes do Java, fornecendo métodos comuns herdados direta ou indiretamente por classes comuns.
Uma variável Object pode conter um estudante ou um vetor, e ambos oferecem os métodos que todo objeto possui.
Objeto receptor
Um objeto receptor é o objeto no qual um método de instância é invocado e cuja classe real orienta o despacho dinâmico da assinatura de método selecionada.
Em shape.area(), o objeto receptor é o objeto de forma, mesmo quando a variável tem um tipo declarado abstrato.
Ocultação de campo
Ocultação de campo ocorre quando uma subclasse declara um campo com o mesmo nome de um campo herdado, deixando campos separados selecionados pelo tipo declarado.
A ocultação de campo pode fazer p.label ler o campo da classe pai enquanto p.show() executa uma sobrescrita da classe filha que usa outro rótulo.
Ocultação de método
Ocultação de método ocorre quando uma subclasse declara um método estático com a mesma assinatura de um método estático disponível em sua superclasse.
A ocultação de método faz com que o tipo em tempo de compilação selecione um método estático mesmo quando uma referência contém um objeto de subclasse.
Operador instanceof
O operador instanceof testa se uma referência designa uma instância de determinado tipo ou subtipo, retornando falso quando a referência é nula.
Um teste instanceof permite que o código verifique se uma pessoa tem o tipo de estudante antes de usar um método exclusivo de estudante.
Ordem natural
Uma ordem natural é a ordenação padrão definida pelo método de comparação de um tipo, usada por operações de ordenação que não solicitam uma ordem alternativa.
A ordem natural de um corredor pode colocar tempos menores de chegada antes de tempos maiores quando uma lista é ordenada.

P

Polimorfismo
Polimorfismo permite que uma referência geral trabalhe com diferentes subtipos por meio de operações compartilhadas, com cada objeto fornecendo seu próprio comportamento.
O polimorfismo permite que um laço chame area() em círculos, retângulos e triângulos sem ramos separados por tipo.
Posição ordinal
Uma posição ordinal é a posição de declaração de uma constante de enumeração, contada a partir de zero, que pode mudar quando constantes são inseridas antes desse valor.
Em um enum declarado como LOW, HIGH, a posição ordinal de HIGH muda de 1 para 2 quando MID é inserido antes dela.

R

Registro
Um record é uma classe Java concisa para um grupo fixo de valores de componentes, com comportamento gerado de construção, acesso, igualdade, cálculo de código de dispersão e representação textual.
Um record Point(int x, int y) agrupa duas coordenadas e fornece igualdade baseada nos componentes sem métodos escritos à mão.
Representação textual
Uma representação textual é a cadeia de caracteres retornada para um objeto por toString(), usada quando o objeto é exibido ou unido a outro texto.
A representação textual de um estudante pode mostrar nome e créditos no lugar do texto padrão com classe e código de dispersão.

S

Simetria
Simetria na igualdade exige que inverter dois objetos em uma comparação de igualdade produza o mesmo resultado nas duas direções.
A simetria falha se book.equals(ebook) é verdadeiro, mas ebook.equals(book) é falso para esses mesmos objetos.
Sobrecarga
Sobrecarga declara métodos com o mesmo nome, mas listas de parâmetros diferentes, permitindo que o compilador escolha uma assinatura aplicável.
A sobrecarga pode acrescentar print(String text) ao lado de print(int number) sem substituir nenhum dos métodos.
Sobrescrita
Sobrescrita fornece uma implementação de subclasse para um método de instância herdado com o mesmo nome e tipos de parâmetros correspondentes.
Sobrescrever yearlyPay() permite que um gerente acrescente um bônus enquanto quem faz chamadas continua usando a operação de funcionário.
Subclasse
Uma subclasse é uma classe declarada para estender outra, acrescentando estado ou comportamento enquanto continua podendo ser usada como esse tipo mais geral.
Uma subclasse GraduateStudent pode se basear em Student e ainda ser usada onde se espera uma Person.
Superclasse
Uma superclasse é uma classe cujos membros acessíveis são herdados por uma subclasse, formando o tipo mais geral na relação entre as classes.
Person é a superclasse de Student, portanto um método que aceita uma pessoa também pode receber um estudante.
Superinterface
Uma superinterface é uma interface estendida por outra interface, cujas operações obrigatórias passam a fazer parte do contrato da interface que a estende.
Chargeable é uma superinterface de SmartDevice quando esta a estende junto com Connectable.

T

Tipo de interface
Um tipo de interface é um tipo usado para referências, parâmetros ou resultados para expor uma capacidade enquanto permite diferentes classes que o implementam.
Usar List como tipo de interface permite que um método aceite tanto listas baseadas em vetor quanto listas encadeadas.
Tipo de retorno covariante
Um tipo de retorno covariante é um subtipo usado como tipo de retorno de referência de uma sobrescrita no lugar do tipo de retorno original, mais geral.
Um tipo de retorno covariante permite que uma sobrescrita retorne String quando o método da superclasse retorna Object.
Tipo declarado
O tipo declarado é o tipo estático atribuído a uma variável no código-fonte, que determina quais operações o compilador permite por meio dela.
Em Person p = new Student(...), o tipo declarado é Person, portanto métodos exclusivos de estudante não estão disponíveis diretamente.
Tipo enumerado
Um tipo enumerado é um tipo Java que define um conjunto fixo de constantes nomeadas, impedindo que quem faz chamadas as substitua por cadeias de caracteres ou números arbitrários.
Um enum para estados de entrega pode restringir um pedido aos valores nomeados NEW, SENT e DONE.
Tipo real
O tipo real é a classe do objeto para o qual uma referência aponta, fixada quando esse objeto é criado e usada na seleção de sobrescritas de instância.
Uma referência Person pode apontar para um estudante cujo tipo real continua sendo Student após a conversão para supertipo.
Transitividade
Transitividade na igualdade exige que um objeto igual a um segundo objeto, e esse segundo igual a um terceiro, também seja igual ao terceiro.
A transitividade faz com que três livros com o mesmo código e a mesma edição, campos que definem a igualdade, concordem nas comparações entre pares.

V

Variável de padrão
Uma variável de padrão é uma variável local tipada introduzida por um padrão de tipo bem-sucedido, disponível onde se sabe que essa correspondência é válida.
A variável de padrão student em person instanceof Student student expõe com segurança o método de identificação do objeto correspondente.
Vinculação dinâmica
Vinculação dinâmica seleciona uma implementação sobrescrita de método de instância em tempo de execução usando a classe real do objeto receptor.
A vinculação dinâmica permite que um hello() herdado que chama who() chegue a B.who() quando o objeto receptor pertence à classe B.

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