Oggetti e classi

Progettazione e Sviluppo del Software

C.D.L. Tecnologie dei Sistemi Informatici

Danilo Pianini — danilo.pianini@unibo.it

Gianluca Aguzzi — gianluca.aguzzi@unibo.it

Angelo Filaseta — angelo.filaseta@unibo.it

Compiled on: 2025-09-07 — versione stampabile

back

Riconoscimenti

  • Questo materiale è ampiamente basato su quello realizzato dai Prof. Mirko Viroli e Roberto Casadei, che ringrazio.

  • Ogni errore riscontratovi è esclusiva responsabilità degli autori di questo documento.

Outline

Goal della lezione

  • Illustrare i concetti base del paradigma object-oriented
  • Mostrare un primo semplice programma Java
  • Fornire una panoramica di alcuni meccanismi Java

Argomenti

  • Oggetti e riferimenti
  • Tipi primitivi
  • Classi, metodi e campi
  • Accenno a package e librerie
  • Stampe a video
  • Primo semplice programma Java

Astrazione OO

  • Everything is an object. Un oggetto è una entità che fornisce operazioni per essere manipolata.
  • Un programma è un set di oggetti che si comunicano cosa fare scambiandosi messaggi. Questi messaggi sono richieste per eseguire le operazioni fornite.
  • Un oggetto ha una memoria fatta di altri oggetti. Un oggetto è ottenuto impacchettando altri oggetti.
  • Ogni oggetto è istanza di una classe. Una classe descrive il comportamento dei suoi oggetti.
  • Tutti gli oggetti di una classe possono ricevere gli stessi messaggi. La classe indica tra le altre cose quali operazioni sono fornite, quindi per comunicare con un oggetto basta sapere qual è la sua classe.

Classi e Oggetti

Due forni a microonde dello stesso modello sono due oggetti distinti, ma hanno la stessa struttura e gli stessi comportamenti.

La classe in OOP è la descrizione di un tipo di oggetto, ne definisce:

  • struttura (come è fatta, quali sono gli elementi che la compongono)
  • comportamento (cosa può fare, quali operazioni sono possibili)
    • il comportamento potrebbe prevedere l’interazione con altri oggetti

A partire da una classe, si possono creare degli oggetti

  • Si dice che l’opggetto è istanza della classe

Un programma OOP è un insieme di classi. Il comportamento è definito a partire da un punto di ingresso (il main), e si sviluppa attraverso l’interazione tra gli oggetti.

Flusso:

  • HelloWorld in Java che mostra il punto d’ingresso senza codice
  • Interazione di base fra il programma e il sistema utilizzando System.out.println
    • Esiste un oggetto sistema (sempre disponibile e creato dalla JVM) che si chiama System.out e rappresenta lo standard output
    • Questo oggetto può ricevere messaggi print (funziona come printf in C) che stampano a video quello che viene fornito e messaggi println che vanno anche a capo
  • Abbiamo usato una Stringa "...": esistono alcuni tipi di dato che sono forniti dal sistema

Tipi primitivi e operatori

  • In java i tipi di dato si dividono in due categorie: le classi e i tipi primitivi
    • Infatti Java non è un linguaggio OOP “puro” (i primitivi esistono per motivi di efficienza)
  • descrizione dei primitivi
  • operatori su primitivi (+ - * / % < > <= >= == != && || !)
  • descrizione delle stringhe (che sono classe)
  • concatenazione di stringhe
  • definizione di variabili con tipo esplicito o con var

Esempio (a parole) di programma OOP

TODO: Gianlu metti un UML

Programma che scalda un piatto di pasta e lo mangia:

  • Classi: MicrowaveOven, Food
  • Programma:
    1. Crea un oggetto di tipo MicrowaveOven, modello "HotPoint Ariston", nome: oven
    2. Crea un oggetto di tipo Food, tipo: "Pasta al sugo", nome: pasta
    3. Invia il messaggio insert(pasta) all’oggetto oven
    4. Invia il messaggio setTime(60) all’oggetto oven
    5. Invia il messaggio setPower(800) all’oggetto oven
    6. Invia il messaggio start() all’oggetto oven
    7. Invia il messaggio getContent() all’oggetto oven
    8. Invia il messaggio consume() all’oggetto pasta

