Skip to the content.

☕ Java: Interfaces

📢 Aviso

❔ O que é uma Interface?

Interface é um tipo que define um conjunto de operações que uma classe deve implementar. A interface estabelece um contrato que a classe deve cumprir.

Uma interface define um conjunto de métodos abstratos que as classes concretas devem fornecer. Ela especifica o quê uma classe deve fazer, mas não como ela deve fazer.

interface Forma {
    double area();
    double perimetro();
}

Pra quê interfaces?

🚗 Problema Exemplo: Locadora de Veículos

Uma locadora brasileira de carros cobra um valor por hora para locações de até 12 horas. Porém, se a duração da locação ultrapassar 12 horas, a locação será cobrada com base em um valor diário. Além do valor da locação, é acrescido no preço o valor do imposto conforme regras do país que, no caso do Brasil, é 20% para valores até 100.00, ou 15% para valores acima de 100.00.

Fazer um programa que lê os dados da locação (modelo do carro, instante inicial e final da locação), bem como o valor por hora e o valor diário de locação. O programa deve então gerar a nota de pagamento (contendo valor da locação, valor do imposto e valor total do pagamento) e informar os dados na tela.

Exemplo 1:

Dados da locação: Modelo do carro: Civic Retirada (dd/MM/yyyy hh:mm): 25/06/2018 10:30 Devolução (dd/MM/yyyy hh:mm): 25/06/2018 14:40 Preço por hora: 10.00 Preço por dia: 130.00

FATURA: Pagamento básico: 50.00 Imposto: 10.00 Pagamento total: 60.00

Cálculos: Duração = (25/06/2018 14:40) - (25/06/2018 10:30) = 4:10 ≈ 5 horas Pagamento básico = 5 * 10.00 = 50.00 Imposto = 50.00 * 20% = 50.00 * 0.2 = 10.00

Exemplo 2:

Dados da locação: Modelo do carro: Civic Retirada (dd/MM/yyyy hh:mm): 25/06/2018 10:30 Devolução (dd/MM/yyyy hh:mm): 27/06/2018 11:40 Preço por hora: 10.00 Preço por dia: 130.00

FATURA: Pagamento básico: 390.00 Imposto: 58.50 Pagamento total: 448.50

Cálculos: Duração = (27/06/2018 11:40) - (25/06/2018 10:30) = 2 dias + 1:10 ≈ 3 dias Pagamento básico = 3 * 130.00 = 390.00 Imposto = 390.00 * 15% = 390.00 * 0.15 = 58.50

🏗️ Solução do Problema (Estrutura de Camadas)

Recordando o capítulo de Composição, um sistema pode ser organizado em camadas:

Diagrama de Entidades (Exemplo Genérico)

classDiagram
    Order --|> Client : 1 "client"
    Order --|> OrderItem : "*" "items"
    OrderItem --|> Product : 1 "product"
    Order : +Date moment
    Order : +OrderStatus status
    Order : +addItem(OrderItem item) void
    Order : +removeItem(OrderItem item) void
    Order : +total() Double
    Client : -String name
    Client : -String email
    Client : -Date birthDate
    OrderItem : -Integer quantity
    OrderItem : -Double price
    OrderItem : +subTotal() Double
    Product : -String name
    Product : -Double price
    OrderStatus : <<enumeration>>
    OrderStatus : PENDING_PAYMENT
    OrderStatus : PROCESSING
    OrderStatus : SHIPPED
    OrderStatus : DELIVERED

Diagrama de Serviços (Exemplo Genérico)

classDiagram
    <<interface>> CrudRepository
    CrudRepository : +save(T obj) T
    CrudRepository : +delete(T obj) void
    CrudRepository : +findById(ID id) T
    CrudRepository : +findAll() List~T~
    OrderRepository --|> CrudRepository
    OrderRepository : +findByDate(Date minDate, Date maxDate) List~Order~
    OrderService ..> OrderRepository : -repository
    OrderService : +save(Order order) void
    OrderService : +search(Date minDate, Date maxDate) List~Order~
    AuthService ..> EmailService : -emailService
    AuthService : +getToken(String username, String password) String
    AuthService : +refreshToken(String token) String
    AuthService : +sendNewPassword(String email) void
    EmailService : +sendEmail(String sender, String recipient, String subject, String message) void

