Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Programmazione Orientata agli Oggetti in Java: Classi e Metodi, Sintesi del corso di Programmazione Java

Un'introduzione completa alla programmazione orientata agli oggetti in java, coprendo concetti fondamentali come classi, metodi, astrazione e invarianti di rappresentazione. La definizione di classi, la creazione di oggetti, l'incapsulamento, la gestione degli errori tramite eccezioni e l'utilizzo di asserzioni per il debug. Inoltre, vengono approfonditi i concetti di astrazione e implementazione, con particolare attenzione alla funzione di astrazione e all'invariante di rappresentazione. Ricco di esempi pratici e illustrazioni che facilitano la comprensione dei concetti chiave.

Tipologia: Sintesi del corso

2024/2025

Caricato il 12/03/2025

gxntly
gxntly 🇮🇹

1 documento

1 / 40

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Teoria
1
Teoria
Introduzione
Programmazione Imperativa: Sequenza di operazioni (istruzioni) in uno
specifico ordine
Programmazione Procedurale: Istruzioni strutturate in procedure
indipendenti, riusabili e parametrizzate
La Decomposizione è la suddivisione di un problema
complesso in sottoproblemi (moduli) separabili che siano più
comprensibili, più facili da implementare e mantenere, anche in
maniera indipendente.
■ I sottoproblemi dovrebbero essere logicamente coesi e svolgibili
indipendentemente
■ Combinando le soluzioni dei sottoproblemi si dovrebbe poter risolvere il
problema originale
■ I sottoproblemi dovrebbero essere allo stesso livello di dettaglio
Il modo per modellare il processo di Decomposizione è mediante Astrazione
Astrazione
LʼAstrazione è lʼidentificazione delle caratteristiche rilevanti
del problema, slegate dalla loro effettiva implementazione.
■ Può essere visto come un mapping molti → uno al fine di definire un
meccanismo generale per risolvere una serie di problemi simili
■ Es: In matematica le operazioni sono delle astrazioni che ci permettono di
svolgere calcoli con diversi valori degli operandi
Meccanismi di astrazione:
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28

Anteprima parziale del testo

Scarica Programmazione Orientata agli Oggetti in Java: Classi e Metodi e più Sintesi del corso in PDF di Programmazione Java solo su Docsity!

Teoria

Introduzione

Programmazione Imperativa: Sequenza di operazioni (istruzioni) in uno specifico ordine Programmazione Procedurale: Istruzioni strutturate in procedure indipendenti, riusabili e parametrizzate

La Decomposizione è la suddivisione di un problema

complesso in sottoproblemi (moduli) separabili che siano più

comprensibili, più facili da implementare e mantenere, anche in

maniera indipendente.

■ I sottoproblemi dovrebbero essere logicamente coesi e svolgibili indipendentemente

■ Combinando le soluzioni dei sottoproblemi si dovrebbe poter risolvere il problema originale

■ I sottoproblemi dovrebbero essere allo stesso livello di dettaglio

Il modo per modellare il processo di Decomposizione è mediante Astrazione

Astrazione

LʼAstrazione è l ʼidentificazione delle caratteristiche rilevanti

del problema , slegate dalla loro effettiva implementazione.

■ Può essere visto come un mapping molti → uno al fine di definire un meccanismo generale per risolvere una serie di problemi simili

■ Es: In matematica le operazioni sono delle astrazioni che ci permettono di svolgere calcoli con diversi valori degli operandi

Meccanismi di astrazione:

■ Astrazione per parametrizzazione

Trascura i valori effettivi utilizzati nei calcoli e si concentra sul loro tipo , potrò sostituire i parametri con i valori effettivi, mantenendo valido il calcolo

Lʼastrazione per parametrizzazione è unʼastrazione sui dati

Genero entità che dipendono e lavorano su dei parametri. Ci consente di rappresentare un insieme infinito di calcoli diversi con un singolo testo di programma che è un'astrazione di tutti loro.

