I parametri e il sovraccarico

Vediamo dei dettagli sul passaggio dei parametri.

Intuitivamente, sono dati ``trasmessi" dal programma al metodo.

Prime due: già viste con le variabili scalari.

Oggi: con gli oggetti.


Memoria degli oggetti

Ogni metodo ha una sua zona di memoria in cui ci sono le variabili.

Queste zone non comprendono gli oggetti, che stanno da un'altra parte.

class Esempio {
  static void prova() {
    int x;
    Point q;
    q=new Point();
  }

  public static void main(String args[]) {
    int a;
    Point p;
    p=new Point();
  }
}

Una zona per ogni metodo, più una zona comune a tutti per gli oggetti.


Passare un oggetto a un metodo

Mettendo insieme: nel parametro formale ci va a finire l'indirizzo dell'oggetto passato.

Quindi: una modifica all'oggetto risulta nel programma.


Passare un oggetto: esempio

Cosa stampa questo programma?

import java.awt.*;

class Modifica {
  static void azzera(int x, Point p) {
    x=0;

    p.x=0;
    p.move(0,0);
  }

  public static void main(String args[]) {
    int x=12;
    Point p;
    p=new Point();
    p.move(10,20);

    azzera(x, p);

    System.out.println(x);
    System.out.println(p);
  }
}

Risposta sbagliata

Il programma stampa...

Prima fare il diagramma.

Poi dare la risposta.


Memoria, prima del metodo

Esiste solo la zona delle variabili di main

La zona per azzera viene creata solo quando si invoca azzera


Memoria, quando si invoca il metodo

Viene creata la nuova zona in cui ci sono i parametri formali x e p e le variabili locali (in questo caso, nessuna)

Nei parametri formali vengono messi i valori passati.

Valori passati: valore di x, e valore di p

Il valore di p è l'indirizzo in cui si trova l'oggetto.


L'esecuzione del metodo

x=0 mette 0 nella variabile locale.

p.x=0; e p.move(0,0); modificano l'oggetto il cui indirizzo sta in p


Cosa viene stampato?

Quando il metodo termina, viene eliminata la sua zona di memoria.


L'effetto globale

Quando passo una variabile, questa non viene modificata: x mantiene il valore 12

Quando passo un oggetto, questo può venire modificato p.x diventa 0

Non è questa la regola!

È una conseguenza della regola dei puntatori.


Il sovraccarico

Definire più metodi con lo stesso nome.

Serve quando la stessa operazione si può fare con diversi tipi di dato.


Tipo dei parametri

Metodo: fare qualcosa sulla base di parametri.

Cosa succede se uso i parametri sbagliati?

import java.awt.*;

class Errore {
  static void sbaglio(int x) {
    System.out.println(x);
  }

  public static void main(String args[]) {
    Point p;
    p=new Point();
    p.move(12,-4);

    sbaglio(p);
  }
}

Risposta: viene stampato questo errore:

Errore.java:13: sbaglio(int) in Errore -
  cannot be applied to (java.awt.Point)
    sbaglio(p);
    ^
1 error

Significato del messaggio

Errore.java:13: sbaglio(int) in Errore -
  cannot be applied to (java.awt.Point)
    sbaglio(p);
    ^
1 error
sbaglio(int) in ...
il metodo sbaglio esiste...
cannot be applied to (Point)
me è un metodo che non si aspetta di ricevere un Point

L'errore è dovuto al fatto che il programma ha inviato un Point, ma il metodo si aspetta un int

Non ha importanza che poi un Point si possa anche stampare!


Regola sui parametri

Nella invocazione, i parametri devono essere del tipo usato nella dichiarazione.

  static void sbaglio(int x) ...


  sbaglio(...)

Fra le parentesi ci devo mettere un int

Perchè?

Il valore passato lo devo mettere nella variabile x che è intera.


Regola generale

Il parametro attuale va messo nel parametro formale.

I tipi devono poter permettere questo assegnamento.

Esempio: se il paramtro formale è double, posso passare un intero.

Se il parametro formale è intero, non posso passare un double, perchè l'assegnamento non si può fare.


Metodi sovraccarichi

System.out.println() è un metodo

Però funziona su tutti i tipi!

Intuitivamente: potrei dover fare le stesse cose su tipi diversi.

Come definire un metodo che lavora su più tipi diversi:

definire un metodo per ognuno dei tipi

Esempio: un metodo per disegnare un punto

Per disegnare un punto, servono le sue coordinate.