Design da Camada de Domínio (Problema da Locadora)

classDiagram
    CarRental "1" -- "1" Vehicle : -vehicle
    CarRental "1" -- "0..1" Invoice : -invoice
    Vehicle : -String model
    CarRental : -Date start
    CarRental : -Date finish
    Invoice : -Double basicPayment
    Invoice : -Double tax
    Invoice : +Double totalPayment()

Design da Camada de Serviço (Sem Interface)

Nesta abordagem, ServicoAluguel depende diretamente de uma classe concreta ServicoImpostoBrasil.

classDiagram
    ServicoAluguel --> ServicoImpostoBrasil
    ServicoAluguel : -Double precoPorHora
    ServicoAluguel : -Double precoPorDia
    ServicoAluguel : +processarFatura(AluguelCarro aluguelCarro) void
    ServicoImpostoBrasil : +imposto(Double quantia) Double

Isso leva a um acoplamento forte:

// Exemplo de acoplamento forte
class ServicoAluguel {
    // ...
    private ServicoImpostoBrasil servicoImposto; // Dependência concreta
    // ...
}

Design da Camada de Serviço (Com Interface)

Aqui, ServicoAluguel depende de uma abstração (interface ServicoImposto). ServicoImpostoBrasil é uma implementação dessa interface.

classDiagram
    ServicoAluguel --> ServicoImposto
    ServicoImpostoBrasil ..|> ServicoImposto : implements
    ServicoAluguel : -Double precoPorHora
    ServicoAluguel : -Double precoPorDia
    ServicoAluguel : +processarFatura(AluguelCarro aluguelCarro) void
    <<interface>> ServicoImposto
    ServicoImposto : +imposto(Double quantia) Double
    ServicoImpostoBrasil : +imposto(Double quantia) Double

Isso promove um acoplamento fraco:

// Exemplo de acoplamento fraco
class ServicoAluguel {
    // ...
    private ServicoImposto servicoImposto; // Dependência via interface
    // ...
}

🔄 Inversão de Controle e Injeção de Dependência

Inversão de Controle (IoC)

É um padrão de desenvolvimento que consiste em retirar da classe a responsabilidade de instanciar suas dependências. Em vez da classe controlar a criação e o ciclo de vida de seus objetos dependentes, essa responsabilidade é delegada a um contêiner ou a um componente externo.

Injeção de Dependência (ID)

É uma forma de realizar a inversão de controle: um componente externo instancia a dependência, que é então injetada no objeto “pai”. A injeção de dependência permite que o acoplamento entre os componentes seja reduzido.

Pode ser implementada de várias formas:

Injeção de Dependência por Meio de Construtor

package programa;

import servicos.ServicoAluguel;
import servicos.ServicoImpostoBrasil; // Implementação concreta

// Supondo que as classes estão em pacotes apropriados
// import entidades.AluguelCarro;
// import java.time.LocalDateTime;
// import java.time.format.DateTimeFormatter;
// import java.util.Locale;
// import java.util.Scanner;

public class Programa {

    public static void main(String[] args) {
        // ... leitura de dados ...
        double precoPorHora = 10.0;
        double precoPorDia = 130.0;

        // A dependência ServicoImpostoBrasil é explicitamente instanciada
        // e injetada no construtor de ServicoAluguel.
        // Ocorre um UPCASTING: ServicoImpostoBrasil é um ServicoImposto.
        ServicoAluguel servicoAluguel = new ServicoAluguel(precoPorHora, precoPorDia, new ServicoImpostoBrasil());

        // ... resto do código para processar a fatura ...
    }
}
package servicos;

