corsoJava
  • Corso JAVA
  • Introduzione linguaggio
  • Verifica tipi primitivi vs reference
  • Esercizi su equals
  • Introduzione su oggetti
  • Packages e import
  • Polimorfismo
    • Pit stop
  • Enum
  • String รจ speciale
  • Eccezioni
  • Nested Classes
  • Array, ArrayList e Hash Table
    • Esempio gioco carte
  • Linked data structures
  • Tipi generici
  • Comparing Java and C# Generics - Jonathan Pryor's web log
  • Contenitori
    • Esempi con classi container
  • Input/Output streams, Files
  • Basic I/O
  • Java IO Tutorial
  • Networking
  • I Thread e concorrenza
    • Esercizi multithreading
    • Thread interference
    • Esercizi thread interference
    • wait(), notify() e notifyAll() per la sincronizzazione tra i thread
    • Verifiche produttore/consumatore
    • Lock esplicito - java.util.concurrent.Locks
    • Semafori
    • Programmare con i thread
    • I Virtual Thread
    • Materiale
  • I Thread e networking
  • Esempi Java Socket Programming
  • Esempi Javascript Socket Programming
  • Messaggi datagram e invio multicast
  • Lambda Expression
  • Java Stream
  • Data Oriented Programming in Java
  • Java improved its 'Hello World' experience
  • Appendice A: utilizzo classe Scanner
  • Java For The Experienced Beginner
  • Modern Java
  • CodeJava - Java Core
  • Is OOP Relevant Today?
  • Book
Powered by GitBook
On this page
  • Riutilizzo dell'implementazione
  • Aggregazione (composition)
  • Ereditarietร  e Polimorfismo
  • Definire un costruttore
  • Costruttori multipli
  • Chiamare un costruttore da un costruttore
  • Variabili di classe
  • Accesso alle variabili di classe
  • Costanti, un altro esempio di variabili di classe
  • Metodi di classe
  • Definizione di un metodo static per Circle
  • Sottoclassi ed ereditarietร 
  • Costruttore delle sottoclassi
  • La catena dei costruttori e costruttore di default
  • Method Overriding & Variable Hiding
  • Data hidding e incapsulation
  • Classi astratte e interface
  • Classi astratte
  • Interface

Introduzione su oggetti

Ripresa del capitolo "Introduction to Objects" di Thinking in Java.

PreviousEsercizi su equalsNextPackages e import

Last updated 4 years ago

Riutilizzo dell'implementazione

Ci sono due modi per riutilizzare le classi esistenti, la composition (o anche detta aggregazione) e con l'ereditarietร .

Con la composition, definiamo una nuova classe, che รจ composta di classi giร  esistenti. Con l'ereditarietร , si deriva una nuova classe basandosi su classi giร  esistenti, con modifiche o esensioni.

Aggregazione (composition)

L' aggregazione permette di costruire oggetto piรน complessi ad esempio un oggetto di tipo (classe) Car dalla composizione di oggetti di altri tipi ad esempio Engine o Weel. La relazione aggregazione รจ detta anche "has-a", es: 'La macchina ha un motore'.

Ereditarietร  e Polimorfismo

Ereditarietร 

Sopra grafico UML relazione superclasse/sottoclasse.

Sotto esempio di relazione tra la superclasse shape e le sottoclassi Circle, Square e Triangle, sottoclassi di Shape.

Le sottoclassi Circle, Square e Triangle ereditano i metodi draw(), erase(), move(), getColor() e setColor() dalla superclasse Shape.

Nell'esempio sotto vediamo che la sottoclasse Triangle, estende la classe Shape in quanto aggiunge due metodi a quelli ereditati da Shape, i metodi FlipVertical() e FlipHorizontal().