Flusso:

  • Uml del programma di esempio
  • Costruzione di classi (solo campi)
  • Istanziazione di oggetti
  • Programma di esempio quindi quello sopra ma senza metodi
  • Individuare la scomodità: vogliamo controllare che la potenza non superi 800W.
  • introdurre if (else/else if)
  • Il controllo di potenza lo deve fare il microonde, non il programma: se provate a scaldare la pasta a 8KW, il microonde semplicemente non ve lo fa fare
  • Aggiunta di metodi alla classe

Ogni oggetto ha una interfaccia

Classi, istanze, metodi, interfaccia

  • Oggetti simili sono istanze della stessa classe, o tipo
  • La classe definisce i messaggi ricevibili, attraverso metodi
  • L’insieme dei metodi prende il nome di interfaccia
  • Un messaggio ha effetto su stato e comportamento dell’oggetto

Everything is an object

Riferimenti ad oggetti

  • Nessun meccanismo per accedere ai dati per valore o puntatore!
  • Le variabili conterranno dei riferimenti agli oggetti veri e propri, sono quindi dei nomi “locali” utilizzabili per denotare l’oggetto
// Creo un oggetto String, e lo assegno al nome s
// questo oggetto stringa rappresenta la sequenza vuota di caratteri
String s = new String();

// In generale si crea un oggetto con l'operatore "new"
// Accetta 0,1 o più argomenti, a seconda del tipo: valori primitivi (numeri), string literals, o oggetti
String s2 = new String("prova");
Point2D p = new Point2D(10.5, 20.3);
Object o = new Object();

Variabili, oggetti, e valori primitivi

Concetti base

  • Variabile: un contenitore con nome (come in C), usabile per denotare un oggetto
  • Valore primitivo: p.e. un numero, anche assegnabile ad una variabile
String s = new String("prova");

// Un caso ad-hoc di Java, equivalente a:
// String s2 = new String("casa");
String s2 = "casa";

// Definisco il nome s4, ma non lo inizializzo
String s4;
// ora assegno s4, da qui in poi è utilizzabile
s4 = "auto";

// Il nome i è assegnato al valore primitivo 5
int i = 5;

“(Almost) Everything is an object”

Tipi primitivi: rappresentano i valori “built-in”

Assomigliano molto a quelli del C, ma hanno dimensioni fissate

Type name Size (bits) Minimum Maximum
boolean
char 16 \u0000 \uFFFF
byte 8 $-128$ $128$
short 16 $-2^{15}$ $2^{15}-1$
int 32 $-2^{31}$ $2^{31}-1$
long 64 $-2^{63}$ $2^{63}-1$
float 32 IEEE754 IEEE754
double 64 IEEE754 IEEE754
void
  • I boolean possono valere true o false
  • Altre classi di libreria (BigDecimal, BigInteger) gestiscono numeri di dimensione/precisione arbitraria

Una prima classificazione dei tipi

Java types

Classes

Object

String

...

Primitive types

int

boolean

short

char

byte

long

float

double

void

Variabili e tipi

// assegnamenti con tipi primitivi
int i = 5;
double d = 145e-20;
boolean b = true;

// assegnamenti con classi
Object o = new Object(); 
String s = "altra stringa";
Point2D p = new Point2D(10.4, 20.3);

// assegnamento da/a variabile
int other = i;
Point2D q = p;

// assegnamento a null
Object on = null;

Costrutto var: local variable type inference (Java 10+)

  • usabile nelle variabili locali (a funzioni/metodi) per avere maggiore concisione
  • il compilatore capisce (inferisce) il tipo della varibile dall’espressione assegnata
  • non abusarne, specialmente all’inizio!
// assegnamenti con tipi primitivi
var i = 5;
var d = 145e-20;
var b = true;

// assegnamenti con classi
var o = new Object(); 
var s = "altra stringa";
var p = new Point2D(10.4, 20.3);

// assegnamento da/a variabile
var other = i;
var q = p;

Oggetti e memoria

Gestione della memoria

  • tutti gli oggetti sono allocati nella memoria heap
  • le variabili allocate nello stack, nei rispettivi record di attivazione
  • le variabili di tipi primitivi contengono direttamente il valore
  • le variabili che contengono oggetti in realtà hanno un riferimento verso lo heap
  • nota: ancora non sappiamo cosa contiene un oggetto