// import entidades.AluguelCarro;
// import entidades.Fatura;
// import java.time.Duration;

// Interface para o serviço de imposto
interface ServicoImposto {
    double imposto(double quantia);
}

// Implementação brasileira do serviço de imposto
class ServicoImpostoBrasil implements ServicoImposto {
    public double imposto(double quantia) {
        if (quantia <= 100.0) {
            return quantia * 0.20;
        } else {
            return quantia * 0.15;
        }
    }
}

public class ServicoAluguel {
    private double precoPorHora;
    private double precoPorDia;

    private ServicoImposto servicoImposto; // Depende da interface

    // A dependência é injetada via construtor
    public ServicoAluguel(double precoPorHora, double precoPorDia, ServicoImposto servicoImposto) {
        this.precoPorHora = precoPorHora;
        this.precoPorDia = precoPorDia;
        this.servicoImposto = servicoImposto;
    }

    /*
    public void processarFatura(AluguelCarro aluguelCarro) {
        long minutos = Duration.between(aluguelCarro.getInicio(), aluguelCarro.getFim()).toMinutes();
        double horas = (double) minutos / 60.0;
        double pagamentoBasico;

        if (horas <= 12.0) {
            pagamentoBasico = Math.ceil(horas) * precoPorHora;
        } else {
            pagamentoBasico = Math.ceil(horas / 24.0) * precoPorDia;
        }

        double imposto = servicoImposto.imposto(pagamentoBasico);
        aluguelCarro.setFatura(new Fatura(pagamentoBasico, imposto));
    }
    */
}

📝 Exercício de Fixação: Processamento de Contratos

Uma empresa deseja automatizar o processamento de seus contratos. O processamento de um contrato consiste em gerar as parcelas a serem pagas para aquele contrato, com base no número de meses desejado.

A empresa utiliza um serviço de pagamento online para realizar o pagamento das parcelas. Os serviços de pagamento online tipicamente cobram um juro mensal, bem como uma taxa por pagamento. Por enquanto, o serviço contratado pela empresa é o do Paypal, que aplica juros simples de 1% a cada parcela, mais uma taxa de pagamento de 2%.

Fazer um programa para ler os dados de um contrato (número do contrato, data do contrato, e valor total do contrato). Em seguida, o programa deve ler o número de meses para parcelamento do contrato, e daí gerar os registros de parcelas a serem pagas (data e valor), sendo a primeira parcela a ser paga um mês após a data do contrato, a segunda parcela dois meses após o contrato e assim por diante. Mostrar os dados das parcelas na tela.

Exemplo:

Dados do contrato: Número: 8028 Data (dd/MM/yyyy): 25/06/2018 Valor do contrato: 600.00 Número de parcelas: 3

Parcelas: 25/07/2018 - 206.04 25/08/2018 - 208.08 25/09/2018 - 210.12

Cálculos (1% de juro simples mensal + 2% de taxa de pagamento): Valor base da parcela = 600.00 / 3 = 200.00

Parcela #1: Juro = 200.00 * 1% * 1 = 2.00 Valor com juro = 200.00 + 2.00 = 202.00 Taxa de pagamento = 202.00 * 2% = 4.04 Valor final Parcela #1 = 202.00 + 4.04 = 206.04

Parcela #2: Juro = 200.00 * 1% * 2 = 4.00 Valor com juro = 200.00 + 4.00 = 204.00 Taxa de pagamento = 204.00 * 2% = 4.08 Valor final Parcela #2 = 204.00 + 4.08 = 208.08

Parcela #3: Juro = 200.00 * 1% * 3 = 6.00 Valor com juro = 200.00 + 6.00 = 206.00 Taxa de pagamento = 206.00 * 2% = 4.12 Valor final Parcela #3 = 206.00 + 4.12 = 210.12

Design da Camada de Domínio (Entidades)

classDiagram
    Contrato "1" -- "*" Parcela : -parcelas
    Contrato : -Integer numero
    Contrato : -Date data
    Contrato : -Double valorTotal
    Parcela : -Date dataVencimento
    Parcela : -Double valor