Sebbene ereditarietร  implica talvolta (specialmente in Java, dove la keyword per l'ereditarietร  รจ extends) che to stia andando ad aggiungere nuovi metodi nella sottoclasse, questo non รจ necessariamente vero. Il secondo e piรน importante metodo per differenziare la tua nuova classe รจ cambiare il comportamento del metodo della classe base. Questo รจ comunemente dettto fare l'override (sovrascrivere) il metodo.

Nel grafico sotto UML vediamo che le sottoclassi Circle, Square e Triangle, sovrascrivono (override) i metodi draw() ed erase() di Shape. Nella terminologia dei linguaggi ad oggetti, si dice che nelle classi Circle, Square e Triangle viene eseguita l'override dei metodi draw() e erase(). Ereditarietร  e override dei metodi nelle sottoclassi sono gli ingredienti per il Polimorfismo (vedi paragrafo successivo).

Da notare che oggetti del tipo della sottoclasse possono essere assegnati a variabili della superclasse, in quanto ad esempio un oggetto di tipo Circle รจ anche una Shape (is-a in terminologia dei linguaggi ad oggetti).

Shape sp = new Circle();
sp = new Triangle();
// or
sp = new Square();

Viceversa non รจ detto: potrebbe essere una Shape o ad esempio una forma differente a quella a cui sto facendo l'assegnamento. E' necessario un cast esplicito (downcast) per fare l'assegnazione e se l'assegnamento non รจ valido, genera un'eccezione java.lang.ClassCastException.

Il downcasting richiede il cast espicito nella forma dell'operatore prefisso (new-type). Come giร  detto l'operazione di downcast non รจ sempre sicura, e lancia una eccezione a runtime ClassCastException se l'istanza che su cui รจ eseguito il downcast non appartiene alla sottoclasse. Un oggetto di una sottoclasse puรฒ esssere sostituito a uno della superclasse, ma il contrario non รจ vero.

Shape spA = new Triangle();
Triangle myTriangle = (Triangle) spA; // OK cast 

Square mySquare = (Square) spA; // throws ClassCastException

Polimorfismo

Per spiegare il polimorfismo partiamo da un esempio: il metodo doSomething accetta qualsiasi oggetto di tipo Shape, quindi รจ indipendentemente dal tipo di oggetto passato per eseguire l'operazione di erase e draw.

void doSomething(Shape shape) {
    shape.erase();
    // ...
    shape.draw();
}

Se in qualche altra parte del programma viene chiamato il metodo doSomething():

Circle circle = new Circle();
Triangle triangle = new Triangle(); 
Line line= new Line(); 
doSomething(circle); 
doSomething(triangle); 
doSomething(line);

La chiamata a doSomething() funziona correttamente indipendentemente dal tipo di oggetto passato.

Considera la linea:

doSomething(circle);

Quello che succede quรฌ รจ the un oggetto di tipo Circle รจ passato a un metodo che si aspetta un oggetto di tipo Shape. Siccome il tipo Circle รจ un tipo di Shape, il metdodo doSomthing lo puรฒ trattare come Shape.

Il termine Upcasting per come l'albero della gerarchia di classi รจ disegnato, con la classe base in alto.

Altro esempio, riguardo gli strumenti musicali sempre sul polimorfismo: scrivo il metodo tune(Instrument i) che prende come parametro un oggetto di tipo Instrument (superclasse della gerarchia degli strumenti musicali).

polymorphism/music/Note.java
//: polymorphism/music/Note.java
// Notes to play on musical instruments.
package polymorphism.music;

public enum Note {
    MIDDLE_C, C_SHARP, B_FLAT; // Etc.
} ///:~
polymorphism/music/Instrument.java
//: polymorphism/music/Instrument.java
package polymorphism.music;
import static net.mindview.util.Print.*;

class Instrument {
    public void play(Note n) {
        print("Instrument.play()");
    }
}
///:~
polymorphism/music/Wind.java
//: polymorphism/music/Wind.java
package polymorphism.music;

// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {
    // Redefine interface method:
    public void play(Note n) {
        System.out.println("Wind.play() " + n);
    }
} ///:~
polymorphism/music/Music.java
//: polymorphism/music/Music.java
// Inheritance & upcasting.
package polymorphism.music;

public class Music {
    public static void tune(Instrument i) {
        // ...
        i.play(Note.MIDDLE_C);
    }
    
    public static void main(String[] args) {
        Wind flute = new Wind();
        tune(flute); // Upcasting
    }
} 
/* Output:
Wind.play() MIDDLE_C
*///:~

Overloading invece di upcasting

Esempio per far vedere che se invece di sfruttare il polimorfismo nel metodo tune(Instrument i) nella classe Music dell'esempio sopra, avessi usato un metodo tune per ogni tipo di strumento, facendo quindi l'overloading del metodo tune():

polymorphism/music/Music2.java
package polymorphism.music;

import static net.mindview.util.Print.*;

class Stringed extends Instrument {
    public void play(Note n) {
        print("Stringed.play() " + n);
    }
}

class Brass extends Instrument {
    public void play(Note n) {
        print("Brass.play() " + n);
    }
}
public class Music2 {
    public static void tune(Wind i) {
        i.play(Note.MIDDLE_C);
    }
    
    public static void tune(Stringed i) {
        i.play(Note.MIDDLE_C);
    }
    
    public static void tune(Brass i) {
        i.play(Note.MIDDLE_C);
    }
    
    public static void main(String[] args) {
        Wind flute = new Wind();
        Stringed violin = new Stringed();
        Brass frenchHorn = new Brass();
        tune(flute); // No upcasting
        tune(violin);
        tune(frenchHorn);
    }
} 
/* Output:
Wind.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
*///:~

Facendo l'overloading del metodo tune(), per ogni nuovo tipo di strumento (classe), dovrei aggiungere un metodo tune per quel particolare tipo di strumento: ad esempio tune(Guitar i) nella classe Music2, se aggiungo la classe Guitar, mentre con il polimorfismo tune(Instrument i) funzionerebbe correttamente senza bisogno di modifiche della classe Music, basta che il nuovo strumento Guitar sia sottoclasse di Instrument.

Si vede da questo esempio come il polimorfismo permetta di esendere il codice (ad esempio aggiungere un nuovo tipo di strumento) senza modificare il codice giร  scritto: il metodo tune(Instrument i) funziona correttamente anche con la nuova classe Guitar.

Definire un costruttore

Il costruttore serve per instanziare, creare, un' oggetto data una classe ad esempio

Modifichiamo la classe Circle

public class Circle {
    public double x, y;   // The coordinates of the center
    public double r;      // The radius
    // Methods that return the circumference and area of the circle
    public double circumference() { return 2 * 3.14159 * r; }
    public double area() { return 3.14159 * r*r; }
}

definendo un costruttore:

public class Circle {
    public double x, y, r;  // The center and the radius of the circle
    // The constructor method.
    public Circle(double x, double y, double r)
    {
        this.x = x;
        this.y = y;
        this.r = r;
    }   
    public double circumference() { return 2 * 3.14159 * r; }
    public double area() { return 3.14159 * r*r; }
}

Mentre con il vecchio, di default, costruttore, dovevamo scrivere il codice in questo modo:

Circle c = new Circle();
c.x = 1.414;  
c.y = -1.0;  
c.r = .25;

Ora con il nuovo costruttore:

Circle c = new Circle(1.414, -1.0, .25);

Ci sono due cose importanti da notare, circa il nome e la dichiarazione del costruttore:

  • il costruttore ha lo stesso nome della classe;

  • il tipo di ritorno รจ implicitamente un istanza della classe. Nessun return type รจ specificato nella dichiarazione del costruttore, neppure la parola chiave void รจ utilizzata. L'oggetto this รจ implicitamente ritornato; il contruttore non deve utilizzare return per restituire un valore;

Costruttori multipli

Una classe puรฒ avere piรน costruttori:

public class Circle {
    public double x, y, r;
    
    public Circle(double x, double y, double r) {
        this.x = x; this.y = y; this.r = r;
    }
    public Circle(double r) { x = 0.0; y = 0.0; this.r = r; }
    public Circle(Circle c) { x = c.x; y = c.y; r = c.r; }
    public Circle() { x = 0.0; y = 0.0; r = 1.0; }
    
    public double circumference() { return 2 * 3.14159 * r; }
    public double area() { return 3.14159 * r*r; }
    
    @Override
	public String toString() {
		return "Circle [x=" + x + ", y=" + y + ", raggio=" + r + "]";
	}
}

Nell'esempio un oggetto di tipo Circle puรฒ essere istanziato, utilizzando uno di questi costruttori e viene inizializzato in base al costruttore invocato, esempio:

// costruttore senza argomenti
Circle circle1 = new Circle();
System.out.println("Circle1: " + circle1); // Circle1: Circle [x=0.0, y=0.0, raggio=1.0]
		
// costruttore un parametro double, il raggio
Circle circle2 = new Circle(5);
System.out.println("Circle2: " + circle2); // Circle2: Circle [x=0.0, y=0.0, raggio=5.0]
		
//costruttore con tre parametri double
Circle circle3 = new Circle(3.2, 2.5, 7);
System.out.println("Circle3: " + circle3); // Circle3: Circle [x=3.2, y=2.5, raggio=7.0]
		
Circle circle4 = new Circle(circle3);
System.out.println("Circle4: " + circle4); // Circle4: Circle [x=3.2, y=2.5, raggio=7.0]

Chiamare un costruttore da un costruttore

Quando scriviamo piรน costruttori per una classe, ci sono volte che verrebbe comodo chiamare un costruttore da un altro costruttore per evitare di duplicare del codice. Si puรฒ fare una simile chiamata usando la keyword this.

Normalmente, quando diciamo this, intendiamo nel senso di "questo oggetto" o "l'oggetto corrente" e in se stesso รจ la reference all'oggetto corrente. In un costruttore, la keyword this prende un diverso significato quando รจ seguita da una lista di argomento (anche vuota). Fa una chiamata esplicita al costruttore che corrisponde alla lista degli argomenti. Quindi abbiamo un modo semplice per chiamare altri costruttori.

package prova;

public class Flower {
	int petalCount = 0;
	String s = "initial value";

	Flower(int petals) {
		petalCount = petals;
		System.out.println("Constructor w/ int arg only, petalCount= " + petalCount);
	}

	Flower(String ss) {
		System.out.println("Constructor w/ String arg only, s = " + ss);
		s = ss;
	}

	Flower(String s, int petals) {
		this(petals);
		// ! this(s); // Canโ€™t call two!
		this.s = s; // Another use of "this"
		System.out.println("String & int args");
	}

	Flower() {
		this("hi", 47);
		System.out.println("default constructor (no args)");
	}

	void printPetalCount() {
		// ! this(11); // Not inside non-constructor!
		System.out.println("petalCount = " + petalCount + " s = " + s);
	}

	public static void main(String[] args) {
		Flower x = new Flower();
		x.printPetalCount();
	}
} 
/*
 * Output: Constructor w/ int arg only, petalCount= 47 String & int args default
 * constructor (no args) petalCount = 47 s = hi
 */// :~

Il costruttore Flower(String s, int petals) mostra che si puรฒ usare this per chiamare un altro costruttore, non potete usere this due volte. In aggiunta la chiamata al costruttore deve essere la prima istruzione altrimente si riceve un errore di compilazione.

In questo esempio si vede un altro modo in cui this puรฒ essere usato. Siccome il nome dell'argomento s e il nome del campo della classe s sono gli stessi per evitare l'ambiguitร , si puรฒ utilizzare this.s, per dire che ci stiamo riferendo al dato membro.

Nel metodo printPetalCount() si puรฒ vedere che il compilatore non permette di chiamare il costruttore da nessun altro metodo se non il costruttore.

Riprendendo l'esempio di Circle, riscriviamo il codice, usando this ed evitando cosรฌ duplicazioni di codice:

public class Circle {
    public double x, y, r;
    public Circle(double x, double y, double r) { 
        this.x = x; this.y = y; this.r = r; 
    }
    public Circle(double r) { this(0.0, 0.0, r); }
    public Circle(Circle c) { this(c.x, c.y, c.r); }
    public Circle() { this(0.0, 0.0, 1.0); }

}