Disegno un rettangolo di larghezza e altezza zero.

public class UnPunto extends Applet {
  static void disegnaPunto(Graphics g, Point p) {
    g.drawRect(p.x,p.y,0,0);
  }
    
  public void paint(Graphics g) {
    ...
  }
}

Se ho due interi?

Se voglio disegnare un punto, devo per forza creare un punto.

  public void paint(Graphics g) {
    Point p;
    p=new Point();
    p.move(12,43);

    disegnaPunto(g, p);
  }

Oppure: faccio un nuovo metodo

Alternativa: creo un metodo diverso:

  static void disegnaPuntoDueInteri
          (Graphics g, int x, int y) {
    g.drawRect(x,y,0,0);
  }

  public void paint(Graphics g) {
    disegnaPuntoDueInteri(g, 12, 32);
  }

Il sovraccarico

due metodi possono anche avere lo stesso nome, basta che il numero degli argomenti o il tipo di un argomento sia diverso

Dato che i due metodi per disegnare un punto hanno argomenti diversi, posso usare lo stesso nome.

Posso usare il nome disegnaPunto per tutti e due.

import java.awt.*;
import java.applet.*;

public class MetPunto extends Applet {
  static void disegnaPunto(Graphics g, Point p) {
    g.drawRect(p.x,p.y,0,0);
  }

  static void disegnaPunto(Graphics g, int x, int y) {
    g.drawRect(x,y,0,0);
  }

  static void paint(Graphics g) {
    Point p;
    p=new Point();
    p.move(12,43);

    disegnaPunto(g, p);

    disegnaPunto(g, 12, 32);
  }
}

Si può fare se il numero di argomenti è diverso.

Oppure se il tipo di almeno un argomento è diverso.

Qualche argomento può anche avere lo stesso tipo.


L'invocazione di un metodo

Il compilatore va a vedere il numero e il tipo dei parametri attuali (i valori che il programma manda al metodo)

Fra tutti i metodi con quel nome, sceglie quello che ha parametri di pari numero e tipo.


Cosa non si può fare

Non si possono definire due metodi che differiscono solo per il valore di ritorno.

  // errore!

  int metodo() { ... }
  double metodo() { ... }

Esercizio

Definire dei metodi per calcolare l'area di un rettangolo, dati:


Metodo per il rettangolo

Se ho un rettangolo:

  static int areaRect(Rectangle r) {
    return (r.width*r.height);
  }

Metodo per due punti

Se ho due punti:

  static int areaRect(Point min, Point max) {
    int w, h;

    w=max.x-min.x;
    h=max.y-min.y;

    return w*h;
  }

Si possono mettere insieme?

Attenzione! Bisogna sempre controllare che i due metodi si possano mettere insieme nello stesso programma.

Basta guardare le firme:

  static int areaRect(Rectangle)
  static int areaRect(Point,  Point)

Sia il numero che il tipo degli argomenti è diverso

Si possono usare i due metodi nello stesso programa,
anche se hanno lo stesso nome.


Un programma di prova

  public static void main(String args[]) {
    Rectangle r;
    r=new Rectangle();
    r.setBounds(10,10,20,20);

    System.out.println(areaRect(r));

    Point p, q;
    p=new Point();
    p.move(10,10);
    q=new Point();
    q.move(30,30);

    System.out.println(areaRect(p, q));
  }
}

Le due invocazioni sembrano uguali, ma in effetti si parla di due metodi diversi!


I valori di ritorno

I valori di ritorno dei metodi possono anche essere diversi.

class Prova {
  static int metodo(int x) {
    return 0;
  }

  static double metodo(double x) {
    return 0;
  }

  ...
}

Non fatevi ingannare dal nome

In generale: metodi sovraccarichi sono metodi diversi con lo stesso nome.

Dato che hanno lo stesso nome, per capire quale sto invocando, guardo il tipo/numero degli argomenti.

È per questo che esiste la regola che due metodi con lo stesso nome devono avere numero e/o tipo diverso di argomenti.


Esercizio

Definire un metodo che calcola la somma di due numeri.

I due numeri possono essere reali o interi (il valore di ritorno è di conseguenza)


Soluzione

Mi servono due metodi, uno per sommare interi e uno per sommare reali.

Le firme:

  static int somma(int, int)
  static double somma(double, double)

I tipi dei valori di ritorno non hanno importanza.

I tipi degli argomenti si.

Sono diversi: si possono usare i due metodi.


Soluzione

Scrivo il corpo dei due metodi.

