OBIETTIVI DELLA LEZIONE
In questa lezione:
Alcune definizioni prese dal libro "Thinking in Java", 3rd ed. di Bruce Eckel. Il libro può essere scaricato direttamente dal sito dell'autore. E' un ottimo libro.
"Alan Kay summarized five basic characteristics of Smalltalk, the first successful object-oriented language and one of the languages upon which Java is based. These characteristics represent a pure approach to objectoriented programming:
Booch offers an even more succinct description of an object: An object has state, behavior and identity. This means that an object can have internal data (which gives it state), methods (to produce behavior), and each object can be uniquely distinguished from every other object—to put this in a concrete sense, each object has a unique address in memory
In Java abbiamo visto esistono tipi di dati primitivi e oggetti. I tipi di dati primitivi sono definiti dal linguaggio e di certo non ne possiamo aggiungere altri. Per scrivere i nostri programmi possiamo invece ideare nuovi oggetti.
Sappiamo già che per avere a disposizione (da oggi diremo istanziare) un oggetto nei nostri programmi scrivevamo:
String frase = "Questa è una stringa";
double[] movimenti = new double[10];
Queste istruzioni:
Cosa è una classe? E' uno strumento che Java ci fornisce per ideare nuovi oggetti (attenzione ideare, non costruire! capiremo adesso la differenza).
Una ditta che produce sedie dovrà sicuramente avere tutti i macchinari ed i materiali per costruire delle sedie vere e proprie. Ma per costruirle gli operai e le macchine a controllo numerico devono seguire dei progetti, ben definiti e preparati a priori (magari dopo analisi di mercato o di stile). Inoltre saranno stabilite delle procedure per effettuare determinati tipi di azione (ad esempio una procedura per verniciare le seggiole).
La stessa cosa vale per i nostri oggetti Java. Per costruirli servono due cose: una serie di meccanismi per fare in modo che vengano effettivamente creati nella memoria del calcolatore e un progetto in base al quale vengano costruiti.
Una classe è proprio questo progetto per ideare un nuovo oggetto. La classe inoltre contiene una serie di procedure per operare sugli oggetti: sono dette metodi
class ContoBancario { // Variabili istanza: private double saldo; // Metodi istanza: // costruttore predefinito public ContoBancario() { this.saldo = 0.0; } // costruttore alternativo public ContoBancario(double sommaIniziale) { this.saldo = sommaIniziale; } public double saldoConto() { return this.saldo; } public void deposita(double sommaDepositata) { this.saldo += sommaDepositata; } public void preleva(double sommaPrelevata) { this.saldo -= sommaPrelevata; } }
class ProvaConto0 { public static void main(String[] args) { ContoBancario contoLuigi = new ContoBancario(); ContoBancario contoGino = new ContoBancario(1000.0); // Trasferiamo 100.0 dal conto di Gino a quello di Luigi contoGino.preleva(100.0); contoLuigi.deposita(100.0); System.out.println("Saldo di Gino: " + contoGino.saldoConto()); System.out.println("Saldo di Luigi: " + contoLuigi.saldoConto()); } }
L'operatore new costruisce "fisicamente" l'oggetto in memoria seguendo le indicazione date dalla classe. L'indicazione fondamentale che segue è legata ad esempio al tipo di oggetti o tipi primitivi che l'oggetto deve contenere.
Le variabili che ciascun oggetto contiene sono chiamate variabili istanza.
I metodi istanza operano sull'oggetto a cui appartengono: ogni oggetto non solo contiene delle variabili ma anche dei metodi! Per far si che un oggetto segua la procedura contenuta in uno dei suoi metodi, dobbiamo richiederlo all'oggetto stesso. La notazione è sempre la seguente:
nomeOggetto.nomeMetodoIstanza(eventualiArgomentiDelMetodoIstanza);
Il nome dell'oggetto e quello del suo metodo che vorremmo venisse invocato sono separati da un . punto.
Un metodo istanza ha sempre:
Particolari metodi istanza sono detti costruttori e hanno lo stesso nome della classe (identico). Sono metodi che sono preposti all'inizializzazione degli oggetti una volta che sono stati creati dalla JVM.
Nell'esempio sopra c'è però una novità. Abbiamo due metodi costruttori che hanno lo stesso nome e con differenti parametri espliciti. Il compilatore Java selezionerà il metodo opportuno a partire dalla chiamata al metodo. Cioè: se il costruttore verrà invocato con nessun parametro, allora il compilatore selezionerà il primo, se il parametro sarà invece uno, verrà invocato il secondo. Questa proprietà di Java è detta polimorfismo.
Costruendo il nostro oggetto non vogliamo che i suoi dati siano disponibili all'esterno ma che l'utente programmatore utilizzi di volta in volta i metodi offerti dalla classe per operare sull'oggetto.
Questa caratteristica tipica dei linguaggi OO si dice astrazione perché crea tipi di dati astratti (non ci interessa come fisicamente sono memorizzati ne come si debba operare su di essi).Per incapsulare le variabili nell'oggetto (cioè non renderle disponibili all'esterno) basta utilizzare lo specificatore di accesso private.
Al contrario, generalmente vogliamo che i metodi siano disponibili all'esterno della classe, per questo utilizzaremo lo specificatore di accesso public.
Nell'esempio sopra le due classi possono essere in due file separati. Non c'è bisogno che siano nello stesso, ma andrebbe bene ugualmente. Come regola nel nostro corso li terremo tutti nella stessa directory. Attenzione: solo una classe per file può essere dichiarata public.
class ContoBancario { // Variabili istanza: private double saldo; // Metodi istanza: // costruttore predefinito public ContoBancario() { this.saldo = 0.0; } // costruttore alternativo public ContoBancario(double sommaIniziale) { this.saldo = sommaIniziale; } public double saldoConto() { return this.saldo; } public void deposita(double sommaDepositata) { this.saldo += sommaDepositata; } public void preleva(double sommaPrelevata) { this.saldo -= sommaPrelevata; } // Metodo di classe public static void trasferisci(double sommaTrasferita, ContoBancario origine, ContoBancario destinatario) { origine.saldo -= sommaTrasferita; destinatario.saldo += sommaTrasferita; } }
class ProvaConto1 { public static void main(String[] args) { ContoBancario contoLuigi = new ContoBancario(); ContoBancario contoGino = new ContoBancario(1000.0); // Trasferiamo 100.0 dal conto di Gino a quello di Luigi ContoBancario.trasferisci(100.0, contoGino, contoLuigi); System.out.println("Saldo di Gino: " + contoGino.saldoConto()); System.out.println("Saldo di Luigi: " + contoLuigi.saldoConto()); } }
Non sempre un metodo agisce direttamente solo su di un oggetto. Talvolta vengono coinvolti più oggetti, di tipo differente ad esempio. Per questo esistono i metodi di classe. Vanno utilizzati il meno possibile.
Per definire un metodo di classe va utilizzata la parola chiave static (deriva dal C, ma non ha un significato esplicito).
import javax.swing.*; class Ellisse { // variabili istanza private double asseMaggiore, asseMinore; // costruttori public Ellisse() { this.asseMaggiore = 0.0; this.asseMinore = 0.0; } public Ellisse(double maggiore, double minore) { this.asseMaggiore = maggiore; this.asseMinore = minore; } // metodi istanza public void setAsseMaggiore(double maggiore) { this.asseMaggiore = maggiore; } public void setAsseMinore(double minore) { this.asseMinore = minore; } public double getAsseMaggiore() { return this.asseMaggiore; } public double getAsseMinore() { return this.asseMinore; } public double calcolaArea() { return Math.PI * this.asseMaggiore * this.asseMinore; } } public class AreaEllisse { public static void main(String[] args) { String stringaInput; Ellisse ellisseUtente = new Ellisse(); stringaInput = JOptionPane.showInputDialog("Inserisci l'asse maggiore: "); ellisseUtente.setAsseMaggiore(Double.parseDouble(stringaInput)); stringaInput = JOptionPane.showInputDialog("Inserisci l'asse minore: "); ellisseUtente.setAsseMinore(Double.parseDouble(stringaInput)); JOptionPane.showMessageDialog(null, "L'area dell'ellisse avente asse minore " + ellisseUtente.getAsseMinore() + " e asse maggiore " + ellisseUtente.getAsseMaggiore() + " è " + ellisseUtente.calcolaArea() + "."); System.exit(0); } }
class ContoCorrenteBancario extends ContoBancario { // variabili istanza: private double tassoInteresse; // costruttori(sono metodi istanza) public ContoCorrenteBancario() { super(5.0); this.tassoInteresse = 0.01; } public ContoCorrenteBancario(double tassoInteresseIniziale) { this(); this.tassoInteresse = tassoInteresseIniziale; } public ContoCorrenteBancario(double sommaIniziale, double tassoInteresseIniziale) { super(sommaIniziale); this.tassoInteresse = tassoInteresseIniziale; } // metodi istanza public void aggiungiInteressi(int numeroMesi) { double interessiMaturati = (numeroMesi / 12.0) * this.tassoInteresse * this.saldoConto(); this.deposita(interessiMaturati); } }
class ProvaConto2 { public static void main(String[] args) { ContoCorrenteBancario contoLuigi = new ContoCorrenteBancario(); ContoCorrenteBancario contoGino = new ContoCorrenteBancario(1000.0, 0.02); // Trasferiamo 100.0 dal conto di Gino a quello di Luigi ContoBancario.trasferisci(100.0, contoGino, contoLuigi); // E' trascorso un anno, Luigi e Gino maturano interessi. contoLuigi.aggiungiInteressi(12); contoGino.aggiungiInteressi(12); System.out.println("Saldo di Gino: " + contoGino.saldoConto()); System.out.println("Saldo di Luigi: " + contoLuigi.saldoConto()); } }
Perchè costruire classi? Per riutilizzarle espandendole (non modificandone il codice!). Per espandere una classe si utilizza la parola chiave extends
Cosa significa espandere una classe? Data una classe iniziale, che chiamiamo superclasse, costruiamo una classe derivata, detta sottoclasse, che la estenda e la specializzi. La sottoclasse eredita tutte le variabili ed i metodi della superclasse. Non dobbiamo riscriverli!
La sottoclasse può (deve nel caso del costruttore) ridefinire alcuni metodi perché agiscano con le nuove variabili introdotte.
Nell'esempio abbiamo visto due nuovi metodi:
class ContoCorrenteBancarioNumerato extends ContoCorrenteBancario { // variabile istanza: private int numeroConto; // variabile classe: private static int ultimoNumeroContoAssegnato = 0; // costruttori(sono metodi istanza) public ContoCorrenteBancarioNumerato() { super(); ultimoNumeroContoAssegnato++; this.numeroConto = ultimoNumeroContoAssegnato; } public ContoCorrenteBancarioNumerato(double tassoInteresseIniziale) { super(tassoInteresseIniziale); ultimoNumeroContoAssegnato++; this.numeroConto = ultimoNumeroContoAssegnato; } public ContoCorrenteBancarioNumerato(double sommaIniziale, double tassoInteresseIniziale) { super(sommaIniziale, tassoInteresseIniziale); ultimoNumeroContoAssegnato++; this.numeroConto = ultimoNumeroContoAssegnato; } // metodi istanza public int getNumeroConto() { return this.numeroConto; } }
class ProvaConto3 { public static void main(String[] args) { ContoCorrenteBancarioNumerato contoLuigi = new ContoCorrenteBancarioNumerato(0.03); ContoCorrenteBancarioNumerato contoMario = new ContoCorrenteBancarioNumerato(); ContoCorrenteBancario contoGino = new ContoCorrenteBancario(1000.0, 0.02); // Trasferiamo 100.0 dal conto di Gino a quello di Luigi ContoBancario.trasferisci(100.0, contoGino, contoLuigi); System.out.println("Saldo di Gino: " + contoGino.saldoConto()); System.out.println("Saldo del conto numero " + contoLuigi.getNumeroConto() + " di Luigi: " + contoLuigi.saldoConto()); System.out.println("Saldo del conto numero " + contoMario.getNumeroConto() + " di Mario: " + contoMario.saldoConto()); } }
In questo ultimo esempio abbiamo visto che è possibile definire anche delle variabili di classe utilizzando lo specificatore static. Questo vuol dire che ciascun oggetto della classe NON conterrà una copia della variabile, che verrà invece custodita altrove. Di questa variabile ne esisterà una copia sola.
Anche le variabili statiche, come i metodi statici del resto, vanno utilizzate il meno possibile!
Esercizio 1: Classi
Implementate una classe Lattina che contenga i metodi getSuperfice e getVolume. Nel costruttore specificate il diametro e l'altezza della lattina. Prevedete anche un costruttore predefinito per la lattina standard da 33cc (altezza=10, diametro=5).
Esercizio 2: Classi
Costruire la classe CalcolaLattina il cui metodo di classe main chieda all'utente le dimensioni della lattina e restituisca il volume e l'area. Utilizzare la classe Lattina sviluppata nell'esercizio precedente.
Esercio 3: Metodi
Sviluppare la classe ParolaPalindroma che contenga il metodo
public static boolean verificaParolaPalindroma(String parola) { ... }
che verifichi se la stringa ricevuta come argomento sia palindroma o meno.
Una parola si dice palindroma quando la successione delle lettere è identica
sia se la parola è letta da sinistra a destra che viceversa. Ingegni, ottetto e
onorarono sono esempi
di parole palindrome. Scrivete un metodo di classe main che
chieda all'utente di inserire una parola e restituisca il risultato.
Esercizio 4: Classi
Scrivete una nuova classe Cerchio che estende la classe Ellisse dell'esempio visto a lezione. Scrivete gli opportuni costruttori e introducete i nuovi metodi di istanza getRaggio e setRaggio. Costuite la classe di prova ProvaRaggio che chieda all'utente il raggio e restituisca l'area.
©2005 Roberto Sassi