👉 è un mezzo importante per raggiungere la generalità nei programmi

■ Astrazione per specifica

Trascura il modo in cui viene risolto, si concentra sulle specifiche del problema. Posso mantenere la validità del programma anche se implementato in maniera diversa

Lʼastrazione per specifica è unʼastrazione sui vincoli

dellʼoperazione (cosa deve ottenere, non come viene fatta)

Dichiaro, in maniera esplicita o implicita, il modo in cui queste entità devono essere utilizzate (senza doverne conoscere lʼimplementazione)

Mediante astrazione per parametrizzazione generalizzo i comportamenti del tipo di dato Mediante astrazione per specifica definisco cosa modella il tipo di dato e i suoi comportamenti, senza spiegare come è costruito il tipo di dato o come sono implementati i suoi comportamenti. ■ Astrazione di iterazione: ci consente di i terare sugli elementi di una collezione senza rivelare dettagli su come vengono ottenuti gli articoli Mediante astrazione per parametrizzazione definisco i metodi per iterare sulla sequenza di dati Mediante astrazione per specifica definisco il comportamento dellʼiterazione (in che modo e ordine mi arrivano gli elementi), senza spiegare come questo processo è implementato o come è memorizzata la sequenza di dati. ■ Gerarchia dei tipi di dati: caratterizza la creazione dei nuovi tipi di dato partendo da tipi di dato esistenti ed estendendone le capacità. Mediante astrazione per parametrizzazione posso (ri-)definire i comportamenti del nuovo tipo di dato, possibilmente riutilizzando quelli del dato “Genitoreˮ Mediante astrazione per specifica definisco le differenze e le similitudini nel comportamento, senza spiegare come queste sono implementate

OBJ Orientation

Differenza tra approccio procedurale e orientato a oggetti:

■ Nei linguaggi procedurali la logica del programma è strutturata in:

Dei dati memorizzati nelle variabili Delle azioni sui dati, definite nelle funzioni

■ Nei linguaggi orientati ad oggetti i dati sono strutturati come oggetti aventi sia:

Uno stato , ovvero una serie di variabili ( attributi ) che caratterizzano lʼoggetto Dei comportamenti ( metodi ) che agiscono (ispezionando o modificando) sullo stato dellʼoggetto