// assegnamenti con tipi primitivi: contengono VALORI!
var i = 5;
var d = 145e-20;
var b = true;
// assegnamenti con classi contengono RIFERIMENTI!
var o = new Object(); 
var s = "altra stringa";
var p = new Point2D(10.4, 20.3);
var other = i; // VALORE!
var q = p; // Stesso RIFERIMENTO!
Object on = null; // Riferimento speciale al valore null

Visibilità

“Scope” delle variabili

  • È simile a quello di C
  • variabili dentro un blocco non sono visibili fuori
  • differenza rispetto a C: variabili non inizializzate non sono utilizzabili!

Tempo di vita degli oggetti

  • finito lo scope di una variabile, l’oggetto continua a esistere
  • verrà deallocato automaticamente dal sistema se non più usato
    • se, direttamente o indirettamente, nessuna variabile lo può raggiungere
    • un componente della JVM, il garbage collector, è preposto a questo compito

Principali costrutti dell’object-orientation

Costruire classi

Premesse

  • la classe è l’unità fondamentale di programmazione OO
  • progettare e costruire classi correttamente sarà l’obiettivo del corso
  • incominciamo descrivendo la loro struttura generale
  • solo nelle prossime lezioni daremo linee guida definitive

Cos’è una classe

  • è un template (stampino) per generare oggetti di una certa forma
  • ne definisce tipo, struttura in memoria e comportamento

Classe vs. oggetto

  • classe: è una descrizione (parte di programma)
  • oggetto: è una entità a tempo di esecuzione, è istanza di una classe

Struttura di una classe

Nome della classe

È anche il nome del tipo

Membri della classe

Campi (fields)

  • descrivono la struttura/stato dell’oggetto in memoria

Metodi (methods)

  • descrivono i messaggi accettati e il comportamento corrispondente

Classi: un po’ di codice

Costruzione di classi

class A { // A è il nome della classe
  ... // qui si riporta il suo contenuto
}

class AltroEsempioDiClasse { // Nota il PascalCase
  ...
}

Uso

// codice cliente
A obj1 = new A(); // creo un oggetto di A, con nome obj1
A obj2 = new A(); // creo un altro oggetto di A
AltroEsempioDiClasse obj3 = new AltroEsempioDiClasse();
A obj4; // variabile obj4 non inizializzata
obj4 = new A(); // ok
obj4 = new AltroEsempioDiClasse(); // NO!! Errore semantico

Campi

Elementi costitutivi dei campi

  • i campi di una classe assomigliano ai membri di una struct del C
  • ognuno è una sorta di variabile (nome + tipo)
    • per i campi non è usabile il costrutto var!
  • ve ne possono essere 0,1, molti
  • lo stato di un oggetto è l’attuale valore associato ai campi
  • potrebbero essere valori primitivi, o altri oggetti

Valore di un campo

  • impostabile al momento della sua dichiarazione
  • se non inizializzato vale:
    • 0 per i tipi numerici
    • false per i booleani
    • null per le classi
  • accessibile da codice cliente con notazione object.field
    • c.d. “dot notation”

Campi: Un semplice esempio giocattolo

Classe

class A {
  int i;
  int j = 2;
  Object o;
}

Uso

A obj = new A();
int a = obj.i; // a varrà 0
int b = obj.j; // b varrà 2
obj.i = 10; // modifico lo stato di obj
int d = obj.i; // d varrà 10
obj.o = new Object(); // riassegno obj.o
A obj2 = new A();
obj.i = obj2.i; // quanto varrà ora obj.i?

Campi: l’esempio Point3D

Classe

class Point3D {
  double x;	// Nota, l'ordine dei campi è irrilevante
  double y;
  double z;
}

Uso

Point3D a = new Point3D(); // Creo due punti, di nome a e b
Point3D b = new Point3D();
a.x = 10.0;	// Imposto lo stato di a
a.y = 20.0;
a.z = 30.0;
b.x = a.x * 2; // Imposto lo stato di b
b.y = a.y * 2; // .. a partire da quello di a
b.z = a.z * 2;
int mod2a = a.x * a.x + a.y * a.y + a.z * a.z;
int mod2b = b.x * b.x + b.y * b.y + b.z * b.z;
boolean aGreater = (mod2a > mod2b); // false

Metodi

