Desafio Java

Ponto imutável com cópia defensiva

Enunciado

Crie uma classe imutável Ponto com coordenadas x e y (private final double). Ela deve ter um método mover(double dx, double dy) que retorna um novo Ponto em vez de alterar o atual. Adicione também um método distanciaA(Ponto outro) que calcula a distância euclidiana.

Requisitos

  • x e y são private final double.
  • Não existe setter; mover retorna um novo Ponto.
  • distanciaA(Ponto outro) usa Math.hypot ou Math.sqrt.
  • O main cria um ponto, gera outro via mover e imprime as coordenadas e a distância.

Código inicial

public class Ponto {
    // TODO: atributos private final

    public Ponto(double x, double y) {
        // TODO: atribua
    }

    public double getX() { return x; }
    public double getY() { return y; }

    // TODO: mover e distanciaA

    public static void main(String[] args) {
        // TODO: demonstre
    }
}

Saída esperada

p1 = (0.0, 0.0)
p2 = (3.0, 4.0)
distancia = 5.0
Ver dica

Math.hypot(dx, dy) calcula a hipotenusa sem risco de overflow e já é bem precisa.

Mostrar solução
public class Ponto {
    private final double x; // final: imutável após o construtor
    private final double y;

    public Ponto(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX() { return x; }
    public double getY() { return y; }

    // Não altera this; devolve um novo Ponto
    public Ponto mover(double dx, double dy) {
        return new Ponto(this.x + dx, this.y + dy);
    }

    // Distância euclidiana até outro ponto
    public double distanciaA(Ponto outro) {
        return Math.hypot(this.x - outro.x, this.y - outro.y);
    }

    @Override
    public String toString() {
        return "(" + x + ", " + y + ")";
    }

    public static void main(String[] args) {
        Ponto p1 = new Ponto(0, 0);
        Ponto p2 = p1.mover(3, 4); // p1 continua (0,0)
        System.out.println("p1 = " + p1);
        System.out.println("p2 = " + p2);
        System.out.println("distancia = " + p1.distanciaA(p2));
    }
}

Passo a passo

  1. x e y são private final, então só podem ser atribuídos no construtor e nunca mais mudam.
  2. Não existe setter: a única forma de "mudar" um ponto é criar outro.
  3. mover(dx, dy) calcula x + dx e y + dy e devolve um new Ponto, deixando o original intacto.
  4. distanciaA(outro) usa Math.hypot para calcular a hipotenusa entre as diferenças de coordenadas.
  5. Sobrescrevemos toString só para deixar a saída legível.
  6. No main, p1 permanece (0.0, 0.0) mesmo depois de p1.mover(3, 4), porque o resultado foi guardado em p2.

Por que funciona

Imutabilidade combina private final, ausência de setters e operações que retornam novos objetos. Como o estado nunca muda, não há risco de um método alterar o ponto "por baixo dos panos" de outro código, e a classe é naturalmente segura para uso concorrente. Encapsular aqui significa não expor x e y para escrita, apenas para leitura.

Erros comuns

  • Declarar private double x; sem final e criar um setter: a classe deixa de ser imutável.
  • Fazer mover alterar this.x += dx; e retornar this: o objeto original muda, quebrando a promessa de imutabilidade.
  • Expor arrays ou listas internas diretamente em getters: mesmo com final, o conteúdo pode ser alterado. Nesse caso, devolva uma cópia (clone() ou List.copyOf(...)).
  • Esquecer de marcar a classe como final se ela puder ser herdada: uma subclasse poderia adicionar estado mutável.

Outra forma de resolver

Em vez de mover, você pode usar um método estático fábrica que descreve a operação:

public static Ponto transladado(Ponto p, double dx, double dy) {
    return new Ponto(p.x + dx, p.y + dy);
}

É preferível quando a operação não pertence conceitualmente a um único ponto, mas sim a uma transformação sobre pontos.

Saída esperada

p1 = (0.0, 0.0)
p2 = (3.0, 4.0)
distancia = 5.0