Qual è la differenza? (cosa ci permette di fare questa astrazione?

👉 Encapsulation (fondamento della OOP modificare stato dellʼoggetto solo attraverso i suoi metodi!

Classi - Archetipi di Oggetti

Per definire questi oggetti in Java scriveremo delle Classi di oggetti, che:

■ Definiscono quali attributi un oggetto di quella classe avrà

■ Definiscono quali metodi potranno essere chiamati su un oggetto di quella classe

■ Le classi si definiscono con ‘public class NomeClasse> {...}ʼ (nome inizia con maiuscola)

Esistono classi che contengono solo metodi statici ( non modificano lo stato di un oggetto ), queste classi, non avendo uno stato, non possono neanche creare oggetti (es main)

Package - Collezione di Classi

Java è un linguaggio compilato

Genera eseguibili NON in codice macchina ma bytecode

Per eseguire il bytecode serve una macchina virtuale VM che lo i nterpreta in istruzioni macchina. Il bytecode può girare sulla Java VM di qualsiasi hardware → WORM Write Once Run Many). Le “ librerie ˮ delle classi sono contenute nel percorso visibile dal compilatore e dalla VM

Java è un linguaggio fortemente tipizzato. Ciò significa che gli errori di tipo come l'utilizzo di un puntatore come numero intero vengono rilevati dal compilatore.

Esecuzione: Le classi sono definite in file .java (in genere una per file)  Nome file = nome della classe (necessario per classi con visibilità pubblica)  Il comando ‘javac .java ‘ produce il file .class corrispondente alla classe definita  Il comando ‘java (senza estensione) esegue il metodo ‘main‘ della classe

Variabili e obj

In Java le espressioni sono eseguite da sinistra verso destra e possono essere costituite da:

atomi : variabili, costanti, letterali operatori (+, -, =, ...) invocazioni di metodi

Le espressioni hanno come risultato un tipo e un valore  Possibile usarle per costruire altre espressioni (es: come operando, parametro di funzione)  A

compile time è possibile calcolare il tipo di una qualunque espressione per induzione  Il valore invece potrebbe essere calcolabile solo a run time (dipende da condizioni)

Le variabili in java hanno un nome e un tipo. Questi tipi possono essere:

primitivi (int, float, boolean...), generati sulla stack e contengono dei valori

riferimenti ad obj (array e oggetti) con riferimento nella stack e contenuto nella heap oggetti si inizializzano con operatore new e chiamata al costruttore

 String e array si possono anche creare con un literal (es: ‘s String = "ciao";‘)

A variabile riassegnata o metodo terminato lʼoggetto in heap è cancellabile dal garbage collector

👉 Le variabili riferimento possono puntare allo stesso oggetto

Se creo una variabile ‘a String = "paperino";‘ questa allocherà uno spazio sulla heap. Se assegno una nuova variabile ‘c=a‘ ora queste punteranno allo stesso spazio in heap.

prevedibile. Il concetto che un unico simbolo può avere diversi significati a seconda del contesto dʼutilizzo si chiama Polimorfismo ed è un fondamento della OOP ??

Conversioni Implicite e Overloading

Nel caso di espressioni con tipi diversi in alcuni casi Java può fare casting implicito. Questo è possibile anche nel caso dei parametri dei metodi

  • es: ‘somma(double a, double b)‘, se chiamato con un parametro intero effettua casting implicito

Tuttavia, nel caso dellʼoverloading dei metodi ci possono essere molteplici possibilità  Ad esempio dati due metodi ‘somma(int a, double b)‘ e ‘somma(double a, double b)‘

  • la chiamata ‘somma(2, 3)‘ quale dei due invoca?

Si effettua la chiamata più specifica (most specific)

Se non sono possibili conversioni o non cʼè una più specifica vi è un errore di compilazione  Ad esempio dati due metodi ‘somma(int a, double b)‘ e ‘somma(double a, int b)‘

  • la chiamata ‘somma(2, 3)‘ quale dei due invoca?

👉 ⚠ !! Il meccanismo che decide quale metodo invocare si chiama Dispatching !!

La variante dinamica degli array sono le List (es: ArrayList). List è unʼinterfaccia , per funzionare con i diversi tipi sono collezioni di tipo Object , posso quindi metterci dentro anche oggetti di tipo diverso. Però devo anche fare casting esplicito per usare i metodi del loro tipo giusto

Spesso però non mi serve avere oggetti diversi in una List e fare continuamente casting esplicito rallenta la scrittura del codice ed è prono a errori. È possibile restringere la collezione ad un tipo specifico di dato con il meccanismo di Generics

⚠Wrapping ⚠

Gli oggetti primitivi non possono essere usati in molti contesti in Java, es. ArrayList (o altre Collections) lavorano solo su Oggetti

Per questo, per ogni tipo primitivo vi è un tipo di oggetto

corrispondente

Ci sono degli appositi metodi per passare da tipo primitivo a oggetto e viceversa

👉 Questa meccanica si chiama Wrapping (avvolgimento) e Unwrapping (svolgimento)

Possibile conversione automatica da tipo primitivo a oggetto corrispondente

■ Questa capacità viene chiamata Boxing (inscatolamento) o Autoboxing

■ Lʼoperazione opposta è Unboxing (viene fatto anche comparando con==)

MODIFIES (effetti collaterali): specifica gli input modificati dalla procedura

EFFECTS (post-condizioni): specifica gli effetti della procedura

  • deve essere presente, utile confrontare lo stato pre e post esecuzione

OVERVIEW (su classe): specifica lʼobiettivo della classe e possibili funzionalità chiave

👉 ATTENZIONE  La specifica deve essere il primo passo nella scrittura del codice e serve per progettare più agevolmente la struttura del codice

es linguaggio specifica Liskov

Proprietà delle specifiche: !!!

restrittività : dovrebbe escludere le implementazioni non corrette

minimalità : dovrebbe porre il meno possibile vincoli sullʼimplementazione. Questo ha come effetto una maggiore generalità delle procedure , inoltre potrebbe permettere la sottodeterminazione , ovvero comportamento non determinato, ma le procedure dovrebbero sempre avere un comportamento deterministico

chiarezza : dovrebbe essere chiara semanticamente, > chiarezza tramite la ridondanza - dovrebbe chiarire il significato mediante ulteriori spiegazioni

Procedure

Proprietà delle procedure:

semplicità : deve essere chiaro cosa fa una procedura

generalità : determina il contesto di applicabilità di una procedura. Una procedura è più generale se accetta unʼinsieme più ampio di valori di input. Si può rendere più generale mediante parametrizzazione delle variabili o delle assunzioni

Le procedure si distinguono in:

Procedure totali

Ovvero senza vincoli sul dominio , tutti gli input sono validi

non richiedono la clausola require, il Compilatore stesso controlla lʼunico vincolo di validità, ovvero il tipo di input Procedure parziali

Ovvero con vincoli sui valori dei parametri (sul dominio)

Richiedono la clausola requires. Bisogna specificare su quali parametri la procedura può funzionare In tutti gli altri casi il comportamento non è definito, Ovvero può succedere qualunque cosa (crash, hang, output di qualche genere). Il comportamento deve essere deterministico Perchè costruire procedure parziali? Anche se le procedure totali sono più sicure, quelle parziali possono essere più performanti e più facili da scrivere

Quando fare una procedura parziale e quando una totale?

Totale per ridurre errori e rendere più generale lʼutilizzo

Parziale se il contesto dʼ uso è limitato e ben noto , e per grandi benefici di performance

Alto costo di gestione dellʼerrore Comma OK  no Esiste in go ma non in Java :(, non si possono restituire più valori in java. Ma in Java è possibile fare un wrapping di più valori di return in un unico tipo di ritorno Problemi: ■ Bisogna ricordarsi di verificare se cʼè stato un errore ■ Altissimo rischio di propagazione ■ Alto rischio di runtime errors

 Eccezioni

Meccanismo (parzialmente) separato dal control flow del programma per la gestione degli eventi eccezionali

■ Un evento eccezionale causa unʼinterruzione della procedura ■ È indipendente dal codominio della procedura (dal valore di return) ■ Costringe a gestire lʼiregolarità: Non si propaga perchè è sicuramente individuato

Tipi di eccezioni

Sono oggetti (sottotipo Throwable ). Gerarchia con due gruppi principali: ■ Error: errori fatali nellʼesecuzione del programma ■ Exception : comportamenti inattesi ma gestibili nellʼesecuzione del programma

RuntimeException - avvenimenti imprevedibili

Altre Exception - avvenimenti prevedibili

Uncheked exceptions: Eventi fuori dal controllo del programmatore Checked exceptions: Tutte le altre, il programmatore le gestisca nel codice, Altrimenti compile error

Come si usano?

Trattamento esplicito in main:

■ Rinchiudere le espressioni che possono causare eccezioni in un blocco ‘ try {...}‘ seguito da un blocco ‘ catch TipoEccezione> e) {...}‘ che uso per gestire lʼeccezione

■ È permesso un blocco finale ‘ finally {...}‘ che viene eseguito sempre, anche se try va bene. Serve per chiudere le risorse aperte

Come si specifica?

Sono necessarie modifiche alla signature e alla specifica dei metodi

■ keyword “ throws ˮ nella signature , seguita dai tipi di eccezione che potrebbero accadere, riportare anche le eccezioni unchecked per rendere chiaro il comportamento

■ Deve esserci la descrizione di ciascuno dei casi in EFFECTS

Se lʼeccezione avviene per alcuni valori del dominio, questi contano come validi.

■ Se vi sono modifiche agli input o effetti collaterali, nella clausola MODIFIES bisogna rendere chiaro se e in quali eccezioni avvengono le modifiche.

Come si lanciano?

Si usa lʼistruzione throw :

■ “throw new Exception("Messaggio ʼerrore");ˮ

Utile lanciare eccezioni di tipo appropriato:

■ Fornire un messaggio dʼerrore utile per gestirlo (su crash o da log)

■ Creare eccezioni nuove più appropriate alla situazione

Come si definiscono nella classe?

Serve estendere una classe base

■ ‘public class NewKindOfException extends Exception ‘ ( checked )

Definizione della classe : ‘(public) class NomeClasse {...}ʼ

Attributi (fields): ‘TipoDato> nome;ʼ  Sono le variabili il cui valore rappresenta lo stato dellʼoggetto  Saranno la rappresentazione del dato che andrà a finire in memoria  Si impostano quando si crea una nuova istanza della classe (oggetto)

Metodi di istanza : servono per accedere agli attributi di un oggetto della classe:

costruttori : ‘(public) NomeClasse() {...}ʼ per creare nuovi oggetti del tipo

altri metodi : ‘(public) TipoRitorno> nomeMetodo(...) {...}ʼ che agiscono sullo stato

metodi di classe (statici) : ‘(public) static TipoRitorno> nomeMetodo() {...}ʼ che non modificano lo stato, non serve riferimento a obj per essere chiamato

In che cosa differiscono i metodi di istanza dai metodi statici?

■ I metodi di istanza agiscono sugli attributi dellʼoggetto

Quindi si possono usare solo su oggetti specifici (istanze)

I metodi di istanza possono essere:

Metodi di costruzione Creators): creano un nuovo oggetto e ne assegnano gli attributi Metodi di mutazione Mutators): cambiano lo stato dellʼoggetto (modificando gli attributi) Metodi di osservazione Observers): restituiscono informazioni sugli attributi dellʼoggetto Metodi di produzione Producers): restituiscono un altro oggetto del loro stesso tipo