C'รจ una restrizione nell'utilizzo di this( ): puรฒ esssere solo il primo statement del costruttore.

Questa restrizione รจ legata all'invocazione automatica del costruttore della superclasse, meccanismo che verrร  spiegato in seguito.

Variabili di classe

public class Circle {
    static int num_circles = 0; // class variable: how many circles created
    public double x, y, r;      // instance vars: the center and the radius
    public Circle(double x, double y, double r) {
        this.x = x; this.y = y; this.r = r;
        num_circles++;
    }
    public Circle(double r) { this(0.0, 0.0, r); }
    public Circle(Circle c) { this(c.x, c.y, c.r); }
    public Circle() { this(0.0, 0.0, 1.0); }
    public double circumference() { return 2 * 3.14159 * r; }
    public double area() { return 3.14159 * r*r; }
}

Accesso alle variabili di classe

System.out.println("Number of circles created: " + Circle.num_circles);

Costanti, un altro esempio di variabili di classe

public class Circle {
    public static final double PI = 3.14159265358979323846;
    public double x, y, r;
    // ... etc....
}

Oltre a static c'รจ la parola chiave final che significa che il valore della variabile non puรฒ piรน cambiare. Cosรฌ non รจ possibile fare qualcosa come:

Circle.PI = 4; // errore in compilazione

Metodi di classe

public class Circle {
    double x, y, r;
    // is point (a,b) inside this circle?  
    public boolean isInside(double a, double b)
    {
        double dx = a - x;
        double dy = b - y;
        double distance = Math.sqrt(dx*dx + dy*dy);
        if (distance < r) return true;
        else return false;
    }
        .
        .  // Constructor and other methods omitted.
        .
}

Math.sqrt รจ un metodo di classe della classe Math del JDK.

I metodi di classe sono definiti con l'identificativo static

No this nei metodi di classe (static)

I metodi statici di classe differiscono da quelli di istanza per una cosa importante: non รจ passato il this al metodo automaticamente come avviene per i metodi di istanza.

Definizione di un metodo static per Circle

public class Circle {
    public double x, y, r;
    // An instance method.  Returns the bigger of two circles.
    public Circle bigger(Circle c) { 
        if (c.r > r) return c; else return this; 
    }
    // A class method.  Returns the bigger of two circles.
    public static Circle bigger(Circle a, Circle b) {
        if (a.r > b.r) return a; else return b;
    }
        .
        .  // Other methods omitted here.
        .
}

Invocazione di un metodo d'instanza cosรฌ:

Circle a = new Circle(2.0);
Circle b = new Circle(3.0);
Circle c = a.bigger(b);         // or, b.bigger(a);

Invocazione di un metodo di classe:

Circle a = new Circle(2.0);
Circle b = new Circle(3.0);
Circle c = Circle.bigger(a,b);

L' inizializzazione statica

// We can draw the outline of a circle using trigonometric functions.
// Trigonometry is slow though, so we pre-compute a bunch of values.
public class Circle {
    // Here are our static lookup tables, and their own simple initializers.
    static private double sines[] = new double[1000];
    static private double cosines[] = new double[1000];
    // Here's a static initializer "method" that fills them in.  
    // Notice the lack of any method declaration!
    static {
        double x, delta_x;
        int i;
        delta_x = (Circle.PI/2)/(1000-1);
        for(i = 0, x = 0.0; i < 1000; i++, x += delta_x) {
            sines[i] = Math.sin(x);
            cosines[i] = Math.cos(x);
        }
    }
        .
        .  // The rest of the class omitted.
        .
}

Sottoclassi ed ereditarietร 

Riprendiamo il concetto di sottoclassi ed ereditarietร .

Un esempio:

public class GraphicCircle extends Circle {
    // We automatically inherit the variables and methods of
    // Circle, so we only have to put the new stuff here.
    // We've omitted the GraphicCircle constructor, for now.
    Color outline, fill;
    public void draw(DrawWindow dw) { 
        dw.drawCircle(x, y, r, outline, fill); 
    }
}

Con la parola chiave extends significa che GraphicCircle รจ una sottoclasse di Circle e che eredita di campi e metodi della superclasse.

Un' altra proprietร  importante รจ che ogno oggetto di tipo GraphicCircle , รจ anche un oggetto di tipo Circle.

Costruttore delle sottoclassi

public GraphicCircle(double x, double y, double r, 
                                Color outline, Color fill)
{
    this.x = x; 
    this.y = y; 
    this.r = r;
    this.outline = outline; 
    this.fill = fill;
}

Questo construttore si basa sul fatto che la classe GraphicCircle eredita tutte le variabili da Circle e semplicemente inizializza lui stesso quelle variabili. Ma questo duplica il codice del costruttore di Circle, e se Circle facesse, nel costruttore, una inizializzazione piรน elaborata, la duplicazione, in GraphicCircle sarebbe complicata. Inoltre, se la classe Circle avesse attributi private (sono spiegati dopo) non potrebbe neppure inizializzarli direttamente, come abbiamo fatto sopra. Ciรฒ di cui abbiamo bisogno รจ um modo per chiamare un costruttore di Circle, all'interno del nostro costruttore di GraphicCircle.

Nel codice sotto vediamo come si fa.