Projeto dos Serviços

classDiagram
    ServicoContrato --> ServicoPagamentoOnline
    ServicoPagamentoPaypal ..|> ServicoPagamentoOnline : implements
    ServicoContrato : +processarContrato(Contrato contrato, Integer meses) void
    <<interface>> ServicoPagamentoOnline
    ServicoPagamentoOnline : +taxaPagamento(Double quantia) Double
    ServicoPagamentoOnline : +juros(Double quantia, Integer meses) Double
    ServicoPagamentoPaypal : +taxaPagamento(Double quantia) Double
    ServicoPagamentoPaypal : +juros(Double quantia, Integer meses) Double

O que o método processarContrato deve fazer? Para cada mês de parcelamento:

  1. Calcular o valor base da parcela (valor total do contrato / número de meses).
  2. Calcular o juro mensal sobre o valor base da parcela (usando o ServicoPagamentoOnline).
  3. Somar o juro ao valor base.
  4. Calcular a taxa de pagamento sobre o valor com juro (usando o ServicoPagamentoOnline).
  5. Somar a taxa de pagamento para obter o valor final da parcela.
  6. Adicionar a nova parcela (com data de vencimento e valor) à lista de parcelas do contrato.

🆚 Herdar vs. Cumprir Contrato (Interface)

Aspectos em comum entre herança (classes) e interfaces:

Diagrama de Herança (Classes):

classDiagram
    Forma <|-- Retangulo
    Forma <|-- Circulo
    Forma : -Cor cor
    Forma : +area() Double
    Retangulo : -Double largura
    Retangulo : -Double altura
    Retangulo : +area() Double
    Circulo : -Double raio
    Circulo : +area() Double

Diagrama de Implementação (Interfaces):

classDiagram
    ServicoImposto <|.. ServicoImpostoBrasil
    ServicoImposto <|.. ServicoImpostoEUA
    <<interface>> ServicoImposto
    ServicoImposto : +imposto(Double quantia) Double
    ServicoImpostoBrasil : +imposto(Double quantia) Double
    ServicoImpostoEUA : +imposto(Double quantia) Double

⚖️ Diferença Fundamental

E se eu precisar implementar Forma como interface, porém também quiser definir uma estrutura comum reutilizável para todas as figuras (ex: um atributo cor)?

Pode-se usar uma combinação: uma interface para o contrato e uma classe abstrata para o reuso comum.

classDiagram
    <<interface>> IForma
    IForma : +area() Double
    AbstractForma ..|> IForma : implements
    AbstractForma <|-- Retangulo
    AbstractForma <|-- Circulo
    AbstractForma : -Cor cor
    Retangulo : -Double largura
    Retangulo : -Double altura
    Retangulo : +area() Double
    Circulo : -Double raio
    Circulo : +area() Double

Neste caso, AbstractForma implementaria IForma e forneceria o atributo cor. Retangulo e Circulo herdariam de AbstractForma e implementariam o método area().

📧 Outro Exemplo: Serviço de Email

classDiagram
    <<interface>> ServicoEmail
    ServicoEmail : +enviarEmail(MensagemEmailSimples mensagem) void
    ServicoEmail : +enviarEmailConfirmacaoPedido(Pedido pedido) void
    AbstractServicoEmail ..|> ServicoEmail
    AbstractServicoEmail <|-- ServicoEmailMock
    AbstractServicoEmail <|-- ServicoEmailSmtp
    AbstractServicoEmail : +prepararMensagemEmailSimplesDePedido(Pedido pedido) MensagemEmailSimples
    AbstractServicoEmail : +enviarEmailConfirmacaoPedido(Pedido pedido) void
    ServicoEmailMock : +enviarEmail(MensagemEmailSimples mensagem) void
    ServicoEmailSmtp : +enviarEmail(MensagemEmailSimples mensagem) void

