Esercizi Java OOP svolti: Automobile, ContoBancario e Giocatore

Cerchi esercizi svolti di programmazione a oggetti in Java? In questa pagina trovi tre problemi graduati, ispirati alle esercitazioni su classi, attributi, metodi e incapsulamento: Automobile, ContoBancario e Giocatore. Ogni esercizio presenta consegna, progettazione, codice completo, output atteso e possibili estensioni. Puoi copiare ciascun esempio separatamente in Main.java per provarlo.

Prima di iniziare: metodo di lavoro

  • Leggi la consegna e identifica gli attributi necessari.
  • Individua i metodi e specifica cosa fanno e cosa restituiscono.
  • Definisci le condizioni che mantengono lo stato valido.
  • Scrivi la classe, il costruttore e infine il main() per eseguire le prove.
  • Controlla l’output e prova valori limite: zero, quantità negative, capacità esaurita.

Esercizio 1 — Classe Automobile: carburante e autonomia

Consegna: progettare una classe Automobile con modello, litri di carburante e consumo in litri per 100 km. Implementare viaggia(km), che deve impedire viaggi senza carburante sufficiente, e autonomiaResidua(), che calcola i chilometri ancora percorribili. L’oggetto deve essere costruito con valori validi.

Progetto: attributi modello:String, carburante:double, consumoPer100Km:double. Il metodo viaggia restituisce boolean: true se il viaggio viene effettuato, false altrimenti.

class Automobile {
    private final String modello;
    private double carburante;
    private final double consumoPer100Km;

    public Automobile(String modello, double carburante, double consumoPer100Km) {
        if (modello == null || modello.isBlank()
                || carburante < 0 || consumoPer100Km <= 0
                || !Double.isFinite(carburante)
                || !Double.isFinite(consumoPer100Km)) {
            throw new IllegalArgumentException("Dati auto non validi");
        }
        this.modello = modello;
        this.carburante = carburante;
        this.consumoPer100Km = consumoPer100Km;
    }

    public boolean viaggia(double km) {
        if (!Double.isFinite(km) || km <= 0) return false;
        double necessari = km * consumoPer100Km / 100.0;
        if (!Double.isFinite(necessari) || necessari > carburante) return false;
        carburante -= necessari;
        return true;
    }

    public double autonomiaResidua() {
        return carburante * 100.0 / consumoPer100Km;
    }
}

public class Main {
    public static void main(String[] args) {
        Automobile auto = new Automobile("City", 30, 6);
        System.out.println(auto.autonomiaResidua()); // 500.0
        System.out.println(auto.viaggia(100));         // true
        System.out.println(auto.autonomiaResidua()); // 400.0
        System.out.println(auto.viaggia(500));         // false
    }
}

Spiegazione: con 30 litri e consumo 6 L/100 km, l’autonomia è 30 * 100 / 6 = 500 km. Un viaggio di 100 km consuma 6 litri, lasciandone 24: la nuova autonomia è 400 km. Tentare 500 km viene rifiutato e non modifica il carburante.

OperazioneCarburante dopoAutonomia dopo
Costruzione30 L500 km
viaggia(100)24 L400 km
viaggia(500) rifiutato24 L400 km

Estensione: aggiungi il metodo rifornisci(litri) con una capacità massima del serbatoio e controlla che non venga superata.

Esercizio 2 — Classe ContoBancario: deposito e prelievo

Consegna: realizzare un conto corrente con saldo privato. Il cliente può depositare un importo positivo e prelevare soltanto se il saldo è sufficiente. Si richiede anche un metodo per leggere il saldo senza modificarlo.

Scelta progettuale: nell’esempio rappresentiamo gli importi come centesimi interi per evitare gli errori di arrotondamento tipici dei numeri in virgola mobile. Per un’applicazione bancaria reale occorrono requisiti aggiuntivi, gestione delle transazioni e controlli più rigorosi.

class ContoBancario {
    private int saldoCentesimi;

    public ContoBancario(int saldoIniziale) {
        if (saldoIniziale < 0) {
            throw new IllegalArgumentException("Saldo negativo");
        }
        saldoCentesimi = saldoIniziale;
    }