Elementi costitutivi dei metodi

  • i metodi di una classe assomigliano a funzioni (del C)
  • ognuno ha una intestazione (o signature) e un corpo
    • a sua volta l’intestazione ha il nome, tipo di ritorno, argomenti
  • di metodi ve ne possono essere 0,1, molti
  • definiscono il comportamento dell’oggetto

Significato di un metodo

  • codice cliente richiama un metodo con notazione object.method(arguments)
    • Di nuovo, dot notation! (stavolta con le parentesi)
  • corrisponde ad inviare un messaggio a object
  • object è chiamato il receiver del messaggio (o dell’invocazione)
  • il comportamento conseguente è dato dall’esecuzione del corpo
  • il corpo può leggere/scrivere il valore dei campi

Metodi: Un esempio giocattolo

Classe

class Adder {
  int total;

  void add(int a){ // input "int a"
    total = total + a;              
  }

  int getValue(){ // intestazione funzione
    return total; // corpo funzione
  }
}

Uso

Adder adder = new Adder();
int v = adder.total; // vale 0
adder.add(10); // modifico adder
adder.add(20); // modifico adder
int v2 = adder.total; // vale 30
int v3 = adder.getValue(); // vale 30

La variabile speciale this

  • dentro ad un metodo si può accedere agli argomenti o ai campi
  • per rendere meno ambigua la sintassi, Java fornisce una variabile speciale denotata con this
    • contiene il riferimento all’oggetto che sta gestendo il messaggio
  • per motivi di leggibilità, viene spesso omessa
    • all’inizio, è opportuno usarla sempre!
class Adder {
  int total;

  void add(int a){ // input "int a"
    this.total = this.total + a;              
  }

  int getValue(){ // intestazione funzione
    return this.total; // corpo funzione
  }
}

Metodi: altro esempio Point3D

class Point3D { // Class declaration
    double x; // 3 campi
    double y;
    double z;

    void build(double a, double b, double c){
        this.x = a;
        this.y = b;
        this.z = c;
    }

    double getNormSquared(){
        return this.x * this.x + this.y * this.y + this.z * this.z;
    }

    boolean equal(Point3D q){	// true if two points are equal
        return this.x == q.x && this.y == q.y && this.z == q.z;    
    }
}
// codice cliente
Point3D p = new Point3D(); // Create a new point p
p.build(10.0, 20.0, 30.0); // set up the point 
Point3D q = new Point3D(); // create a new point q
q.build(10.0, 20.0, 31.0); // set up point q
double m2 = p.getNormSquared(); // get the squared norm of m2
boolean samePoint = p.equal(q); // chiedo a p se è uguale a q

Programmi Java

Programmi Java

Elementi costitutivi dei programmi Java

  • librerie (insiemi di classi)
    • Incluse nel Java Development Kit
    • esterne (nostre o di altri, vedremo come importarle)
  • un insieme di classi che costituiscono l’applicazione
  • almeno una di tali classi ha un metodo speciale main
    • Ma possono essercene più d’una
  • un main è un punto d’accesso di un programma

Il metodo main

Il main deve avere la seguente dichiarazione:

public static void main(String[] args)

tre concetti che spiegheremo in dettaglio nel prosieguo:

  • public indica il fatto che debba essere visibile “a tutti”
  • static indica che non è un metodo dell’oggetto, ma della classe
  • String[] indica il tipo “array di stringhe”

Package e librerie Java: organizzazione

Librerie di Java

Package

  • un package è un contenitore di classi con uno scopo comune, di alto livello
  • tipicamente un package contiene qualche decina di classi
  • i package sono organizzati ad albero, con notazione name1.name2.name3
  • Package principali: java.lang, java.util, java.io

Package, moduli e librerie Java

Moduli (Java 9+)

  • un modulo definisce un frammento di codice “autonomo”:
    • con chiara interfaccia ed esplicite dipendenze da altri
    • esempi: java.base, java.desktop, java.sql, java.xml
  • le librerie per Java 9+ possono essere distribuite in uno o più moduli
  • un modulo è costituito internamente da uno o più package
  • ad esempio, java.base contiene i package principali che useremo

Impatto sulla programmazione “base”

  • il concetto di modulo non impatta i sorgenti, ma “solo” la gestione dei componenti: per il momento non ce ne occuperemo perché il JDK fornisce “di default” accesso a tutti i moduli che ci servono (java.*)
  • il concetto di package, invece, impatta i sorgenti: il nome completo di una classe include il package in cui si trova