💎 Herança Múltipla e o Problema do Diamante

A herança múltipla (uma classe herdando de múltiplas superclasses) pode gerar o problema do diamante: uma ambiguidade causada pela existência do mesmo método (com a mesma assinatura) em mais de uma superclasse.

classDiagram
    Dispositivo <|-- Scanner
    Dispositivo <|-- Impressora
    Scanner <|-- DispositivoCombo
    Impressora <|-- DispositivoCombo
    Dispositivo : +processarDoc(String doc) void
    Scanner : +processarDoc(String doc) void
    Scanner : +scan() String
    Impressora : +processarDoc(String doc) void
    Impressora : +print(String doc) void
    DispositivoCombo : // qual processarDoc() herdar?

Se DispositivoCombo herda de Scanner e Impressora, e ambas têm um método processarDoc, qual versão DispositivoCombo usaria?

A herança múltipla de classes não é permitida na maioria das linguagens orientadas a objetos, incluindo Java, justamente para evitar este e outros problemas de complexidade.

Implementando Múltiplas Interfaces

Porém, uma classe pode implementar mais de uma interface.

classDiagram
    Dispositivo : -String numeroSerial
    Dispositivo : +processarDoc(String doc) void
    Dispositivo <|-- DispositivoCombo
    DispositivoCombo ..|> IScanner : implements
    DispositivoCombo ..|> IImpressora : implements
    <<interface>> IScanner
    IScanner : +scan() String
    <<interface>> IImpressora
    IImpressora : +print(String doc) void
    DispositivoCombo : +processarDoc(String doc) void // herdado de Dispositivo
    DispositivoCombo : +scan() String // implementado de IScanner
    DispositivoCombo : +print(String doc) void // implementado de IImpressora

ATENÇÃO: Isso NÃO é herança múltipla de implementação, pois NÃO HÁ REUSO de código das interfaces IScanner e IImpressora (antes do Java 8 com default methods). DispositivoCombo não herda implementação das interfaces, mas sim implementa os métodos definidos por elas (cumpre o contrato). Se as interfaces tivessem métodos default com a mesma assinatura, a classe DispositivoCombo seria obrigada a sobrescrever o método para resolver a ambiguidade.

⚖️ Interface Comparable

A interface java.lang.Comparable<T> é usada para impor uma ordem natural aos objetos de uma classe que a implementa.

public interface Comparable<T> {
    int compareTo(T outroObjeto);
}

O método compareTo(T outroObjeto) compara este objeto com o outroObjeto especificado. Retorna:

Classes como String, Integer, Double, Date já implementam Comparable. Isso permite que listas ou arrays desses objetos sejam facilmente ordenados usando Collections.sort() ou Arrays.sort().

Exemplo com Strings:

System.out.println("maria".compareTo("alex"));   // Saída: 12 (m vem depois de a)
System.out.println("alex".compareTo("maria"));   // Saída: -12 (a vem antes de m)
System.out.println("maria".compareTo("maria")); // Saída: 0 (iguais)

📂 Problema Motivador: Ordenar Nomes de um Arquivo

Faça um programa para ler um arquivo contendo nomes de pessoas (um nome por linha), armazenando-os em uma lista. Depois, ordenar os dados dessa lista e mostrá-los ordenadamente na tela. Nota: o caminho do arquivo pode ser informado “hardcoded”.

Arquivo entrada.txt (exemplo):

Maria Brown
Alex Green
Bob Grey
Anna White
Alex Black
Eduardo Rose
Willian Red
Marta Blue
Alex Brown

Código Java: Este código funcionará em qualquer IDE Java como VS Code ou IntelliJ IDEA. Certifique-se de que o arquivo entrada.txt exista no caminho especificado ou ajuste o caminho.

package aplicacao;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections; // Para o método sort
import java.util.List;

public class Programa {

