Riassunto Programmazione ad Oggetti
Dennis Rapaccini
27 gennaio 2022
Indice
1 Paradigmi di Java 3
1.1 Complilazione, interpretazione e JVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.1 Gestione della memoria nella JVM . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.1.2 Riferimenti e valori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.1.3 Memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 I tre paradigmi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.1 Incapsulamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.2 Ereditarietà . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.3 Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2 Classi 7
2.1 Costruttore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 Parola chiave this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3 Metodi e classi in dettaglio 7
3.1 Metodi ausiliari . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.1.1 Cosa succede quando si invoca un metodo? . . . . . . . . . . . . . . . . . . . . . 7
3.2 Costrutto for-each . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.3 Overloading (sovraccarico) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.3.1 Overloading di metodi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.3.2 Overloading di costruttori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.4 Parola chiave static . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.5 Parole chiave final . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.6 Vararg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4 Ereditarietà 10
4.1 Fondamenti di ereditarietà . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.1.1 Accesso di membri ed ereditarietà . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4.2 Utilizzo di super . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4.2.1 Utilizzo di super per chiamare i costruttori della superclasse . . . . . . . . . . . . 11
4.2.2 Secondo utilizzo di super . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4.3 Overriding (sovrapposizione) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4.3.1 Perché fare l’overriding dei metodi . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4.4 Ereditarietà e controllo dei tipi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4.5 Classi astratte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4.6 Utilizzo di final con l’ereditarietà . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4.6.1 Utilizzo di final per impedire l’overriding . . . . . . . . . . . . . . . . . . . . . . 14
4.6.2 Utilizzo di final per impedire l’ereditarietà . . . . . . . . . . . . . . . . . . . . . . 15
4.7 La classe Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1
5 Package e interfacce 15
5.1 Package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
5.2 Protezione dell’accesso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
5.3 Interfacce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
5.3.1 Definizione di un’interfaccia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
5.3.2 Implementazione di interfacce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
5.4 Interfacce vs Classi astratte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
6 Gestione delle eccezioni 17
6.1 Basi sulla gestione delle eccezioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
6.2 Tipi di eccezioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
6.3 Eccezioni non intercettate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
6.4 Utilizzo di try e catch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
6.4.1 Visualizzazione della descrizione di un’eccezione . . . . . . . . . . . . . . . . . . . 18
6.5 Clausole catch multiple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
6.6 Dichiarazioni try annidate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
6.7 Throw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
6.8 Throws . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
6.9 Finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
6.10 Creazione di sottoclassi di eccezioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
7 Strutture dati 20
7.1 Classe Vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
7.2 Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
7.2.1 Iteratori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
7.3 HashMap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
8 Input/Output 22
8.1 Fondamenti su I/O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
8.2 Lettura e scrittura di file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
8.2.1 Input/Output bufferizzato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
8.2.2 Input/Output formattato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
8.2.3 Serializzazione degli oggetti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
9 Javadoc 25
10 JSON 25
11 API 26
11.1 Postman . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
12 Maven 26
13 Framework 26
13.1 Spring Boot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
13.2 JUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
13.3 JNI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
14 Definizioni comuni 28
2
1 Paradigmi di Java
1.1 Complilazione, interpretazione e JVM
Il linguaggio Java si basa su un approccio che combina compilazione (in bytecode) e interpretazione
(del bytecode).
Il bytecode può essere visto come l’assembly di una macchina virtuale, un calcolatore (com-
puter) ipotetico che ha le caratteristiche simili (semplificate) a quelle delle architetture hardware più
comuni.
Il bytecode è un linguaggio di basso livello (tipo assembly) e non è legato ad una particolare architet-
tura hardware.
L’interprete del bytecode è dette Java Virtual Machine (JVM). Il bytecode non è un codice diret-
tamente eseguibile, ma è un codice per una macchina virtuale (cioè un interprete) che si incarica
dell’esecuzione effettiva.
Le classi compilate in bytecode sono portabili su qualsiasi macchina per cui esista una macchina
virtuale Java (JVM).
Il programma finale viene assemblato a runtime (cioè il linking delle classi) e dipende dall’ambiente di
esecuzione.
Perciò si possono costruire programmi indipendenti dalla piattaforma hardware e software (purché
esista una macchina virtuale) e le classi compilate in bytecode assomigliano a componenti software
ricombinabili a piacere.
La macchina virtuale Java esegue come prima cosa il metodo main della classe di bootstrap.
Quindi:
• Compilatore: un programma che prende in input il programma sorgente e produce in output
il corrispondente programma in linguaggio macchina, eseguibile successivamente dal computer.
• Interprete: un programma che prende in input il programma sorgente, traduce un comando
per volta e lo esegue man mano. 3
Approfondimento: Compilazione e interpretazione
Linguaggi come il C, C++, Delphi, Visual Basic sono linguaggi compilati e seguono
questi passi: si scrive il codice in un editor, al più utilizzando un ambiente di sviluppo IDE che
ne facilita la creazione, questo codice viene poi controllato per verificare che non ci siano errori
e poi viene compilato, ovvero ogni istruzione viene trasformata nel corrispondente codice in
linguaggio macchina che può essere, cosı̀, eseguito dal processore; questi sono i linguaggi com-
pilati che vengono detti anche linguaggi imperativi, ed hanno il vantaggio di prestazioni migliori.
I linguaggi interpretati, invece, seguono una strada diversa, il codice sorgente viene, ap-
punto, interpretato al volo e vengono, quindi, eseguite le istruzioni cosı̀ come descritte nel
codice sorgente; un esempio su tutti è il PHP il cui codice viene “elaborato” e restitui-
sce una pagina HTML pura. La potenza di questo genere di linguaggi è, di fatto, l’alta
portabilità e l’immediatezza tra quello che scriviamo e quello che viene presentato all’e-
secuzione del programma, ma rimangono dei problemi come la ricerca di errori nel codice
sorgente o il carico di lavoro maggiore per il processore (che ogni volta deve elaborare la pagina).
Un linguaggio che è a metà strada tra queste metodologie è Java che è sia compilato
che interpretato; il codice sorgente viene compilato in un formato intermedio (chiamato
bytecode), il quale a sua volta viene interpretato dalla Java Virtual Machine (JVM), che ha il
compito di interpretare “al volo” le istruzioni bytecode in istruzioni per il processore; la JVM
viene sviluppata per ogni Sistema Operativo e permette di astrarre la macchina virtuale creata
dal SO ad un livello di standardizzazione superiore (data di fatto dalla creazione della virtual
machine sopra un’altra virtual machine) che rende, in pratica, JAVA altamente portabile.
Questa metodologia implica la possibilità di controllare eventuali errori del codice sorgente
(grazie alla compilazione), di creare programmi relativamente leggeri (il bytecode è un formato
che crea file di dimensioni ragionevoli), ma ha la pecca di avere delle prestazioni non proprio
soddisfacenti, questo perché il codice viene interpretato dalla JVM che a sua volta deve delegare
l’esecuzione vera e propria al Sistema Operativo.
1.1.1 Gestione della memoria nella JVM
La memoria usata dalla JVM è concettualmente divisa in tre parti:
• Ambiente delle classi: area di memoria in cui vengono caricate (allocate) tutte le classi che
costituiscono il programma.
Nell’ambiente delle classi quindi:
– vengono memorizzati il codice dei metodi e le variabili statiche di tutte le classi del pro-
gramma
– sono le parti condivise dai vari oggetti della classe
– le variabili statiche sono utilizzabili anche in assenza di oggetti
• Stack: area di memoria in cui vengono caricati (allocati) i record di attivazione dei metodi, e
quindi tutte le variabili locali.
Nello stack quindi:
– vengono memorizzate le variabili locali dei metodi in esecuzione
– per le variabili di tipi primitivi viene memorizzato il valore (esempio: somma)
– per le variabili di tipo classe viene memorizzato un riferimento (indirizzo di memoria di un
oggetto)
• Heap: area di memoria in cui vengono caricati (allocati) tutti i vari oggetti creati nel programma,
man mano che vengono creati.
Nell’heap quindi: 4
– per ogni oggetto creato vengono memorizzate le variabili d’istanza (ossia, le variabili non
statiche)
– ogni oggetto nell’heap contiene anche il nome della classe di appartenenza
1.1.2 Riferimenti e valori
Rinfreschiamo la memoria (la nostra, non quella della JVM).
Quando un parametro viene passato per riferimento, il chiamante e il chiamante usano la stessa
variabile per il parametro. Se la chiamata modifica la variabile del parametro, l’effetto è visibile alla
variabile del chiamante (tipo puntatore in C).
Quando un parametro viene passato per valore, il chiamante e il chiamante hanno due variabili indi-
pendenti con lo stesso valore. Se la chiamata modifica la variabile del parametro, l’effetto non è visibile
al chiamante (copia).
In Java le variabili associate a tipi primitivi sono passate a un metodo come valore.
Cioè viene fatta una copia e questa e’ passata al metodo. Modifiche al valore passato non influenzano
la variabile nel codice che richiama il metodo. Quindi ad esempio se si assegna un variabile int x =
e poi si fa succede che sia che hanno valore 15. Se poi si fa il valore di
15 int y = x, y x y = 40,
cambia, ma quello di rimane 15.
y x
Una variabile di un tipo classe contiene invece un riferimento a un oggetto.
Quindi ad esempio se si crea una variabile di tipo Rettangolo (che ha come attributi base e altezza)
e se ne crea un oggetto: la creazione dell’og-
Rettangolo primo = new Rettangolo(10,30),
getto primo alloca un nuovo oggetto e assegna un riferimento alla variabile. Se poi si fa Rettangolo
tale assegnamento fa si che e facciano riferimento alla stes-
secondo = primo, secondo primo
sa cella di memoria dove è contenuto l’oggetto, infatti se poi si fa anche
secondo.base = 186,
sarà 186.
primo.base
Lo stesso discorso vale anche quando si passa un oggetto a un metodo come parametro.
Una conseguenza del fatto che le variabili di tipo classe contengono riferimenti, è che l’operatore di
confronto non si comporta (con gli oggetti) come uno si potrebbe aspettare, infatti se si prova a
==
fare oggetto1 == oggetto2 ad esempio in un if, questo è true solo se i due fanno riferimento allo stesso
oggetto, perciò si usa equals().
1.1.3 Memoria
In Java tutte le entità sono allocate dinamicamente (per esempio non esistono variabili globali).
Tuttavia, solo l’allocazione della memoria (creazione di oggetti) viene gestita esplicitamente,
La deallocazione è gestita dalla macchina virtuale (garbage collection)
Garbage collection Per Garbage Collection si intende una modalità automatica di gestione della
memoria, mediante la quale un sistema operativo, o un compilatore e un modulo di run-time liberano
porzioni di memoria non più utilizzate dalle applicazioni. In altre parole, il garbage collector annoterà
le aree di memoria non più referenziate, cioè allocate da un processo attivo, e le libererà automatica-
mente.
Questo meccanismo ha provocato un notevole cambio nello stile di programmazione dei linguaggi che
lo implementano. Infatti non è più necessario richiedere esplicitamente la liberazione della memoria
utilizzata da un oggetto, ovvero terminare tale oggetto in modo deterministico, ma si lascia che il
sistema esegua questa operazione automaticamente, nel momento in cui lo riterrà più opportuno al
fine di migliorare le prestazioni complessive.
Il Mark and Sweep è un algoritmo di garbage collection che prevede l’interruzione dell’esecuzione,
in una prima fase (mark ) si marcano tutti gli oggetti accessibili. In una seconda fase (sweep) si rimuo-
vono gli oggetti non marcati (e si rimuove il marcatore dagli altri).
Si usa quando gli oggetti sono molti e globalmente accessibili.
Il Reference Counting è un algoritmo di garbage collection che non prevede l’interruzione dell’ese-
cuzione. Funzionamento: 5
Si incrementa/decrementa un contatore quando un oggetto viene referenziato/dereferenziato.
Vengono rimossi gli oggetti con contatore a zero.
Viene usato quando gli oggetti sono pochi e/o non raggiungibili globalmente (per esempio sistema
distribuito, ossia un insieme di processori indipendenti interconnessi da una rete di comunicazione).
1.2 I tre paradigmi
1.2.1 Incapsulamento
E’ il meccanismo che collega il codice e i dati che manipola, mantenendoli al sicuro da interferenze
esterne e da utilizzi impropri. Si può pensare all’incapsulamento come un rivestimento protettivo che
impedisce l’accesso arbitrario a codice e dati da parte di altro codice definito fuori dal rivestimento.
L’accesso al codice e ai dati all’interno del rivestimento è strettamente controllato mediante un’inter-
faccia ben definita.
Si può fare un paragone con il mondo reale pensando alla trasmissione automatica (cambio automati-
co) di una automobile. Questa incapsula centinaia di informazioni su motore, accelerazione, pendenza
della strada ecc... ma l’utente ha solo un metodo per influenzare tale incapsulamento complesso: la
leva del cambio.
Il Java la base dell’incapsulamento è la classe. All’interno di questa si può contrassegnare ciascun
metodo o variabile come privato o pubblico. L’interfaccia pubblica di una classe rappresenta tutto ciò
che gli utenti esterni hanno necessità di sapere, o possono sapere. Codice o dati non membri della
classe non possono accedere a metodi o variabili private di quella classe, possono farlo mediante metodi
pubblici della classe, evitando cosı̀ azioni scorrette.
1.2.2 Ereditarietà
L’ereditarietà è il processo con cui un oggetto acquisisce le proprietà di un altro oggetto. E’ impor-
tante, perché supporta il concetto di classificazione gerarchica.
Nel mondo reale ad esempio, un Labrador fa parte della classificazione cane, che a sua volta fa parte
della classe mammiferi, che si trova sotto la classe animali, di dimensioni maggiori. Senza l’utiliz-
zo delle gerarchie, ogni oggetto dovrebbe definire esplicitamente tutte le proprie caratteristiche, ma
mediante l’ereditarietà un oggetto deve definire solo le qualità che lo rendono unico all’interno della
relativa classe e può ereditare gli attributi generali dal suo genitore. Se si desiderasse descrivere gli
animali in modo astratto, si direbbe che dispongono di alcuni attributi quali dimensioni, intelligenza
e tipo di sistema scheletrico.
Se si desidera descrivere una classe di animali più specifica, per esempio i mammiferi, avrebbero at-
tributi più specifici, quali il tipo di denti e di ghiandole mammarie. Ciò è noto come
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
-
Appunti di Programmazione ad oggetti
-
Appunti di Programmazione ad oggetti - Java
-
Miei appunti Latex di Programmazione ad oggetti
-
Appunti Programmazione ad oggetti e Java