GraphicCircle costruttore con super

Invocazione del costruttore della superclasse:

public GraphicCircle(double x, double y, double r, 
                                Color outline, Color fill) 
{
    super(x, y, r); // invoco costruttore di Circle
    this.outline = outline; 
    this.fill = fill;
}

Utilizzo di super(...) .

super รจ una parola riservata di Java. Uno dei suoi utilizzi รจ mostrata in questo esempio - per invocare il costruttore della superclasse. Il suo utilizzo รจ analogo all'uso di this per invocare il costruttore di una classe dall'interno di un altro costruttore della stessa classe.

L'utilizzo di super per invocare un costruttore รจ soggetto alle stesse restrizioni dell'utilizzo di this per l'invocazione di un costruttore:

  • super puรฒ essere solo usato in questo modo all'interno di un metodo costruttore;

  • La chiamata al costruttore della superclasse deve essere la prima istruzione all'interno del costruttore;

La catena dei costruttori e costruttore di default

Quando si definisce una classe, Java garantisce che il metodo costruttore della classe รจ invocato quando un'istanza della classe รจ creata. Inoltre garantisce che il costruttore รจ invocato anche quando un' istanza della sottoclasse รจ creata. Per garantire questo secondo punto, Java deve assicurare che ogni costruttore chiama un costruttore della superclasse. Se la prima istruzione nel costruttore non รจ la chiamata al costruttore della superclasse con la super, allora Java implicitamente aggiunge l'istruzione super() - cioรจ, chiama il costruttore della superclasse senza parametri. Se la superclasse non ha un costruttore senza parametri, viene generato un errore di compilazione.

C'รจ una eccezione alla regola che Java invoca super() implicitamente se non lo fai esplicitamente. Se la prima riga del costruttore, C1, usa la sintassi this() per invocare un altro costruttore, C2, della classe, Java si basa su C2 per invocare il costruttore della superclasse., e non inserisce in C1 la chiamata super(). Se, per esempio, anche C2 utilizzasse this() per invocare un terzo costruttore, allora anche C2 non chiamerebbe super(), ma un qualche costruttore della classe, esplicitamente o implicitamente dovrร  invocare il costruttore della superclasse.

Tutto questo vuol dire che le chiamate dei costruttori sono in catena, tutte le volte che un oggetto รจ creato, tutta la sequenza dei costruttori รจ invocata, dalla sottoclasse alla superclasse, fino ad Object, la radice della gerarchia delle classi. Siccome il costruttore della superclasse รจ sempre invocato come prima istruzione del costruttore, il codice del costruttore di Object sempre viene eseguito per primo, seguito del codice del costruttore di ogni sottoclasse, e giรน per la gerarchia delle classi, fino alla classe che sta per essere istanziata.

Da tener anche presente che se nella classe non รจ specificato un costruttore, Java genera implicitamente un costruttore di default, che invoca super(). Esempio, se in GraphicCircle non fossero definiti dei costruttori, Java genera implicitamente questo costruttore:

public GraphicCircle() { super(); }

Nota che se nella superclasse Circle, non c'รจ un costruttore senza argomenti (o quello di default o perchรจ dichiarati altri costruttori) allore dร  errore di compilazione.

Method Overriding & Variable Hiding

L' esempio seguente serve a mostrare l'utilizzo di super per invocare metodi delle superclassi e von la versione sovrascritta (overridden) della sottoclasse.

Data una classe Circle deriviamo una sottoclasse Cylinder

Circle.java
public class Circle {
   // private instance variables
   private double radius;
   private String color;

   // Constructors
   public Circle() {
      this.radius = 1.0;
      this.color = "red";
   }
   public Circle(double radius) {
      this.radius = radius;
      this.color = "red";
   }
   public Circle(double radius, String color) {
      this.radius = radius;
      this.color = color;
   }

   // Getters and Setters
   public double getRadius() {
      return this.radius;
   }
   public String getColor() {
      return this.color;
   }
   public void setRadius(double radius) {
      this.radius = radius;
   }
   public void setColor(String color) {
      this.color = color;
   }

   // Describle itself
   public String toString() {
      return "Circle[radius=" + radius + ",color=" + color + "]";
   }

   // Return the area of this Circle
   public double getArea() {
      return radius * radius * Math.PI;
   }
}

La sottoclasse Cylinder:

Cylinder.java
/*
 * A Cylinder is a Circle plus a height.
 */
public class Cylinder extends Circle {
   // private instance variable
   private double height;
   
   // Constructors
   public Cylinder() {
      super();  // invoke superclass' constructor Circle()
      this.height = 1.0;
   }
   public Cylinder(double height) {
      super();  // invoke superclass' constructor Circle()
      this.height = height;
   }
   public Cylinder(double height, double radius) {
      super(radius);  // invoke superclass' constructor Circle(radius)
      this.height = height;
   }
   public Cylinder(double height, double radius, String color) {
      super(radius, color);  // invoke superclass' constructor Circle(radius, color)
      this.height = height;
   }
   
   // Getter and Setter
   public double getHeight() {
      return this.height;
   }
   public void setHeight(double height) {
      this.height = height;
   }

   // Return the volume of this Cylinder
   public double getVolume() {
      return getArea()*height;   // Use Circle's getArea()
   }

   // Describle itself
   public String toString() {
      return "This is a Cylinder";  // to be refined later
   }
}