    public boolean deposita(int centesimi) {
        if (centesimi <= 0 || centesimi > Integer.MAX_VALUE - saldoCentesimi) {
            return false;
        }
        saldoCentesimi += centesimi;
        return true;
    }

    public boolean preleva(int centesimi) {
        if (centesimi <= 0 || centesimi > saldoCentesimi) {
            return false;
        }
        saldoCentesimi -= centesimi;
        return true;
    }

    public int getSaldoCentesimi() {
        return saldoCentesimi;
    }
}

public class Main {
    public static void main(String[] args) {
        ContoBancario conto = new ContoBancario(10000); // 100 euro
        System.out.println(conto.deposita(2500));       // true
        System.out.println(conto.preleva(4500));        // true
        System.out.println(conto.preleva(9000));        // false
        System.out.println(conto.getSaldoCentesimi()); // 8000
    }
}

Spiegazione: il conto parte da 100,00 €. Dopo il deposito di 25,00 € e il prelievo di 45,00 € rimangono 80,00 €, rappresentati dal numero intero 8000. Il tentativo di prelevare 90,00 € fallisce e lascia invariato il saldo. Non serve un setter generico setSaldo: il saldo può variare solo attraverso operazioni controllate.

Estensione: aggiungi un contatore delle operazioni completate; incrementalo esclusivamente quando un deposito o un prelievo restituisce true.

Esercizio 3 — Classe Giocatore: punti vita ed esperienza

Consegna: progettare un personaggio di gioco con punti vita compresi fra 0 e 100, livello iniziale 1 ed esperienza. Implementare i metodi subisciDanno, guarisci e aggiungiEsperienza. Ogni 100 punti esperienza si ottiene un livello. Gli attributi devono rimanere validi dopo ogni operazione.

class Giocatore {
    private int puntiVita = 100;
    private int livello = 1;
    private int esperienza = 0;

    public void subisciDanno(int danno) {
        if (danno <= 0) return;
        puntiVita = Math.max(0, puntiVita - danno);
    }

    public void guarisci(int punti) {
        if (punti <= 0) return;
        puntiVita += Math.min(punti, 100 - puntiVita);
    }

    public void aggiungiEsperienza(int punti) {
        if (punti <= 0) return;
        if (punti > Integer.MAX_VALUE - esperienza) {
            throw new IllegalArgumentException("Esperienza troppo grande");
        }
        esperienza += punti;
        while (esperienza >= 100) {
            esperienza -= 100;
            livello++;
        }
    }

    public void mostraStatistiche() {
        System.out.println("Vita: " + puntiVita);
        System.out.println("Livello: " + livello);
        System.out.println("Esperienza: " + esperienza);
    }
}

public class Main {
    public static void main(String[] args) {
        Giocatore g = new Giocatore();
        g.subisciDanno(30);
        g.guarisci(15);
        g.aggiungiEsperienza(250);
        g.mostraStatistiche();
    }
}

Risultato: i punti vita passano da 100 a 70 e poi a 85. I 250 punti esperienza producono due avanzamenti: livello 3 ed esperienza residua 50. Il ciclo while è necessario perché una singola chiamata può superare la soglia più volte.

Estensione: aggiungi isVivo(), che restituisce true quando i punti vita sono maggiori di zero, e impedisci di guarire un giocatore con punti vita pari a zero se le regole del tuo gioco non lo consentono.

Come verificare se hai capito la POO

  • Quali attributi devono essere private? Per quale motivo?
  • In quale punto del codice viene controllata la validità degli oggetti appena creati?
  • Quali metodi modificano lo stato e quali si limitano a osservarlo?
  • Come cambierebbe il programma se due variabili indicassero lo stesso oggetto Giocatore?
  • Quali nuovi casi limite dovresti provare prima di considerare completo un esercizio?

Percorso consigliato per approfondire

Prima di affrontare gli esercizi puoi ripassare classi e oggetti, metodi con parametri e return e incapsulamento e invarianti. Torna all’indice del corso di Programmazione Orientata agli Oggetti in Java per trovare le altre lezioni.

Nota didattica: ogni frammento di codice completo ha una propria classe Main. Esegui gli esercizi uno alla volta, in file o progetti separati, evitando di definire più volte una classe pubblica con lo stesso nome nello stesso progetto.