class DueSomme {
  static int somma(int x, int y) {
    return x+y;
  }

  static double somma(double x, double y) {
    return x+y;
  }

  public static void main(String args[]) {
    System.out.println(somma(12,23));
    System.out.println(somma(12.3,23.1));
    System.out.println(somma(12,23.1));
  }
}

Nota: posso anche usare gli stessi nomi per i parametri formali (x e y) nei due metodi (ogni metodo vede solo le sue variabili).


Chi viene invocato da somma(12,23.1)?

Ho un intero e un reale.

Viene invocato quello dei reali.

Regola generale: viene invocato quello che ha esattamente gli stessi tipi, se c'è, altrimenti quello che li può assegnare tutti ai parametri formali.

In questo caso: non sono due interi, per cui non posso usare il metodo con i due interi.

Posso assegnare 12 e 23.1 a due reali, per cui uso il metodo con due reali.


Posso fare più di due metodi con lo stesso nome?

Si


Posso fare due metodi con lo stesso nome che fanno cose completamente diverse?

Si: sono a tutti gli effetti due metodi diversi:

class Diversi {
  static void aaa(int x) {
    System.out.println("Questa e' una stringa");
  }

  static void aaa(double x) {
    System.out.println(x+2);
  }

  public static void main(String args[]) {
    aaa(1);
    aaa((double) 1);
  }
}

Non è però consigliabile farlo.
(il programma non si capisce più)


Numero degli argomenti

Si possono fare due metodi diversi se hanno numero di argomenti diverso.

Esempio: metodo che stampa la somma di interi, al quale posso passare da zero a tre interi.


Soluzione

Devo fare quattro metodi diversi.

class SommaInt {
  static int somma() {
    return 0;
  }

  static int somma(int x) {
    return x;
  }

  static int somma(int x, int y) {
    return x+y;
  }

  static int somma(int x, int y, int z) {
    return x+y+z;
  }

  public static void main(String args[]) {
    System.out.println(somma(2,3));
    System.out.println(somma(2,3,4));
    System.out.println(somma());
    System.out.println(somma(2));
  }
}

Perchè usare il sovraccarico?

Se i metodi fanno la stessa cosa conviene.

Quando si scrive il metodo, non devo usare un nome di metodo diverso a seconda degli argomenti.

Se i metodi non fanno la stessa cosa, è meglio dare nomi diversi.


Esercizio

Estendere la classe Point con un metodo vicino che vede se un punto è a distanza minore o uguale di uno dall'oggetto di invocazione.

Il metodo riceve come parametri un punto oppure due interi che rappresentano le sue coordinate.

Non è un metodo statico, ma un metodo della classe.

Il sovraccarico si può fare anche per questi metodi.


Firme dei metodi

Devo fare un metodo che riceve un punto e un metodo che riceve due interi.

Devono però avere lo stesso nome e restituire tutti e due un booleano:

  boolean vicino(Point)
  boolean vicino(int, int)

Dato che hanno parametri diversi, si può fare.


Implementazione

Il calcolo è fatto come al solito.

import java.awt.*;

class NewPoint extends Point {
  boolean vicino(Point q) {
    return (
      Math.sqrt((this.x-q.x)*(this.x-q.x)+
                (this.y-q.y)*(this.y-q.y))
      <=1);
  }

  boolean vicino(int x, int y) {
    return (
      Math.sqrt((this.x-x)*(this.x-x)+
                (this.y-y)*(this.y-y))
      <=1);
  }
}

Invocare metodi

Ogni volta che ho un oggetto, posso invocare un metodo della sua classe.

Questo vale anche dentro i metodi:

  boolean vicino(Point q) {
    return (this.distance(q)<=1);
  }

NewPoint eredita il metodo distance da Point

Dato che this è un NewPoint, posso invocare il metodo.


Posso invocare questo metodo dall'altro?

Si può fare.

Se ho un oggetto, posso invocare tutti i metodi della classe (poi vedremo i modificatori di accesso).

import java.awt.*;

class NewPoint extends Point {
  boolean vicino(Point q) {
    return (this.distance(q)<=1);
  }

  boolean vicino(int x, int y) {
    Point p;
    p=new Point();
    p.move(x,y);

    return this.vicino(p);
  }
}

Nel metodo vicino(Point) ho invocato il metodo distance (altro metodo di NewPoint)

Stessa cosa per vicino(int, int): ho invocato il metodo vicino(Point).

Pensate sempre che si tratti di un altro metodo, che però ha lo stesso nome.