La classe di test:

TestCylinder.java
/*
 * A test driver for the Cylinder class.
 */
public class TestCylinder {
   public static void main(String[] args) {
      Cylinder cy1 = new Cylinder();
      System.out.println("Radius is " + cy1.getRadius()
         + " Height is " + cy1.getHeight()
         + " Color is " + cy1.getColor()
         + " Base area is " + cy1.getArea()
         + " Volume is " + cy1.getVolume());
   
      Cylinder cy2 = new Cylinder(5.0, 2.0);
      System.out.println("Radius is " + cy2.getRadius()
         + " Height is " + cy2.getHeight()
         + " Color is " + cy2.getColor()
         + " Base area is " + cy2.getArea()
         + " Volume is " + cy2.getVolume());
   }
}

Una sottoclasse eredita tutte le variabili membro e metodi dalla sua superclasse (quella padre e i suoi predecessori). Essa puรฒ utilizzare i metodi ereditati e le variabili cosรฌ come sono. Puรฒ anche sovrascrivere un metodo ereditato provvedendo la propria versione (overridding), o nascondere la variabile ereditata definendo una variabile con lo stesso nome.

Per esempio, il metodo ereditato getArea() (da Circle) in un oggetto di tipo Cylinder, calcola l'area di base del cilindro. Supponiamo che vogliamo sovrascrivere il metodo getArea() per calcolare l'area di superfice del cilindro. Sotto ci sono le modifiche alla classe Cylinder:

Cylinder.java
public class Cylinder extends Circle {
   ......
   // Override the getArea() method inherited from superclass Circle
   @Override
   public double getArea() {
      return 2 * Math.PI * getRadius() * height 
         + 2 * super.getArea();
   }
   // Need to change the getVolume() as well
   public double getVolume() {
      return super.getArea() * height;   // use superclass' getArea()
   }
   // Override the inherited toString()
   @Override
   public String toString() {
      return "Cylinder[" + super.toString() + ",height=" + height + "]";   
   }
}

Se getArea() รจ chiamato da un oggetto di tipo Circle, esso calcola l'area del cerchio. Se getArea() รจ invocato da un oggetto di tipo Cylinder, esso calcola l'area di superfice del cilindro, usando l'implementazione sovrascritta (overridden). Nota che bisogna utilizzare i metodti pubblici di accesso getRadius() per recuperare il valore di radius del Circle, perchรจ radius รจ dichiarato private e quindi non accessibile dalle altre classi, neppure dalle sottoclassi (in questo caso Cylinder).

Ma se sovrascriviamo getArea() in Cylinder, il metodo getVolume() (=getArea()*height) non รจ piรน corretto. Questo perchรจ il metodo nuovo, sovrascritto, getArea() sarร  quello utilizzato nel metodo getVolume() di Cilinder, ma la nuova implementazione del metodo non calcola piรน l'area di base del cerchio (che a me servirebbe per calcolare il volume). Puoi correggere l'errore nel metodo getVolume() scrivendo super.getArea(), per utilizzare la versione di Circle del metodo getArea().

Data hidding e incapsulation

Uno dei principi fondamentali della programmazione ad oggetti รจ che all'esterno la classe esponga solo ciรฒ che รจ utile perchรจ sia utilizzata, senza particolari inutili, ad esempio variabili di istanza della classe che non รจ appropriato che chi utilizza l'oggetto possa accedere e modificare, magari creando poi un comportamento non voluto riguardo l'utilizzo del metodo. Il metdodo principale per nascondere variabili e metodi di istanza di una classe รจ tramite il modificatore d'accesso private messo davanti alla variabile o la dichiarazione del metodo. Questo fa si che anche le eventuali sottoclassi non possano accedere alla variabile e al metodo. Con il modificatore private abbiamo quindi la chiusora piรน totale: solo dall'interno della classe posso accedere a variabili e metodi dichiarati come private. Al contrario con la dichiarazione con il modificatore d'accesso public ho l'apertura piรน totale. Chiunque puรฒ invocare il metodo o accedere alla variabile (anche da classi in altri package, basta che importi la classe).

Situazioni intermedie si han il modificatore protected: variabili o metodi dichiarati come protected sono accedibili da classi dello stesso pacchetto ma anche all'interno di sottoclassi anche se in package diversi.

Se non si mette nessun modificatore d'accesso alla variabile o al metodo, si ha l'accessibilitร  di tipo package, cioรจ sono accedibili solo da classi all'interno dello stesso package.

Accessibile a:

public

protected

package

private

Stessa classe

si

si

si

si

Classi stesso pacchetto

si

si

si

no

Sottoclassi in pacchetto differente

si

si

no

no

Non sottoclassi, pacchetti differenti

si

no

no

no

Classi astratte e interface

Le interface e le classi astratte provvedono a un meccanismo piรน strutturato per separare le interfacce (inteso come comportamento esposto da una classe) e l'implementazione.

Questi meccanismi non sono supportati in altri linguaggi, come il C++, fornendo un supporto solo indiretto a questi concetti. Il fatto che in Java esistano parole chiave (abstract e interface) indica che questi concetti sono cosรฌ importanti da fornirgli supporto diretto.