Package, moduli e librerie Java: uso

Importare una classe di “libreria”

  • per usare le classi di una libreria prima le si importa
  • lo si fa con la clausola import, da usare all’inizio del sorgente
    • importo la singola classe: import java.util.Date;
    • importo l’intero package: import java.util.*;
    • importazione di default: import java.lang.*;
  • senza importazioni, ogni classe andrebbe sempre qualificata indicandone anche il package completo:
    • java.util.Date now = new java.util.Date();
  • importare evita quindi solo di dover indicare ogni volta il package

Classi (e funzionalità) “deprecate”

  • dichiarate come “scadute”, ossia preferibilmente “da non usare più”
    • Tipicamente perché il loro funzionamento è stato mal progettato
    • Potrebbero essere rimosse in una nuova versione di Java!
  • noi le utilizzeremo per solo scopo didattico java.util.Date

Stampe su terminale

Il metodo System.out.println

  • System è una classe nel package java.lang
  • out è un suo campo che rappresenta lo standard output
    • statico, ossia uguale per ogni istanza di System
  • println è un metodo che accetta una Stringa e la stampa
  • l’operatore + concatena stringhe a valori

“Hello world” Java Program

// This class should be into a file named Hello.java
class Hello {
    public static void main(String[] args){
        System.out.println("Hello World!");
    }
}

Compilazione ed esecuzione

  • con un editor di testo si scrive la classe in un file Hello.java
  • si compila la classe col comando: javac Hello.java
  • se non ci sono errori, viene generato il bytecode Hello.class
  • si esegue il programma con: java Hello
  • la JVM cerca la classe Hello, e ne esegue il main
    • Nota: la JVM cerca la classe Hello, non il file Hello.class

Librerie, oggetti, e stampe

import java.util.Date;
import java.util.Random;
// java.lang.System non va importata

class PrintingObjects{
    public static void main(String[] args){
        // Date
        Date d = new Date();
        System.out.println("Today: " + d);
        System.out.println("Milliseconds since 1/1/1970: " + d.getTime());
        // Random number generator
        Random r = new Random();
        System.out.println("Random number: " + r.nextInt());
        System.out.println("Another random: " + r.nextInt());
        System.out.println("A random in 0..99: " + r.nextInt(100));
        // System properties
        String javaVersion = System.getProperty("java.version");
        String osname = System.getProperty("os.name");
        String usrdir = System.getProperty("user.dir");
        System.out.println("Running Java " + javaVersion + " in " + osname + " from " + usrdir);
    }
}

Costruire e provare classi

class A {

    int i;

    void print(){
        System.out.println("Sono un oggetto della classe A");
        System.out.println("Il mio valore di i è: " + this.i);
    }
}

class Print {

    public static void main(String[] args) {
        A obj = new A();
        obj.i = 12;
        obj.print();
    }
}
  • Dopo aver compilato, si esegue la classe col main:
    • java Print

Classi…

class Point3D {

    double x;
    double y;
    double z;

    void build(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    double getNormSquared() {
        return this.x * this.x + this.y * this.y + this.z * this.z;
    }
    
    boolean equal(Point3D other) {
        return this.x == other.x && this.y == other.y && this.z == other.z;    
    }
}

…e classi clienti

class UsePoint3D{   

    public static void main(String[] args) {
        Point3D p = new Point3D();
        p.build(10.0, 20.0, 30.0);
        Point3D q = new Point3D();
        q.build(10.0, 20.0, 31.0);
        System.out.println("p's squared norm: " + p.getNormSquared());
        System.out.println("Are p and q the same point? " + p.equal(q));
    }
}
  • si compilano separatamente
    • Siccome per compilare UsePoint3D.java serve la classe Point3D, il file Point3D.java va compilato prima
  • si possono anche compilare insieme, dandoli entrambi al compilatore
    • javac UsePoint3D.java Point3D.java
    • o, usando la sintassi “glob”: javac *.java
  • si esegue la classe col main
    • java UsePoint3D

Preview del prossimo laboratorio

Obiettivi

  • familiarizzare con la compilazione da linea di comando in Java
  • fare qualche esercizio con la costruzione e uso di classi