OBIETTIVI DELLA LEZIONE
In questa lezione:
Che cosa è un oggetto software? E cosa una classe? Vediamolo insieme in queste slides.
Per completare la nostra conoscenza di cosa sia un oggetto software, ecco alcune definizioni prese dal libro "Thinking in Java", 3rd ed. di Bruce Eckel. Il libro può essere scaricato direttamente dal sito dell'autore (è 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 object-oriented 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. In definitiva, 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!
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()); } }
Analizziamo il primo esempio in queste slides. Una sintesi è riportata nei paragrafi che seguono. Questo che stiamo affrontando è un argomento importante: fate anche riferimento al libro di testo!
Lo stato di un oggetto è "contenuto" in particolari variabili dette campi o variabili istanza.
I metodi istanza sono l'interfaccia dell'oggetto verso altri oggetti. Ne definiscono il comportamento. Per invitare un oggetto a manifestare un comportamento (invocare un metodo), si utilizza la notazione:
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 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.
È tipico utilizzare l'overload cioè avere più costruttori con lo stesso nome (anche perché non potrebbe essere diversamente, visto che il metodo costruttore deve avere lo stesso nome della classe) 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.
All'interno dei costruttori, come nei metodi di istanza è possibile utilizzare il parametro implicito this.
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 utilizzeremo 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()); } }
Analizziamo il secondo esempio in queste altre slides.
Non sempre un metodo agisce direttamente solo su di un oggetto. Talvolta vengono coinvolti più oggetti, di tipo differente ad esempio. Anche per questo esistono i metodi di classe.
Per definire un metodo di classe va utilizzata la parola chiave static (deriva dal C, ma non ha un significato esplicito).
Inoltre è 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 (nello spazio di memoria della JVM chiamato "permanent generation"). Di questa variabile ne esisterà una copia sola.
Le variabili di classe vengono spesso utilizzate per definire dei valori costanti. Per farlo si utilizza lo specificatore final.Esempio:
public static final double PI = 3.14159;
Per convenzione le costanti hanno nomi tutti maiuscoli. Più parole si congiungono con un carattere "_". Esempio:
public static final int ALBERI_MELE = 20;
Metodi e variabili statiche vanno utilizzate il meno possibile!
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); } }
Quando una variabile di tipo riferimento ad oggetto non viene inizializzata:
null è una costante letterale per le variabili di tipo riferimento ad oggetto.
Se abbiamo finito di utilizzare un oggetto, possiamo assegnare al suo riferimento il valore null. il meccanismo di garbage collection della JVM lo eliminerà dallo heap.
Vediamo alcuni errori in cui è facile incappare lavorando con le classi Java.
Abbiamo visto che in Java c'è una differenza sostanziale tra variabili ti tipo riferimento ad oggetto ed oggetti veri e propri. Capiamolo meglio. Consideriamo questa classe:
class Punto3D { private double x, y, z; public Punto3D(double x0, double y0, double z0) { this.x = x0; this.y = y0; this.z = z0; } public double getX() { return this.x; } public void setX(double nuovoX) { this.x = nuovoX; } public double getY() { return this.y; } public double getZ() { return this.z; } }
A costo di essere noiosi, rivediamo in dettaglio cosa significa istanziare un nuovo oggetto Punto3D
Punto3D questo = new Punto3D(1.0,2.0,1.0);
Ecco uno schema dei quattro passi necessari:
Vediamo invece cosa succede quando dichiariamo una variabile di tipo primitivo:
double distanza = 10.0;
Riassumendo:
Attenzione che quando copiate una variabile oggetto viene copiato solo il riferimento all'oggetto (cioè ovviamente il contenuto della variabile). Esempio:
Punto3D questo = new Punto3D(1.0,2.0,3.0); Punto3D quello; quello = questo; // attenzione! quello e quello contengono lo stesso riferimento! quello.setX(8.0); System.out.println(questo.getX()); //Stampa 8.0 non 1.0!
Quando confrontate due variabili di tipo riferimento ad oggetto non confrontate gli oggetti! Esempio:
Punto3D questo = new Punto3D(1.0,2.0,3.0); Punto3D quello = new Punto3D(1.0,2.0,3.0); if(questo == quello) { // Attenzione stiamo confrontando i riferimenti NON gli oggetti! System.out.println("I due riferimenti coincidono"); } if((questo.getX()==quello.getX()) && (questo.getY()==quello.getY()) && (questo.getZ()==quello.getZ())) { // Giusto System.out.println("I due punti coincidono"); }
Una soluzione più efficace ed elegante è quella di sovrascrivere il metodo equals() della superclasse Object. Lo vedremo in seguito.
Da quello che abbiamo visto prima se ci dimentichiamo di invocare il costruttore i passi 2, 3 e 4 non vengono effettuati. La variabile oggetto contiene un valore qualunque e il compilatore si lamenta con l'errore variable ... might not have been initialized. Esempi:
Punto3D questo; questo.setX(10.0); // Sbagliato questo non è stata inizializzata Punto3D questo = null; questo.setX(10.0); // Sbagliato questo non si riferisce a nessun oggetto Punto3D quello = new Punto3D(1.0,2.0,3.0); quello.setX(20.0); // Giusto
Punto3D quello = new Punto3D(1.0,2.0,3.0); // Sbagliato non possiamo ricreare un oggetto! quello.Punto3D(6.0,0.0,1.0); // Giusto, creiamo un secondo oggetto e ne mettiamo il riferimento in quello. // Il vecchio oggetto viene eliminato prima o poi dal meccanismo automatico // di garbage collection. quello = new Punto3D(6.0,0.0,1.0);
Punto3D quello = new Punto3D(1.0,2.0,3.0); setX(10.0); // Sbagliato, su quale oggetto deve operare?
Esercizio 1: Classi [Soluzione]
Implementate la classe Lattina che contenga i metodi getAreaBase() e getVolume(). Ciascuna lattina deve essere caratterizzata da altezza e diametro. Nel costruttore specificate il diametro e l'altezza della lattina. Prevedete anche un costruttore predefinito per la lattina standard (altezza=10, diametro=5).
Esercizio 2: Classi [Soluzione]
Costruire la classe CalcolaLattina il cui metodo di classe main chieda all'utente le dimensioni della lattina e restituisca il volume e la superfice di base della lattina. Utilizzare la classe Lattina sviluppata nell'esercizio precedente.
Esercizio 3: Classi [Soluzione]
Costruire la classe Triangolo per rappresentare triangoli. La classe deve contenere i tre campi, lato1, lato2 e lato3 che contengano le lunghezze dei lati (in senso orario).
Inoltre la classe vede fornire:
Infine implementare la classe provaTriangoli() che costruisce due triangoli e fornisce all'utente i valori di area e perimetro.
Esercio 4: Metodi [Soluzione]
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.
Esercio 5: Classi [Soluzione]
Anche riutilizzando parte del codice dell'esercizio della lezione 4, sviluppare la classe EquazioneSecondoGrado.
Lo stato di ciascun oggetto deve essere nascosto. La classe deve prevedere tutti i metodi setter e getter necessari. Inoltre deve prevedere un costruttore predefinito (a=b=c=0) e un costruttore che permetta di inizializzare opportunamente i parametri dell'equazione. Infine deve fornire i metodi di istanza pubblici delta() (che ritorni il valore b*b-4*a*c), risolvi() che ritorni un array di double con la/le soluzione/i (nel caso l'equazione non abbia soluzione utilizzate il valore Double.NaN), isRisolvibile() (che restituisca all'utente un valore boolean che valga true nel caso l'equazione ammetta soluzioni reali).
Quindi create una seconda classe che chieda all'utente i parametri dell'equazione, istanzi un opportuno oggetto di classe EquazioneSecondoGrado, verifichi se l'equazione ha soluzioni reali, ed, eventualmente, le calcoli.
Cominciamo a vedere alcuni esempi che ci introducano verso la costruzione delle interfacce utente grafiche (GUI). Il primo esempio costruisce una finestra vuota a schermo:
import javax.swing.JFrame; public class PrimaProvaFrame { public static void main(String[] args) { JFrame frame1 = new JFrame(); frame1.setTitle("La mia prima finestra"); frame1.setSize(300,150); frame1.setLocation(200,200); frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // System.exit(0); mi chiude anche il frame ... frame1.setVisible(true); } }
Il secondo esempio aggiunge alla finestra alcuni componenti, i mattoni base per la costruzione di una interfaccia.
import javax.swing.*; public class SecondaProvaFrame { public static void main(String[] args) { // Per prima cosa costruiamo un pò di componenti JLabel etichetta = new JLabel("Testo etichetta"); JTextField campoTesto = new JTextField("Qui puoi scrivere qualcosa"); JButton bottone = new JButton("Conferma"); JCheckBox checkBox = new JCheckBox("Scelta 1"); JRadioButton radioButton = new JRadioButton("Scelta 2"); JComboBox comboBox = new JComboBox(new String[] {"Valore 1", "Valore 2", "Valore 3"}); // Inserisce i componenti in un pannello JPanel mioPannello = new JPanel(); mioPannello.add(etichetta); mioPannello.add(campoTesto); mioPannello.add(bottone); mioPannello.add(checkBox); mioPannello.add(radioButton); mioPannello.add(comboBox); JFrame frame1 = new JFrame(); frame1.add(mioPannello); frame1.setTitle("La mia prima finestra"); frame1.setSize(300,150); frame1.setLocationRelativeTo(null); frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame1.setVisible(true); } }
©2008 Roberto Sassi