Per utilizzare un nuovo oggetto del tipo T devo prima crearlo e poi interagire coi suoi metodi. Si assegna ad una variabile del tipo T un oggetto restituito dal suo costruttore.

•‘Persona giovanni = new Persona("Giovanni", "M", 23, 1.74, "biondo");ʼ

Si possono usare i suoi metodi di osservazione per leggerne gli attributi

  • ‘double altezzaDiGiovanni = giovanni.altezza();ʼ

Si possono usare i suoi metodi di mutazione per cambiare gli attributi

•‘giovanni.siTinge("rosso");ʼ

👉 Non si accede direttamente agli attributi perché lʼimplementazione può cambiare!

Si può accedere direttamente agli attributi di altri oggetti della stessa classe perché una classe ha sempre accesso completo al suo stesso codice

Perchè facciamo astrazione sui dati?

Altrimenti sarei vincolato su come il dato è implementato.

Se in fase di implementazione stabilisco una determinata implementazione del tipo di dato qualsiasi modifica al tipo di dato richiederebbe di:  Modificare come implemento il tipo dato (ovviamente)  Modificare tutte le procedure che utilizzano questo tipo di dato  Incluso le procedure fatte da altri che utilizzano il mio tipo di dato!

Questo rende difficile il riuso dei tipi di dati e limita la modularità

Specifica dei tipi di dati

Devo definire c osa è il dato e cosa fa , ma non come è

implementato e non come lo fa

Ci concentreremo su come specificare lʼastrazione sui dati mediante:

Astrazione per parametrizzazione: metodi e parametri dei metodi Astrazione per specifica: specifiche della classe e dei suoi metodi

Per astrarre un tipo di dati prima decido che cosa voglio modellare e che cosa deve saper fare