Per prima cosa vedremo le classi astratte, che sono in un certo senso una via di mezzo tra le classi ordinarie e le interface. Sebbene il primo impulso sarebbe quello di creare le interface, le classi astratte sono uno strumento importante e necessario per costruire classi che hanno qualche metodo non implementato.

Classi astratte

Qui sotto l'esempio dell'orchestra modificato con l'uso di abstract class e metodi.

interfaces/music4/Music.java
//: interfaces/music4/Music4.java
// Abstract classes and methods.
package interfaces.music4;

import polymorphism.music.Note;
import static net.mindview.util.Print.*;

abstract class Instrument {
	private int i; // Storage allocated for each

	public abstract void play(Note n);

	public String what() {
		return "Instrument";
	}

	public abstract void adjust();
}

class Wind extends Instrument {
	public void play(Note n) {
		print("Wind.play() " + n);
	}

	public String what() {
		return "Wind";
	}

	public void adjust() {
	}
}

class Percussion extends Instrument {
	public void play(Note n) {
		print("Percussion.play() " + n);
	}

	public String what() {
		return "Percussion";
	}

	public void adjust() {
	}
}

class Stringed extends Instrument {
	public void play(Note n) {
		print("Stringed.play() " + n);
	}

	public String what() {
		return "Stringed";
	}

	public void adjust() {
	}
}

class Brass extends Wind {
	public void play(Note n) {
		print("Brass.play() " + n);
	}

	public void adjust() {
		print("Brass.adjust()");
	}
}

class Woodwind extends Wind {
	public void play(Note n) {
		print("Woodwind.play() " + n);
	}

	public String what() {
		return "Woodwind";
	}
}

public class Music4 {
	// Doesnโ€™t care about type, so new types
	// added to the system still work right:
	static void tune(Instrument i) {
		// ...
		i.play(Note.MIDDLE_C);
	}

	static void tuneAll(Instrument[] e) {
		for (Instrument i : e)
			tune(i);
	}

	public static void main(String[] args) {
		// Upcasting during addition to the array:
		Instrument[] orchestra = { new Wind(), new Percussion(), new Stringed(), new Brass(), new Woodwind() };
		tuneAll(orchestra);
	}
} /*
	 * Output: Wind.play() MIDDLE_C Percussion.play() MIDDLE_C Stringed.play()
	 * MIDDLE_C Brass.play() MIDDLE_C Woodwind.play() MIDDLE_C
	 */// :~

Esempio invece della classe Shape resa abstract:

public abstract class Shape {
    public abstract double area();
    public abstract double circumference();
}

class Circle extends Shape {
    protected double r;
    protected static final double PI = 3.14159265358979323846;
    public Circle() { r = 1.0; }
    public Circle(double r) { this.r = r; }
    public double area() { return PI * r * r; }
    public double circumference() { return 2 * PI * r; }
    public double getRadius() { return r; }
}

class Rectangle extends Shape {
    protected double w, h;
    public Rectangle() { w = 0.0; h = 0.0; }
    public Rectangle(double w, double h) { this.w = w;  this.h = h; }
    public double area() { return w * h; }
    public double circumference() { return 2 * (w + h); }
    public double getWidth() { return w; }
    public double getHeight() { return h; }
}

Esempio di utilizzo:

Shape[] shapes = new Shape[3];          // Create an array to hold shapes.
shapes[0] = new Circle(2.0);            // Fill in the array...
shapes[1] = new Rectangle(1.0, 3.0);
shapes[2] = new Rectangle(4.0, 2.0);
double total_area = 0;
for(int i = 0; i < shapes.length; i++)
    total_area += shapes[i].area();     // Compute the area of the shapes.

Interface

Con la parola chiave interface porta il concetto di abstract class ancora piรน in lร . Con abstract permetteva di definire uno o piรน metodi non implementati, definendo l'interfaccia ma non la corrispettiva implementazione. L'implementazione รจ definita nelle sottoclassi. Con la parola chiave interface si crea una classe completamente astratta, una classe che non provvede alcuna implementazione. Permette al creatore di determinare il nome dei metodi, la lista degli argomenti, i tipi di ritorno, ma non l'implementazione del metodo.

Una interface dice: "Tutte le classi che implementano questa particolare interfaccia saranno con questa immagine". Quindi, ogni codice che utilizza una interface, sa quali metodi possono esser chiamati per quella interface, e basta. Cosรฌ l'interface รจ utilizzata per stabilire il protocollo tra le classi.

Per fare una classe che conforma a una particolare interface (o insieme di interfacce), si usa la parola chiave implements, che dice: "L'interfaccia indica come appare, ma ora io sto facendo vedere come funziona". Il diagramma delle classi per l'esempio degli strumenti:

Si puรฒ vedere dalla classi Woodwind e Brass che una volta che รจ implementata un'interfaccia, la implementazione diventa una classe ordinaria che puรฒ essere estesa nel modo normale.

interfaces/music5/Music5.java
//: interfaces/music5/Music5.java
// Interfaces.
package interfaces.music5;

import polymorphism.music.Note;
import static net.mindview.util.Print.*;

interface Instrument {
	// Compile-time constant:
	int VALUE = 5; // static & final
	// Cannot have method definitions:

	void play(Note n); // Automatically public

	void adjust();
}

class Wind implements Instrument {
	public void play(Note n) {
		print(this + ".play() " + n);
	}

	public String toString() {
		return "Wind";
	}