    public static void main(String[] args) {
        List<String> listaNomes = new ArrayList<>();
        String caminhoArquivo = "C:\\temp\\entrada.txt"; // Adapte este caminho

        try (BufferedReader br = new BufferedReader(new FileReader(caminhoArquivo))) {
            String nome = br.readLine();
            while (nome != null) {
                listaNomes.add(nome);
                nome = br.readLine();
            }

            Collections.sort(listaNomes); // String implementa Comparable

            for (String s : listaNomes) {
                System.out.println(s);
            }
        } catch (IOException e) {
            System.out.println("Erro: " + e.getMessage());
        }
    }
}

Saída Esperada (ordenada):

Alex Black
Alex Brown
Alex Green
Anna White
Bob Grey
Eduardo Rose
Maria Brown
Marta Blue
Willian Red

🧑‍💼 Outro Problema: Ordenar Funcionários de um CSV

Faça um programa para ler um arquivo contendo funcionários (nome e salário) no formato .csv, armazenando-os em uma lista. Depois, ordenar a lista por nome e mostrar o resultado na tela. Nota: o caminho do arquivo pode ser informado “hardcoded”.

Arquivo funcionarios.csv (exemplo):

Maria Brown,4300.00
Alex Green,3100.00
Bob Grey,3100.00
Anna White,3500.00
Alex Black,2450.00
Eduardo Rose,4390.00
Willian Red,2900.00
Marta Blue,6100.00
Alex Brown,5000.00

Para ordenar uma lista de objetos Funcionario por nome, a classe Funcionario deve implementar Comparable<Funcionario>.

Classe Funcionario:

package entidades;

public class Funcionario implements Comparable<Funcionario> {
    private String nome;
    private Double salario;

    public Funcionario(String nome, Double salario) {
        this.nome = nome;
        this.salario = salario;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public Double getSalario() {
        return salario;
    }

    public void setSalario(Double salario) {
        this.salario = salario;
    }

    // Implementação do método compareTo para ordenar por nome
    @Override
    public int compareTo(Funcionario outro) {
        return this.nome.compareTo(outro.getNome());
    }

    @Override
    public String toString() {
        return nome + "," + String.format("%.2f", salario);
    }
}

Programa Principal:

package aplicacao;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import entidades.Funcionario; // Importa a classe Funcionario

public class ProgramaLeituraFuncionarios {

    public static void main(String[] args) {
        List<Funcionario> listaFuncionarios = new ArrayList<>();
        String caminhoArquivo = "C:\\temp\\funcionarios.csv"; // Adapte este caminho

        try (BufferedReader br = new BufferedReader(new FileReader(caminhoArquivo))) {
            String linhaCsvFuncionario = br.readLine();
            while (linhaCsvFuncionario != null) {
                String[] campos = linhaCsvFuncionario.split(",");
                String nome = campos[0];
                double salario = Double.parseDouble(campos[1]);
                listaFuncionarios.add(new Funcionario(nome, salario));
                linhaCsvFuncionario = br.readLine();
            }

            Collections.sort(listaFuncionarios); // Funcionario agora é Comparable

            for (Funcionario func : listaFuncionarios) {
                System.out.println(func); // Usa o toString() de Funcionario
            }
        } catch (IOException e) {
            System.out.println("Erro: " + e.getMessage());
        }
    }
}

🆕 Default Methods (Métodos Padrão)

A partir do Java 8, interfaces podem conter métodos concretos, chamados de default methods (ou defender methods).

Intenção básica: Prover implementação padrão para métodos, de modo a evitar:

  1. Repetição de implementação em toda classe que implemente a interface.
  2. A necessidade de se criar classes abstratas apenas para prover reuso de implementação de alguns métodos.

Outras Vantagens:

🏦 Problema Exemplo: Serviço de Juros

Fazer um programa para ler uma quantia e a duração em meses de um empréstimo. Informar o valor a ser pago depois de decorrido o prazo do empréstimo, conforme regras de juros do Brasil. A regra de cálculo de juros do Brasil é juro composto padrão de 2% ao mês.

Exemplo: Quantia: 200.00 Meses: 3 Pagamento após 3 meses: 212.2416 (aproximadamente 212.24)

Cálculos: Pagamento = Quantia * (1 + taxaDeJuros / 100)<sup>Meses</sup> Pagamento = 200.00 * (1 + 2/100)<sup>3</sup> = 200.00 * (1.02)<sup>3</sup> = 200.00 * 1.061208 = 212.2416

Design Inicial (Sem Default Method):

classDiagram
    ServicoJurosBrasil : -double taxaDeJuros
    ServicoJurosBrasil : +pagamento(double quantia, int meses) double

Se quisermos adicionar um serviço de juros de outro país (EUA, com 1% ao mês):

classDiagram
    ServicoJurosEUA : -double taxaDeJuros
    ServicoJurosEUA : +pagamento(double quantia, int meses) double

A lógica do método pagamento é a mesma, apenas a taxaDeJuros muda. Podemos usar uma interface com um método getTaxaDeJuros() e um método pagamento().

classDiagram
    <<interface>> ServicoJuros
    ServicoJuros : +getTaxaDeJuros() double
    ServicoJuros : +pagamento(double quantia, int meses) double
    ServicoJurosBrasil ..|> ServicoJuros
    ServicoJurosEUA ..|> ServicoJuros

Neste caso, tanto ServicoJurosBrasil quanto ServicoJurosEUA teriam que implementar o método pagamento de forma idêntica.

Utilizando Default Method na Interface ServicoJuros

Com default methods, podemos fornecer uma implementação padrão para o método pagamento diretamente na interface.

package servicos;

// Interface com default method
interface ServicoJuros {
    double getTaxaDeJuros(); // Método abstrato a ser implementado

    // Default method com implementação padrão
    default double pagamento(double quantia, int meses) {
        if (meses < 1) {
            throw new IllegalArgumentException("Meses deve ser maior ou igual a 1");
        }
        return quantia * Math.pow(1.0 + getTaxaDeJuros() / 100.0, meses);
    }
}

// Implementação para o Brasil
class ServicoJurosBrasil implements ServicoJuros {
    private double taxaDeJuros;

    public ServicoJurosBrasil(double taxaDeJuros) {
        this.taxaDeJuros = taxaDeJuros;
    }

    @Override
    public double getTaxaDeJuros() {
        return taxaDeJuros;
    }
}

// Implementação para os EUA
class ServicoJurosEUA implements ServicoJuros {
    private double taxaDeJuros;

    public ServicoJurosEUA(double taxaDeJuros) {
        this.taxaDeJuros = taxaDeJuros;
    }

    @Override
    public double getTaxaDeJuros() {
        return taxaDeJuros;
    }
}

public class ProgramaJuros {
    public static void main(String[] args) {
        ServicoJuros sjBrasil = new ServicoJurosBrasil(2.0); // 2%
        ServicoJuros sjEUA = new ServicoJurosEUA(1.0);    // 1%

        double quantia = 200.00;
        int meses = 3;

        System.out.printf("Pagamento Brasil (quantia: %.2f, meses: %d): %.2f%n",
                quantia, meses, sjBrasil.pagamento(quantia, meses));

        System.out.printf("Pagamento EUA (quantia: %.2f, meses: %d): %.2f%n",
                quantia, meses, sjEUA.pagamento(quantia, meses));
    }
}

Saída do ProgramaJuros:

Pagamento Brasil (quantia: 200.00, meses: 3): 212.24
Pagamento EUA (quantia: 200.00, meses: 3): 206.06

As classes ServicoJurosBrasil e ServicoJurosEUA só precisam implementar getTaxaDeJuros(), herdando a lógica de pagamento() da interface.

🤔 Considerações Importantes sobre Default Methods

Default methods são um recurso poderoso para a evolução de APIs e para a escrita de código mais conciso e reutilizável, especialmente quando combinados com interfaces funcionais e expressões lambda.


📚


ricardotecpro.github.io