	public void adjust() {
		print(this + ".adjust()");
	}
}

class Percussion implements Instrument {
	public void play(Note n) {
		print(this + ".play() " + n);
	}

	public String toString() {
		return "Percussion";
	}

	public void adjust() {
		print(this + ".adjust()");
	}
}

class Stringed implements Instrument {
	public void play(Note n) {
		print(this + ".play() " + n);
	}

	public String toString() {
		return "Stringed";
	}

	public void adjust() {
		print(this + ".adjust()");
	}
}

class Brass extends Wind {
	public String toString() {
		return "Brass";
	}
}

class Woodwind extends Wind {
	public String toString() {
		return "Woodwind";
	}
}

public class Music5 {
	// Doesnโ€™t care about type, so new types
	// added to the system still work right:
	static void tune(Instrument i) {
		// ...
		i.play(Note.MIDDLE_C);
	}

	static void tuneAll(Instrument[] e) {
		for (Instrument i : e)
			tune(i);
	}

	public static void main(String[] args) {
		// Upcasting during addition to the array:
		Instrument[] orchestra = { new Wind(), new Percussion(), new Stringed(), new Brass(), new Woodwind() };
		tuneAll(orchestra);
	}
} /*
	 * Output: Wind.play() MIDDLE_C Percussion.play() MIDDLE_C Stringed.play()
	 * MIDDLE_C Brass.play() MIDDLE_C Woodwind.play() MIDDLE_C
	 */// :~

Da notare che non รจ importante se si fa l'upcasting a una classe "regolare" chiamata Instrument, una classe abstact chiamata Instrument o un interface chiamata Instrument. Il comportamento รจ identico. Infatti si puรฒ vedere che il metodo tune() non ha nessun indizio se Instrument sia una classe normale, astratta o una interface.

Altro esempio:

public interface Drawable {
    public void setColor(Color c);
    public void setPosition(double x, double y);
    public void draw(DrawWindow dw);
}

Implementazione di un'interface:

public class DrawableRectangle extends Rectangle implements Drawable {
    // New instance variables
    private Color c;
    private double x, y;
    // A constructor
    public DrawableRectangle(double w, double h) { super(w, h); }
    // Here are implementations of the Drawable methods.
    // We also inherit all the public methods of Rectangle.
    public void setColor(Color c) { this.c = c; }
    public void setPosition(double x, double y) { this.x = x; this.y = y; }
    public void draw(DrawWindow dw) { 
        dw.drawRect(x, y, w, h, c);
    }
}

Utilizzo di interface

Shape[] shapes = new Shape[3];          // Create an array to hold shapes
Drawable[] drawables = new Drawable[3]; // and an array to hold drawables.
// Create some drawable shapes.
DrawableCircle dc = new DrawableCircle(1.1);
DrawableSquare ds = new DrawableSquare(2.5);
DrawableRectangle dr = new DrawableRectangle(2.3, 4.5);
// The shapes can be assigned to both arrays.
shapes[0] = dc;   drawables[0] = dc;
shapes[1] = ds;   drawables[1] = ds;
shapes[2] = dr;   drawables[2] = dr;
// Compute total area and draw the shapes by invoking 
// the Shape and the Drawable abstract methods.
double total_area = 0;
for(int i = 0; i < shapes.length; i++) {
    total_area += shapes[i].area();    // Compute the area of the shapes.
    drawables[i].setPosition(i*10.0, i*10.0);
    drawables[i].draw(draw_window);    // Assume draw_window defined somewhere.
}

Implementazione di molte interface

public class DrawableScalableRectangle extends DrawableRectangle
                implements Drawable, Scalable {
    // The methods of the Scalable interface must be implemented here.
}

Definizione costanti nelle interface

class A { static final int CONSTANT1 = 3; }
interface B { static final int CONSTANT2 = 4; }
class C implements B {
    void f() { 
        int i = A.CONSTANT1;  // Have to use the class name here.
        int j = CONSTANT2;    // No class name here, because we implement
    }                         // the interface that defines this constant.
}

Estendere le interface

public interface Transformable extends Scalable, Rotateable, Reflectable { }
public interface DrawingObject extends Drawable, Transformable { }
public class Shape implements DrawingObject { ... }

Per esempio su uppercast e downcast (cast espicito) vedi:

Esempio su overridding dei metodi

Considera cosa succede quando creiamo una nuova istanza di GraphicCircle. Prima, il costruttore di GraphicCircle, , รจ invocato. Questo costruttore esplicitamente invoca il costruttore di Circle e il costruttore di Circle implicitamente chiama super() per invocare il costruttore della superclasse, Object. In questo modo, il codice del costruttore di Object viene eseguito prima, seguito dal codice del costruttore di Circle e finalmente dal codice del costruttore di GraphicCircle.

Vedi esempio .

Esempio:

Per un ripasso dei package, cosa sono, perchรฉ il codice รจ organizzato in package, vedi:

client/TestSubstitution.java
https://github.com/checksound/Polimorfismo
Circle e GraphicCircle - costruttori ed ereditarietร 
https://github.com/checksound/MethodOverridding-CircleAndCilinder
http://www3.ntu.edu.sg/home/ehchua/programming/java/J9c_PackageClasspath.html
codice
Aggregazione
UML classe padre-figlio
Type hierarchy
Overridding dei metodi
Upcasting
abstact Instrument
interface Instrument