Sei sulla pagina 1di 23

PROGRAMMAZIONE AD OGGETTI

CARATTERISTICHE DI UML

UML consente di modellare i sistemi software come insiemi di oggetti che collaborano tra di loro.
UML ha due aspetti:

- Struttura statica: riguarda i tipi di oggetti necessari per modellare il sistema e como sono tra
loro correlati;
- Comportamento dinamico: riguarda il ciclo div ita di questi oggetti e come collaborano per
fornire le funzionalità richieste al sistema.

STRUTTURA DI UML

La struttura di UML è composta da:

- Costituenti fondamentali:
o Entità: elementi che verranno modificati;
o Relazioni: specificano come due o più entità sono correlate;
o Diagrammi di base: modi di visualizzazione del nostro sistema
- Meccanismi comuni: tecniche comuni per raggiungere specifici obbiettivi con UML;
- Architettura: il modo in cui UML esprime l’architettura del sistema;

DIAGRAMMI DI UML

Il modello statico fissa le entità̀ e le relazioni strutturali tra le entità. Il modello dinamico fissa il
modo in cui le entità interagiscono per generare il comportamento richiesto al sistema software.
Non è necessario seguire un determinato ordine nella creazione dei diagrammi UML.
I diagrammi costituiscono non solo una vista del modello ma anche il principale strumento per
aggiungere nuove informazioni al modello. Ogni diagramma può avere un frame. UML 2 introduce
una nuova sintassi per i diagrammi, illustrata nella seguente figura:

- L’area di intestazione è un pentagono irregolare che


contiene il tipo del diagramma (opzionale), il nome
e i parametri (opzionali).
- Il tipo specifica il tipo del diagramma.
- Il nome dovrebbe descrivere la semantica del
diagramma.
- I parametri forniscono le informazioni necessarie
per includere gli elementi del modello presenti nel
diagramma.
- Un diagramma può avere un frame implicito

I diagrammi fondamentali che useremo saranno: diagramma delle classi, diagramma degli oggetti,
diagramma dei package, diagramma di sequenza

1
MECCANISMI COMUNI DI UML

UML prevede quattro meccanismi comuni che vengono applicati in modo consistente in tutto il
linguaggio, essi descrivono quattro diverse strategie della modellizzazione ad oggetti:

- Specifiche: sono la componente testuale del modello, che descrive la semantica degli
elementi;
- Ornamenti: rendono visibili specifiche delle entità del diagramma;
- Distinzioni comuni: descrivono modi diversi di ragionare sul mondo e sono essenzialmente
classificatore-istanza, interfaccia-implementazione;
- Meccanismi di estensibilità: servono per andare incontro all’esigenze di tutti, possono
essere:
o Vincoli: estendono la semantica di un elemento, è una frase di testo racchiusa tra
parentesi graffe che definisce una condizione o una regola che riguarda l’elemento di
modellizzazione e che deve risultare sempre vera.

o Stereotipi: definiscono un nuovo elemento basandosi su uno esistente, rappresenta


una variazione di un elemento di modellizzazione esistente che ha la stessa forma ma
un diverso scopo. Consentono di introdurre nuovi elementi di modellizzazione
basandosi su quelli esistenti, si usano facendo seguire al nuovo elemento il nome
dello stereotipo tra parentesi angolari (< … >). Ogni elemento di modellizzazione
può non avere stereotipi, e ogni stereotipo può definire un insieme di valori
etichettati e di vincoli.
o Valori etichettati: permettono di estendere la definizione di un elemento tramite
l’aggiunta di nuove informazioni specifiche

Un modello ha due dimensioni: una grafica e una testuale.

Una classe può essere rappresentata senza ornamenti, con il semplice rettangolo e il nome.

La nozione astratta di un’entità è differente da una sua istanza. L’interfaccia rappresenta cosa fa un
oggetto ma non dice come lo fa, ossia la sua implementazione.

In UML si definisce proprietà qualsiasi valore associato a un elemento di modellizzazione.


Consente di utilizzare i valori etichettati per aggiungere nuove proprietà agli elementi di
modellizzazione. La sintassi di un valore etichettato è la seguente:

{etichetta1 = valore1, etichetta2 = valore2, etichetta3 = valore3, … , etichettaN = valoreN }

Un profilo è un insieme di stereotipi, valori etichettati e vincoli.

ANALISI DEGLI OGGETTI

Un oggetto può essere definito come un’entità discreta che incapsula stato, comportamento.
Mascherare la parte di un oggetto sotto forma di strato di funzioni si chiama incapsulamento, ogni
oggetto è l’istanza di una classe. Tutti gli oggetti hanno

- Identità: è la peculiarità dell’esistenza ovvero ciò che lo distingue da tutti gli altri oggetti;
- Stato: viene stabilito dai valori degli attributi, ovvero è l’insieme dei valori degli attributi;
- Comportamento: sono le azioni che un oggetto può eseguire.
2
L’implementazione di un comportamento si chiama metodo, l’esecuzione di questi metodi può
portare variazioni agli attributi.

Per incapsulamento intendiamo l’avvolgere gli attributi ed i metodi degli oggetti in una singola
unità. Gli oggetti collaborano tra di loro stabilendo collegamenti e scambiandosi messaggi, quando
un oggetto riceve un messaggio, esso esamina le sue operazioni per trovare una la cui segnatura
corrisponda alla segnatura del messaggio, se tale operazione esiste allora l’oggetto la esegue.
Queste segnature sono composte come dal nome del messaggio, dai tipi di parametri e dal valore
restituito.

I nomi degli oggetti e dei metodi vengono scritti in lowerCamelCase, ogni valore di un attributo di
un oggetto si rappresenta come ‘nome: tipo = valore’

CLASSE

Una classe può essere definita come il descrittore di un insieme di oggetti che condividono gli stessi
attributi, le stesse operazioni, gli stessi metodi, le stesse relazioni e lo stesso comportamento.

Le classi consentono di descrivere l’insieme di caratteristiche che i corrispettivi oggetti devono


possedere senza che si debba descrivere ogni singolo oggetto.

RELAZIONI

Una relazione può essere definita come una connessione tra elementi del modello. Formata da una
classe con determinati attributi e metodi, che tramite un collegamento detto istanzia attribuisce ad
ogni oggetto della classe un valore agli attributi. Questa istanzia viene rappresentata con una linea
tratteggiata che indica una relazione di dipendenza dell’oggetto alla classe, una dipendenza è
definita come una relazione tra due elementi in cui un cambiamento in un elemento può influenzare
l’altro elemento.

L’istanziazione è la creazione di nuove istanze di elementi nel modello, se applicata alle classi
porta la formazione degli oggetti. I metodi costruttori indicano dei metodi associati alle classi che
hanno lo scopo di inizializzare le variabili di istanza. In un modello di analisi sono fondamentali le
informazioni: quelle che non possono mancare sono: nome della classe, attributi fondamentali,
operazioni fondamentali, stereotipi.

SCRITTURA

Per le classi usiamo la scrittura UpperCamelCase, evitiamo abbreviazioni, se ci sono acronimi si


preferisci scrivere per intero.

Per gli attributi obbligatoriamente dobbiamo mettere un nome in lowerCamelCasem mentre la


visibilità e la molteplicità sono opzionali

La visibilità: viene applicata agli attributi e alle operazioni delle classi ed è uno dei possibili
ornamenti:

‘+’ : pubblica; ‘-‘: privata; ‘#’: protetta; ‘~’: package

3
Il tipo: aspetto molto importante per gli attributi che specificano la natura di quell’attributo,
abbiamo:

- Integer = un numero intero;


- UnlimitedNatural = numero intero ≥ 0;
- Boolean = assume il valore true o false;
- String = una sequenza di caratteri

La molteplicità: usata per la progettazione ma anche per l’analisi indica da cosa sia composto un
attributo, per esempio

- Nome : String [2…*] indica un nome composto da due o più string;


- Indirizzo : String [3] indica un indirizzo composto da tre String;
- indirizzoEmail : string [0 .. 1] indica un indirizzo composto da una stringa o null

il valore iniziale consente di specificare il valore che un attributo assume quando si istanzia un
oggetto della classe.

OPERAZIONI

Le operazioni sono funzioni associate ad una certa classe, hanno tutte le caratteristiche delle
funzioni. La loro segnatura è la seguente:

visibilità nome (nomeParametro : tipoParametro, …) : tipoRestituito

I nomi vengono scritti in lowerCamelCase, ogni tipo dell’operazione è una classe.

Ogni operazione può avere un valore di ritorno (return) ed è scritto nel seguente modo:

valoreMassimo (a : Integer, b : Integer) : Integer

Per restituire due valore possiamo scrivere: restituisce il valore massimo e il valore minimo

maxMin (in a : Integer, in b : Integer, return valoreMax : Integer, return valoreMin : Integer)

oppure per renderlo più ordinato non scriviamo il return e applichiamo come prima:

maxMin (in a : Integer, in b : Integer) : Integer, Integer

AMBITO

Generalmente gli oggetti hanno una propria copia degli attributi e delle operazioni definiti dalla loro
classe, si dice che hanno un ambito di istanza. Possiamo anche definire attributi e operazioni
condivisi tra tutti gli oggetti di una stessa classe, in questo caso si dice che essi hanno ambito di
classe.

4
COLLEGAMENTI

È una connessione tra due oggetti che consente loro di scambiarsi messaggi, per stabilire un
collegamento bisogna che almeno uno dei due oggetti disponga di un riferimento dell’altro. Un
diagramma degli oggetti mostra gli oggetti con le loro relazioni. Gli oggetti si organizzano in
gerarchie o reti, le gerarchie organizzano gli oggetti in modo molto ordinato e strutturato ma molto
rigido, ogni gerarchia ha infatti in nodo radice e ogni altro nodo ha un solo oggetto al di sopra di se.
Con le reti invece non abbiamo un oggetto radice ma ogni aspetto può essere direttamente connesso,
le reti hanno strutture molto flessibili e non esiste un concetto implicito e nessun nodo ha la
supremazia sugli altri.

ASSOCIAZIONE

È una connessione tra due classi e indica che si possono quindi avere dei collegamenti tra i rispettivi
oggetti. È comune anche che una classe abbia un’associazione con se stessa e questa viene chiamata
associazione riflessiva. Per ciascuna associazioni è possibili definire:

- un nome: dovrebbero essere dei verbi o frasi verbali, può essere preceduto da una freccia
che indica la direzione in cui si legge, e vengono scritti in lowerCamelCase
- i nomi dei ruoli
- le molteplicità: è uno dei vincoli che può avere un associazioni e limita il numero di oggetti
di una classe che possono partecipare in una relazione in un dato istante.
- la navigabilità: indica che è possibile spostarsi da un qualsiasi oggetto della classe origine a
uno o più oggetti della classe destinazione, viene segnata aggiungendo una croce o una
freccia all0estremità della relazione, possiamo usare tre modi:
o completamente esplicita: tutte le frecce e le croci sono visualizzate;
o completamente invisibile: non vengono visualizzate ne croci ne frecce;
o eliminare tutte le croci: le assunzioni bidirezionali non hanno frecce, le assunzioni
unidirezionali ne hanno solo una

CLASSI ASSOCIAZIONE

Sono classi associate in una relazione tra due classi, sono costituite dalla classe, dall’associazione e
dalla linea tratteggiate

GENERALIZZAZIONE

La generalizzazione è una relazione tra un elemento e uno più specializzato in cui l’elemento più
specializzato è completamente consistente con quello più generico ma contiene più informazioni.
La generalizzazione divide le classi in superclass (quelle più generiche) e subclass (quelle più
specifiche), tramite questa partizione parliamo di ereditarietà.

EREDITARIETÁ

Le subclass ereditano dalla superclass tutti gli attributi, le operazioni, le relazioni e vincoli ed è
possibile che queste aggiungano altre caratteristiche. Può succedere che alcune operazioni e metodi
della superclass non vengano implementate nella subclass e allora diremo che l’operazione è
astratta, una gerarchia definisce dei livelli di astrazione. In UML una classe può ereditare anche da
due superclass e questo viene chiamato caso di ereditarietà multipla.

5
POLIMORFISMO

È la capacità di presentarsi sotto molte forme, è uno dei pilastri della programmazione insieme
all’ereditarietà e all’incapsulamento. Permette di progettare sistemi più semplici perché consente di
trattare oggetti differenti allo stesso modo, ovvero permette di inviare lo stesso messaggio a oggetti
di diverse classi.

PACKAGE

È l’entità di raggruppamento in UML, ogni package possiede un suo spazio dei nomi, al cui interno
i nomi degli elementi devono essere univoci. Un package è un meccanismo generalizzato per
organizzare e raggruppare elementi e diagrammi. Un package può essere utilizzato per:

- fornire spazio dei nomi incapsulato al cui interno tutti i nomi devono essere univoci;
- raggruppare gli elementi semanticamente correlati;

UML mette a disposizione lo stereotipo <<topLevel>> per identificar il package di livello più alto.
La visibilità per gli elementi di un package può essere solo pubblica o privata.

Un elemento di uno spazio di nomi per referenziare un elemento di un altro spazio dei nomi, deve
indicare oltre al nome del destinatario, anche un cammino per arrivarci ed è chiamato pathname o
nome qualificato. Si crea un nome qualificato premettendo al nome dell’elemento i nomi dei
package in cui risiede, separati da una coppia di due punti (::)

Una relazione di dipendenza tra package indica che uno di questi dipende da un altro tramite
qualche modo.

Allo stesso modo delle classi anche tra package esiste la generalizzazione e l’ereditarietà con
superpackage e subpackage.

REALIZZAZIONI DEI CASI D’USO

Indichiamo un insieme di classi che realizzano il comportamento specificato da un caso d’uso. Un


caso d’uso specifica i requisiti fondamentali del sistema che stiamo modellando, le loro
realizzazioni mostrano come le classi collaborano per realizzare le funzionalità del sistema.

Le realizzazioni dei casi d’uso dono costituite da:

- diagrammi delle classi di analisi: mostrano le classi di analisi che interagiscono per
realizzare il caso d’uso;
- diagrammi di interazione: mostrano le collaborazioni e le interazioni tra le istanze specifiche
che realizzano il caso d’uso;
- requisiti speciali: formulano i requisiti del caso d’uso eventualmente emersi durante il
processo di realizzazione dei casi d’uso;
- raffinamento del caso d’uso: aggiornare il caso d’uso qualora si ottengano nuove
informazioni

Le interazioni sono unità di comportamento di un classificatore che è noto come classificatore di


contesto e che fornisce il contesto per l’intestazione.

6
LINEE DI VITA

Rappresentano un singolo partecipante ad un’interazione ovvero raffigura come un’istanza di uno


specifico classificatore partecipa all’interazione. Ogni linea di vita è composta da:

- nome: per far riferimento alla linea di vita all’interno dell’intestazione;


- tipo: rappresenta il nome del classificatore di cui una linea di vita rappresenta un’istanza;
- selettore: indica una condizione booleana che può essere usata per selezionare una singola
istanza che soddisfa la condizione

La loro rappresentazione nei diagrammi è composta da una coda a forma di riga verticale
tratteggiata.

MESSAGGI

Un messaggio rappresenta un tipo specifico di comunicazione tra le due linee di vita in interazione,
quando una linea di vita riceve un messaggio di chiamata vuol dire che viene invocata
un’operazione che ha la stessa segnatura del messaggio, per ogni messaggio di chiamata ricevuto da
una linea di vita ci deve essere un’operazione corrispondente. Quando una linea adi vita esegue un
messaggio si dice che ha il focus di controllo o di attivazione (segnate da piccoli rettangoli sulla
linea di vita), un focus di controllo può essere annidato in un altro focus di controllo.

Possiamo avere vari tipi di messaggi:

- messaggio sincrono: il mittente aspetta un ritorno del messaggio;


- messaggio asincrono: il mittente non aspetta nessun ritorno;
- messaggio di ritorno: il destinatario restituisce il focus di controllo;
- distruzione dell’oggetto: il mittente distrugge il destinatario;
- messaggio trovato: il mittente del messaggio è al di fuori e lo utilizza per mostrare la
ricezione;
- messaggio perso: il messaggio non raggiunge mai la sua destinazione

DIAGRAMMI DI INTERAZIONE

Usati per modellare qualsiasi tipo di interazione tra istanze di classificatori:

- diagrammi di sequenza: mostrano le interazioni tra le linee di vita come una sequenza di
eventi ordinati temporalmente.
- diagrammi di comunicazione
- diagrammi di interazione generale
- diagrammi di temporizzazione

I diagrammi di sequenza possono esser divisi in aree chiamate frammenti combinati, ogni
frammento è composto da:

- un operatore: determina come vengono eseguiti i suoi operandi


- uno o più operandi: a tutti questi si può applicare una condizione di guardia;
- condizione di guardia: stabilisce se gli operandi devono essere eseguiti

7
OPERATORI

- opt (option): c’è un singolo operando che viene eseguito se la condizione è vera (if…then);
- alt (alternatives): viene eseguito l’operando la cui condizione è vera (else);
- loop (loop): esegue un loop di volte una determinata condizione (while);
- break (break): se la condizione di guardia è vera, viene eseguito l’operando, non il resto
dell’interazione;
- ref (reference): il frammento combinato fa riferimento a un’altra interazione

PROGETTAZIONE

L’aggregazione è una relazione tra oggetti poco forte, calcolatore e periferiche, è del tipo tutto-
parte per cui un aggregato è costituito da molte parti, è transitiva, asimmetrica e riflessiva.

La composizione è una relazione tra oggetti molto forte, albero e le foglie

INTERFACCIA

È un insieme di funzionalità pubbliche identificate da un nome, essa dichiara semplicemente un


contratto che può essere realizzato da zero o più classificatori ma non può essere istanziata. Gli
attributi e le operazioni di un’interfaccia vanno specificati e dovrebbero comprendere: la segnatura
completa, la semantica dell’operazione, il nome e il tipo degli attributi, gli stereotipi di operazioni o
attributi, vincoli e valori etichettati. Le interfacce realizzate da un classificatore sono note come
interfacce fornite, quando un classificatore richiede una o più interfacce si chiamano interfacce
richieste

INTRO JAVA concetti base

Dichiarazione di una variabile: è formata da un tipo e da un nome, abbiamo 8 tipi primitivi, i quali
comprendono:

- Per i numeri interi: int, short, long, byte


- Per I numeri decimali: float, double
- Per Unicode: char
- Boolean: per valori true o false

Inizializzazione di una variabile: dopo la sua dichiarazione le associamo un valore tramite


l’operatore di assegnazione ‘=’

Questi due passaggi possono essere eseguiti sia separatamente uno dietro l’altro oppure incorporati

int numero //dichiarazione

numero = 0 //inizializzazione

int numero = 0 //dichiarazione e inizializzazione

8
Quando una variabile può assumere uno tra un sottoinsieme definito di valori usiamo un enum:

enum Size {small, medium, large, extralarge}

Size s = Size.small;

If (s == Size.extralarge) {

…//continua del codice

Per le stringhe non abbiamo un tipo primitivo, ma una classe predefinita chiamate String che viene
inizializzata tramite l’associazione di una stringa di caratteri racchiusa in “ “ tramite l’operatore
new che appunto indica la creazione di una nuova stringa:

String foo = new String (“Hello world”);

String bar = new String(“ciao mondo”);

Possiamo comparare due stringhe per vedere se sono uguali tramite il metodo equals()

Foo.equals(bar) //restituisce True se sono uguali, ovvero se sono la stessa sequenza di


caratteri

La stringa vuota “ “ è una stringa di lunghezza 0. Una variabile di tipo String può assumere un
valore specifico chiamato null, questo valore indica che non vi è un oggetto associato alla variabile.

ISTRUZIONI CONDIZIONALI

Ciclo if…else, “se la condizione è soddisfatta eseguire lo statement altrimenti eseguire il resto”

If (condition) {

statement 1

} else

statement 2

9
per una selezione di condizioni multiple abbiamo anche il costrutto switch con i vari case

switch (state) {

case 0:

break //interruzione del ciclo in cui sia verificato il case

case 1:

break

default //interruzione del ciclo finale, rappresenta l’ultimo case se gli altri non sono
verificati

Per una ripetizione di n volte finchè una condizione non viene verificata usiamo il ciclo while

While (condition)

Statement;

per una una ripetizione di n volte ‘controllata’ ovvero tramite l’uso di un contatore usiamo il ciclo
for. Per un valore inizializzato i = 0, incrementarlo di1 (i++) a ogni fine del ciclo fino a quando la
condizione (i<5) è soddisfatta’ significa che quando i tramite l’incremento di 1 supera 5 il ciclo
finisce.

for (int i = 0; i < 5; i ++)

System.out.println(i)

ARRAY

Gli array sono una sequenza omogenea di elementi identificati da un unico identificatore, usiamo
l’operatore [ ]. Per riempire un array abbiamo bisogno di un’inizializzazione. Gli array hanno varie
proprietà, la più importante è length che permette di leggere la lunghezza dell’array

Int[] a1 = {4, 5, 6} //array inizializzato


Int[] a3 = new int [3] //creazione dell’espressione di un array
Int[]a4 = a1 //creazione di un array uguale ad un altro

Possiamo anche percorrere un array per leggere i valori contenuti tramite:


for (int e : a1) //itero tramite una variabile generica ‘e’ sull’array per leggere i valori
System.out.println(e) //cosa faccio con i valori letti? In questo caso vengono stampati

10
AVVIO ALLA SCRITTURA DI UN CODICE

Funzione main, identifica la classe principale del mio programma quella dal quale viene eseguito
tutto il processo, per la stesura del codice bisogna innanzitutto procedere con:

- Identificare le classi: ragionare sui sostantivi che ci vengono richiesti


- Dichiarare gli attributi: dichiarazione delle variabili di quella classe in maniera private
visibili solo ai metodi della classe, se un attributo viene definito come static vuol dire che ne
esiste solo uno per quella classe
- Aggiungere alle classi i metodi: ragionare sulla parte verbale e su cosa devono fare le
classi. Per ogni metodo va poi specificato l’oggetto, i metodi hanno la possibilità di
dichiarare variabili locali che non servono direttamente nella classe ma solo all’interno dei
metodi tramite l’uso di var.
I metodi operano sugli oggetti accedendo agli attributi, ricevono sempre un parametro
implicito sul quale il metodo viene invocato, si fa riferimento al parametro implicito tramite
l’utilizzo del keyword this che differenzia le variabili del metodo dagli attributi dell’istanza.
Per garantire l’incapsulamento, ovvero l’utilizzo di alcuni metodi di una classe in un’altra
classe impostando la visibilità come public associata ai metodi .get() .set()
I metodi statici non operano su alcun oggetto, non hanno un parametro implicito (this) e
possono accedere solo agli attributi static (per questo motivo main è definito come static
Alcune classi predefinite hanno dei factory metod ovvero metodi statici che costruiscono
oggetti, come LocalDate che ha il metodo LocalDate.now che restituisce la data odierna
- Identificare un metodo costruttore: permette di creare istanze ed ha lo scopo di
inizializzare oggetti, i costruttori hanno lo stesso nome della rispettiva classe e vanno
combinati con l’operatore new.
Quando in una classe troviamo più di un costruttore parliamo di overloading.
Il costruttore prende in esame gli attributi dichiarati, se non viene trovato l’attributo tra
quelli dichiarati, viene inizializzato con i valori di default ( 0 per gli interi, false per i valori
booleani, null per quelli che si riferiscono ad oggetti)

In java possiamo raggruppare le classi in raccolte chiamate package, questi permettono


un’organizzazione conveniente. La ragione principale dell’uso dei package è quella di garantire
l’univocità dei nomi delle classi.

Ogni classe può accedere alle classi pubbliche predefinite tramite import java.

Per inserire una classe in un package è sufficiente scriver il nome del package in cima al codice,
prima dei blocchi in cui si definisce la classe stessa.

Il class path è l’insieme dei percorsi che possono contenere file delle classi, viene utilizzato nel
processo di ricerca delle classi da parte del compiler.

11
SUGGERIMENTI SULLA CREAZIONE DELLE CLASSI

1. Mantenere i dati sempre privati


2. Inizializzare sempre i dati
3. Usare il meno numero possibile di tipi basilari per una classe (se una classe persona
definiamo i seguenti attributi: una stringa con il nome, una stringa con la via, una stringa con
la città, una stringa con lo stato, un intero con il codice postale, è meglio creare un’altra
classe del tipo Indirizzo cosi poi da attribuire a Persona un unico attributo che sarebbe la
classe)
4. Non tutti gli attributi necessitano di accessori e mutatori
5. Dividere le classi che hanno troppe responsabilità (dividere una classe complessa in due più
semplici)
6. Scegliere nomi in linea con l’utilizzo per le classi e i metodi
7. Preferire classi immutabili

EREDITARIETÁ

L’idea principale è quella di creare nuove classi costruite su classi esistenti, ereditare significa
proprio riusare metodi e proprietà di una classe in un’altra con la possibilità di aggiungere altre
caratteristiche.

La parola chiave che permette di ereditare da una classe è extends

public class Manager extends Employee{ … //la classe manager eredita da employee

Diremo che Manager è una subclass e employee la superclass, una subclass o classe derivata può
aggiungere campi, metodi e fare override (s’intende la modifica di un metodo appartenente ad una
superclass). Per accedere alle informazioni della superclass si usa il keyword super, può essere
usata anche per riferirsi ad un costruttore della superclass.

Con dynamic binding si intende il meccanismo di selezioni del metodo relativo alla classe a cui si fa
riferimento a tempo di esecuzione (run time)

I metodi e i campi di una classe sono accessibili ai membri della stessa e ad altre classi nello stesso
package. I campi e i metodi dichiarati come private sono accessibili soltanto dall’interno della
classe, mentre quelli dichiarati come public sono accessibili da qualunque classe in qualsiasi
package. Un ulteriore modificatore di accesso e il protected i quali metodi e campi dichiarati sono
visibili sono dalle subclass anche se definiti in package differenti.

Buona regola per accertarsi che l’ereditarietà sia una buona scelta è il metodo is-a ovvero afferma
che ogni oggetto della subclass è anche oggetto della superclass, viene formulata anche con il
principio di sostituzione, ovvero si può usare l’istanza di una subclass ogni volta che il programma
si aspetta l’istanza di una superclass.

Le variabili il cui tipo è un oggetto sono polimorfiche.

Possiamo decidere quali classi non possono essere ereditate dette classi finali, tramite l’uso del
modificatore di non accesso final, se viene applicato ad un metodo quel metodo non sarà possibile
fare override. Metodi di classe dichiarate con final hanno automaticamente metodi final.

12
Il processo di conversione forzata da un tipo ad un altro è chiamato casting, la sintassi del cast in
java è il seguente: T x = (T) y; e vale per i tipi primitivi. Prima di eseguire un cast è buona norma
verificare che questo sia possibile per non ricadere in errore di ClassCastException quindi useremo
il termine istanceof per verificare che l’oggetto sia istanziato nella determinata classe.

CLASSI ASTRATTE, prese ad esempio la classe Employee e la classe Student possiamo astrarre il
concetto e dire che entrambi ereditano da una classe Person: quindi bisogna creare un accessore
abstract, che però non va implementato, resta scritto ma non associato, è possibile applicarlo anche
ai metodi per intendere che quei metodi non hanno un’implementazione. Una classe astratta però
può tranquillamente avere anche campi e metodi concreti.

I metodi dichiarati astratti vengono poi implementati in una subclass che eredita dalla classe
astratta, se questa subclass non implementa i metodi astratti anche lei viene definita come subclass
astratta.

Madre di tutte le super class è la classe Object e fortunatamente non va scritta nella stesura del
codice, utilizziamo la variabile di tipo Object in una classe per riferirci a ciascun tipo di oggetto.
Questa superclass ha in essa molti metodi che vengono automaticamente ereditati dalle altre classi
come ad esempio: equals il quale determina se due riferimenti ad oggetti sono uguali tra loro,
toString classico esempio di una classe che usiamo spesso che è derivata da Object la quale
restituisce una stringa che rappresenta il valore dell’oggetto

Un hashcode è un intero che viene derivato da un oggetto

INTERFACCE

Tramite le interfacce possiamo descrivere che cosa delle classi devono fare, senza specificare come
lo devono fare. Sono un insieme di requisiti che le classi dovranno rispettare per essere conformi
all’interfaccia

Esempio: il metodo sort della classe Arrays promette di ordinare un array di oggetti, purché questi
appartengano a classi che implementano l’interfaccia Comparable. Ogni classe che implementa
l’interfaccia Comparable dovrà disporre di un metodo compareTo che prende un parametro Object
e restituisce un intero

Public interface Comparable{

Int compareTo (other object); }

Tutti I metodi di un’interfaccia sono automaticamente public.

Per far si che una classe posso implementare un’interfaccia dobbiamo fare due cose:

- Dichiarare che la classe intende implementare la suddetta interfaccia (class Employee


implements Comparable);
- Fornire nella classe definizioni per tutti i metodi dell’interfaccia

L’operatore new non può essere usato per istanziare un’interfaccia, possiamo usare variabili
interfaccia, una variabile interfaccia deve riferirsi ad oggetti di una classe che la implementa.

13
INTERFACCIA COMPARATOR

Per confrontare due stringhe sulla base della loro lunghezza invece che su base alfabetica, diventa
utile l’utilizzo di un comparatore, il quale è un’istanza che implementa l’interfaccia comparator

INTERFACCIA CLONEABLE

Indica che una classe ha un metodo clone, tramite il quale può essere può essere fatta una copia di
un oggetto inizialmente identico ad un altro ma che potrà divergere dallo stato dell’originale nel
corso del tempo. Il metodo clone della classe Object è di tipo protetto. Una classe che intende usare
il metodo clone dovrà:

- Implementare l’interfaccia Cloneable


- Ridefinire il metodo clone con il modificatore di accesso public

CLASSI INTERNE (inner class)

Una classe interna è una classe definita dentro un’altra classe, i motivi per cui implementare in
questo modo le classi possono essere:

- Le inner class possono essere nascoste dalle altre classi nello stesso package;
- I metodi delle inner class possono accedere ai dati dello scope dove sono definiti
- Se non sono statiche contengo un riferimento implicito all’oggetto della classe esterna che lo
ha stanziato, tramite questo riferimento hanno acceso allo stato dell’oggetto esterno.

In altre parole, un metodo di una classe interna ottiene l’accesso a tutti i campi propri dell’oggetto
esterno che lo ha creato. Il compilatore modifica tutti i costruttori delle classi interne, aggiungendo
un parametro per il riferimento alla classe esterna

GENERICS

Il modo più semplice per implementare generalizzazioni in un linguaggio orientato agli oggetti è il
polimorfismo. Con generic programming si intende sviluppare e scrivere un codice che può essere
riusato per oggetti di diversi tipi. I generics implementano il concetto di tipo parametrizzato in quale
permette di avere più tipo, l’intento è quello di estendere l’espressività di una classe o di un metodo.
Il tipo parametrizzato associato ad una classe o ad un metodo indica un tipo non specificato che può
essere deciso all’occorrenza. Ha sintassi del tipo: Foo<T> con Foo che indica una classe e T
rappresenta il tipo non specificato (indica la dichiarazione del parametro) mentre l’istanziazione sta
nel sostituire il tipo T con quello scelto, ad esempio Foo<String>, il risultato di questa istanziazione
è un oggetto ordinario. Possiamo adoperare questa metodologia anche per dichiarare due type
parameter del tipo Foo<T, U>. Possiamo associare il tipo parametrizzato oltre che alle classi anche
ai metodi il quale prenderà il nome di metodo generico che può appartenere ad una classi ordinaria,
per invocazioni a metodi generici che usano tipi primitivi, usiamo l’autoboxing che si occupa di
wrappare (avvolgere) automaticamente i tipi primitivi nelle loro rispettive classi associate.

Quando definiamo un tipo generico, viene automatico identificare il tipo raw corrispondente,
ovvero una versione della classe generica che non specifica l’argomento nelle parentesi angolari.
Un tipo parametrizzato è senza bound allora il tipo raw è Object, altrimenti è un tipo bounding.

14
Per Bound intendiamo che il tipo di argomento generico deve essere una classe o implementare
l’interfaccia specificata.

Public static < T extends Comparable > T min(T[]a){

In questo caso T può essere sostituito con tipo che estendono Comparable.

I tipi parametrizzati presentano alcune limitazioni come ad esempio:

- Non possono essere istanziati con tipi primitivi;


- Non si possono creare array di tipi generici
- Non si possono istanziare tipi generici;

I tipi wildcard vengono descritti da tipi il cui parametro può cambiare, ad esempio

Pair< ?extends Employee> è un tipo wildcard e denota un tipo generico Pair il cui parametro è una
sottoclasse di Employee. I bound per i tipi wildcard sono simili ai bound per i tipi parametrizzati ma
hanno un fattore addizionale ovvero il supertype bound: super. Si possono usare i tipi wildcard
senza bpund, semplicemente con il simbolo ?, ovvero Pair <?> questo presenta i metodi getFirst() e
void seFirst(?); dove il valore getFirst può essere assegnato ad un Object e il metodo setFirst non
potrà mai essere invocato neppure da un Object.

Allora la differenza principale con il tipo raw di Pair è che in questo possiamo invocare il metodo
setFirst con ogni possibili Object.

OBJECT WRAPPER E AUTOBOXING

A volte si ha bisogno di convertire un tipo primitivo in un oggetto. Tutti i tipi primitivi hanno una
controparte classe: Integer, Long, Float, Double, Short, Byte, Charachter, Boolean. Esse vengono
chiamate classi wrapper e sono immutabili oltre che ad essere final.

Una istanza di una classe wrapper può essere creata a partire dalla controparte primitiva.

Esempio: Integer.valueOf(42) crea un’istanza di Integer il cui valore è 42

Con il termine autoboxing si intende la traduzione automatica del passaggio tra valore primitivo ad
istanza di classe wrapper.

Le classi wrapper offrono molti metodi tra cui: Integer.parseInt(s) che permette di interpretare una
stringa s come un int.

15
COLLECTIONS

Le collections rappresentano strutture dati utili per diversi scopi, una struttura dati è una classe
dedicata a memorizzare e manipolare dei dati. Quindi diremo che le collections rappresentano
strutture dati generiche.

L’interfaccia fondamentale per le collections in java è Collection, la quale ha due metodi


fondamentali:

- Add: aggiunge un elemento alla collezione, e ritorna true se aggiungere l’elemento


comporta modifiche alla collection altrimenti false.
- Iterator: ritorna un oggetto che implementa l’interfaccia Iterator, un iteratore si utilizza per
visitare gli elementi in una collezione uno ad uno.
Quest’interfaccia implementa in sè altri quattro metodi:
o Next(): itera sugli elementi della collezione uno ad uno, fino a quando non viene
lanciato il NoSuchElementException che indica la fine.
o hasNext(): il quale viene posto prima di Next affiche verifichi che ci siano elementi
su cui iterare, e in tal caso restituisce true.
o Remove(): rimuove elementi dalla collection
o forEachRemaining

Oltre l’interfaccia Collection, altra interfaccia molto importante è Map la quale rappresenta una
collezione dove ogni elemento è una coppia (k,v) dove k rappresenta una chiave e v un valore, gli
elementi vengono aggiunti tramite v put(K key, V value). Gli elementi di una Map si dicono
indicizzati e si accede ad essi tramite la chiave, utilizzando il metodo V get(K key)

Altre collezioni concrete sono:

- ArrayList: sequenza di elementi con accesso random, ridimensionata automaticamente;


- LinkedList: sequenza ordinata di elementi, permette inserimenti e rimozioni efficienti;
- HashSet: insieme non ordinato di elementi, senza duplicati
- LinkedHashSet: insieme che memorizza l’ordine di inserimento degli elementi;
- HashMap: struttura dati che chiameremo mappa per elementi (chiave, valore);
- LinkedHashMap: una mappa che ricorda l’ordine di inserimento degli elementi.

HashSet<Integer> valori = new HashSet<Integer>(); inizializza un insieme ordinato di elementi di


tipo integer chiamato valori

Map<String, Integer> conteggioValori = new HashMap<>(); inizializza una mappa la cui chiave è
una stringa e il valore è un intero chiamato conteggioValori

List<String> elenco = new ArrayList<>(); inizializza una sequenza di stringhe con accesso random
chiamata elenco

Andremo a riempire le nostre mappe tramite il metodo put dopo aver dichiarate ad esempio le
nostre variabili scriveremo alla fine del ciclo: conteggioValore.put(stringa1, valore1)

Andremo a riempire la nostra lista tramite add, dopo aver ricavato i valori da inserire scriveremo:
elencoadd(valori)

16
ERRORI ED ECCEZIONI

Obiettivo del programma quando un’operazione non può essere completata a causa di un errore:

- Ritornare a uno stato sicuro e consentire all’utente di eseguire gli altri comandi;
- Consentire all’utente di salvare tutto il lavoro e terminare il programma senza problemi

La gestione delle eccezioni è quella di trasferire il controllo da dove l’errore è accaduto al gestore di
errori che può affrontare la situazione. Java consente che ciascun metodo abbia un percorso
alternativa di uscita (il metodo esce immediatamente o inizia la ricerca della gestione dell’errore)

Un’eccezione è sempre un oggetto istanziato da una classe Throwable.

la gerarchia delle eccezioni si divide in due rami:

- Eccezioni di tipo RuntimeException se vengono commessi errori di programmazione,


queste includono un cast non valido, l’accesso ad elementi fuori confine in un array,
l’accesso ad un riferimento nullo
- Tutto il resto delle eccezioni, includono il tentativo di leggere oltre la fine di un file,
tentativo di aprire un file che non esiste, ricerca di un oggetto Class per una stringa che non
denota una classe esistente.

La specifica dl linguaggio java chiama qualsiasi eccezione che deriva dalla classe Error o da
RuntimeException come un’eccezione unchecked ovvero non controllata. Tutte le altre eccezioni
sono denominate checked (verificate). Un metodo per gestire le eccezioni è il throw usato alla
subito dopo aver implementato il pezzo di codice che potrebbe generare l’eccezione:

public FileInputStream (String name) throws FileNotFoundException

quindi nel caso in cui il file non viene trovato viene gestita l’eccezione

Un’alternativa può essere l’uso della ricerca di eccezioni tramite il try / catch, un metodo che
cattura l’eccezione e la gestisce.

Try {
Code
More code
} catch (ExceptionType e) {
Handler for this type
}

Se un qualunque commando dentro al try lancia un’eccezione del tipo specificato nella clausola
allora il programma salta il resto del try ed esegue la gestione dell’eccezione all’interno del catch.
Se nessuna eccezione viene lanciata all’interno del try allora il codice del catch viene saltato

In aggiunta possiamo usare anche la clausola del finally che viene eseguita sia quando l’eccezione
viene catturata e sia quando non viene catturata. Questa clausola risolve il problema di quando il
nostro codice incontrando un’eccezione termina la sua esecuzione e crea problematiche alle
variabili locali.

17
Uno stack trace è un elenco di chiamate ad un metodo in sospeso in un punto particolare
dell’esecuzione di un programma.

Il meccanismo di asserzione consente di inserire dei controlli durante il test e di rimuoverli


automaticamente nel codice di produzione.
Assert condition
// oppure
Assert condition : expression
Per sccrivere un ‘asserzione ci serviamo della keyword assert, queste forme riportate sopra
valutano la condizione e lanciano un’eccezione del tipo AssertionError se la condizione è falsa

ALBERI
È una delle strutture dati non lineari più importanti, con ‘struttura dati non lineari’ si intende il fatto
che la relazione che organizza i dati all’interno della struttura è più ricca. Avendo i dati organizzati
ad albero riusciamo nello specifico a migliorare l’efficienza di alcuni algoritmi.

Un albero è un tipo astratto che memorizza elementi in modo generico, ad eccezione dell’elemento
in cima, radice, ogni elemento ha un elemento genitore, parent, e zero o più elementi figli,
children.

Definiamo albero T come un insieme di nodi che memorizzano elementi, tra i nodi esiste una
relazione di tipo genitore-figlio. Due nodi che siano figli dello stesso genitore si dicono fratelli.

Un nodo v è esterno se non ha figli, e prendono il nome di foglie, mentre è interno se ha uno o più
figli.

Un ramo, edge, di un albero T è una coppia di nodi (m, v) tali che m è il genitore di v, o viceversa.

Un percorso, path, di un albero T è una sequenza di nodi tale che ciascuna coppia di nodi
consecutivi sia un ramo.

Un albero si dice ordinato se esiste una disposizione lineare significativa.

Definiamo il tipo di dato astratto ‘albero’ usando il concetto di posizione come astrazione di un
nodo dell’albero, in ogni posizione è memorizzato un elemento e le posizioni soddisfano le
relazioni genitore-figlio che definiscono la struttura dell’albero. Un oggetto che rappresenta una
posizione in un albero mette a disposizione il metodo getElement() che restituisce l’elemento
memorizzato in questa posizione.

Un albero mette a disposizione i seguenti metodi d’accesso:


- Root(): restituisce la posizione della radice
- Parent(p): restituisce la posizione del genitore della posizione p
- Children(p): restituisce un contenitore iterabile con i figli nella posizone p
- numChildren(p): restituisce il numero di figli nella posizione p

Un albero dispone di metodi di interrogazione:

- isInternal(p): restituisce true se e solo se la posizione p ha almeno un figlio;


- inExternal(p): restituisce true se e solo se la posizione p non ha figli;
- isRoot(p): restituisce true se la posizione p è la radice dell’albero
18
Un albero dispone di un certo numero di metodi generali:

- size(): restituisce il numero di posizioni, quindi il numeri di elementi presenti;


- isEmpty(): restituisce true se e solo se l’albero non contiene posizioni, ovvero è vuoto;
- iterator(): scandisce tutti gli elementi dell’albero;
- positions(): restituisce un contenitore iterabile con tutte le posizioni dell’albero

Gerarchia delle classi: interfaccia Tree  classe astratta AbstractTree  per creare una classe
concreta bisogna implementarre l’interfaccia e fornire l’implementazione dei tre metodi root(),
parent(p), children(p)  tutto il resto è ereditabile da abstractTree

Sia p una posizione all’interno dell’albero T, la profondità (depth) di p è il numero di antenati di p,


diversi dalla posizione p stessa.
Definiamo altezza di un albero come il valore massimo delle profondità delle sue posizioni
Un albero si dice binario se è un albero ordinato con le seguenti proprietà:

- ogni nodo ha al massimo due figli;


- ogni nodo figlio è etichettato come figlio sinistro e destro
- il figlio sinistro precede quello destro nell’ordinamento tra i figli di un nodo

vengono forniti in supporto altri tre metodi di accesso:


- left(p): restituisce la posizione del figlio sinistro di p;
- right (p): restituisce la posizione del figlio destro di p;
- sibling(p): restituisce la posizione del fratello di p;

Chiamiamo livello d dell’albero T l’insieme di tutti i nodi di T che hanno la stessa profondità d.
È possibile pensare ad un albero binario come struttura concatenata:

- un nodo associato alla posizione p gestisce:


o riferimenti all’elemento memorizzato nella posizione p;
o ai nodi associati ai figli
o al genitore
- un albero gestirà:
o una variabile esemplare che memorizza un riferimento al nodo radice;
o una variabile, size, che contiene il numero totale di nodi di T

I metodi suggeriti per una struttura concatenata di albero binario sono i seguenti:

- addRoot(e): crea una radice di un albero vuoto memorizzando l’elemento e e restituendo la


posizione di tale radice;
- addLeft(p, e): crea il figlio sinistro per la posizione p, memorizzando l’elemento e e
restituendo la posizione di tale nuovo nodo, se p ha il figlio sinistro si verifica errore;
- addRight(p, e): crea il figlio destro per la posizione p, memorizzando l’elemento e e
restituendo la posizione di tale nuovo nodo, se p ha il figlio sinistro si verifica errore;
- set(p, e): sostituisce l’elemento memorizzato nella posizione p con e e restituisce l’elemento
sostituito.
- attach(p, T1, T2): college la struttura interna degli alberi T1 e T2 come rispettivamente sotto
albero sinistro e destro della foglia p.
- remove(p): elimina il nodo in posizione p sostituendolo con suo figlio se esiste, e restituisce
l’elemento memorizzato in p.
19
Per implementare concretamente un albero binario sarà necessario implementare i sei metodi
elencati, definire una classe annidata Node che implementa l’interfaccia Position e per tale classe
verrà definito un factory method createNode che restituisce un nuovo nodo.

L’attraversamento di un albero T è un modo sistematico per ispezionare tutte le posizioni di T

- attraversamento in pre-ordine: viene visitata per prima cosa la radice e poi si attraversano i
sotto-alberi aventi radice nei suoi figli;
- attraversamento in post-ordine: vengono visitati prima i sotto-alberi aventi radice nei figli
della radice e poi viene vista la radice;
- attraversamento in ampiezza: prevede che vengano visitate tutte le posizioni aventi
profondità d prima di visitare qualsiasi posizione avente profondità d + 1;
- attraversamento in ordine simmetrico: prevede che venga visitata la posizione in esame dopo
aver attraversato il suo sotto-albero sinistro e prima di attraversare il suo sotto-albero destro;

Un albero di ricerca binario è un albero binario che memorizza una sequenza ordinata di elementi.
Possiamo usare l’albero di ricerca binario T associato all’insieme S per verificare se un determinato
valore v sia presente in S, navigando lungo un percorso che scende nell’albero T a partire dalla
radice. Preso un insieme di elementi privi di duplicati, un albero di ricerca binario relativo
all’insieme è un albero binario tale che per ogni posizione interna p sia vero che:

- la posizione p memorizza un elemento di S che indichiamo con e(p)


- gli elementi memorizzati nel sotto-albero sinistro di p sono tutti minori di e(p)
- gli elementi memorizzati nel sotto-albero destro di p sono tutti maggiori di e(p)

GRAFI

Un grafo è un modo per rappresentare relazioni esistenti tra coppie di entità, è un insieme di entità
chiamati vertici o nodi e una raccolta di collegamenti tra coppie di vertici chiamati archi o lati.

I grafi è anche una struttura dati che permette di rappresentare entità e relazioni diadiche tra esse.

Un grafo G è una coppia G(V,E), dove V è l’insieme dei nodi di G ed E = {{u,v} : u ∈ V, v ∈V} è
l’insieme di archi del grafo G.

I vertici di un arco e = {u,v} sono detti adiacenti o vicini.

L’ordine di un grafo G(V,E) è il numero dei suoi vertici | V |. La dimensione di un grafo è il


numero dei suoi archi | E |.

Un auto arco è un arco i cui vertici sono uguali e = {u,u}.

Gli archi multipli sono archi aventi la stessa coppia di vertici.

Un grafo indiretto è un grafo che non ha auto archi e archi multipli. È definito dal suo insieme di
vertici e dal suo insieme di archi.

Un grafo diretto è un grafo i cui archi sono orientati e sia dato un arco (u,v) i due vertici vengono
chiamati rispettivamente origine e destinazione. L’arco viene detto uscente di u, entrante di v.

20
Un grafo pesato G (V, E, 𝜔) è un grafo, diretto o indiretto, in cui il valore chiamato peso è
assegnato ad ogni arco tramite 𝜔. Con 𝜔 : E  R (valore reale)

Il grado di un vertice du è il numero di archi incidenti ad esso.

Consideriamo un grafo come tipo astratto e modelliamo la sua astrazione tramite tre tipi:

- Vertex: rappresenta un vertice e memorizza un elemento arbitrario


- Edge: rappresenta un oggetto associato all’arco
- Graph: tipo di dato astratto che rappresenta un grafo diretto o indiretto.

Le varie operazioni che possiamo applicare ad un grafo sono:

- n(): restituisce il numero di vertici nel grafo;


- veritces(): restituisce un conteiner iterabile che consente di scandire i vertici del grafo;
- m(): restituisce il numero di archi nel grafo;
- edges(): restituisce un conteinr iterabile che consente di scandire gli archi del grafo;
- getEdge(u, v): restituisce l’arco {u,v} se esiste, altrimenti null;
- getVertisces(e): restituisce un array contenente i due vertici dell’arco e
- insertVertex(u): crea e restituisce un nuovo oggetto di tipo Vertex che memorizza
l’elemento u;
- insertEdge(u,v,x): crea e restituisce un nuovo oggetto di tipo Edge che memorizza
l’elemento x e rappresenta un lato che va dal vertice u al vertice v;
- removeVertx(u): elimina dal grafo il vertice u e tutti gli archi incidenti ad esso;
- removeEdge(e): elimina del grafo l’arco e

a queste poi troviamo le solite strutture dati concrete:

Lista di archi: si utilizza una lista non ordinata contenente tutti gli archi

Lista di adiacenze: si utilizza una lista ordinata di vertici, e ad ogni vertice è associata la lista di
archi adiacenti a quel vertice

Mappa di adiacenze: si utilizza una lista ordinata di vertici e ad ogni vertice è associata una mappa
dove gli elementi hanno come chiave un vertice e ad esempio l’arco incidente

Matrice di adiacenze: si utilizza una matrice | V | x | V | ddove (i,j) ≤ i,j ≤ | V |, rappresenta l’arco
incidente ai vertici i e j.

Nota: per ogni vertice v si gestisce una collezione I(v) chiamato insieme di incidenza di v, il quale
contiene tutti gli archi incidenti a v. nel caso del grafo orientato, tutti gli archi entranti e uscenti
vengono memorizzati in due insiemi Iin(v) e Iout(v), di solito l’insieme di incidenza viene
rappresentato tramite una lista e la sua implementazione deve consentire di accedere, dato un
vertice v, all’insieme I(v) con complessità O(1).

I vertici di un grafo possono essere ritenuti vicini o lontani rispetto alla definizione di distanza.

Chiameremo cammino di un grafo una sequenza finita di archi in cui due archi contigui hanno
esattamente un vertice in comune. Il path è un cammino in tutti i vertici sono distinti.

21
La lunghezza di un cammino è il numero di archi di cui consiste.

Dato un grafo G, due vertici v e u si dicono connessi se vi è un cammino in G da u a v altrimenti si


dicono disconnessi.

Un grafo è detto connesso se per ogni coppia di vertici nel grafo, questi due sono connessi
altrimenti si dirà che il grafo è disconnesso. Un grafo orientato è fortemente connesso se per ogni
coppia di vertici u e v, esistono gli archi (u,v) e (v,u).

Per visitare un grafo abbiamo due metodi:

- visita in ampiezza: algoritmo ‘non informato’, parte da un vertice ed espande la visita


utilizzando i vicini dell’ultimo vertice visitato, diremo che la visita è esaustiva.
- Visita in profondità: algoritmo ‘non informato’, parte da un vertice e visita il primo dei
suoi vicini, continuando ricorsivamente con questa visita fin quando è possibile, anche in
questo caso diremo che la visita è esaustiva.

Le visite usano operazioni aggiuntive tra cui:

- visited(v) indica se un certo vertice v è stato visitato o meno;


- π(v) restituisce il vertice u da cui si è arrivati a v tramite un arco;
- d(v) restituisce la distanza del nodo v dal nodo di partenza.

Visitare un grafo permette di:

- ricercare un vertice in quel grafo;


- costruire un cammino minimo tra due vertici;
- verificare se un grafo è connesso;
- enumerare le componenti connesse;
- riordinare (topological sort) per grafi diretti aciclici

INPUT/OUTPUT

Chiamiamo in java input stream un oggetto dal quale possiamo leggere una sequenza di byte.
Chiamiamo invece output stream un oggetto sul quale possiamo scrivere una sequenza di byte.

Le classi astratte InputStream e OutputStream sono la base per la gerarchia delle classi degli
input/output.

InputStream ha un metodo astratto abstract int read() che legge un byte e lo restituisce, o -1 se
incontra la fine dell’input stream. Ad esempio System.in permette la lettura di un file input inserito
direttamente dalla console tramite la chiamate di uno Scanner

Public class StandardInputTest {


Public static void main(String [] args){
Scanner in = Scanner(System.in);
}
}
//permette all’utente di inserire l’input e il codice lo legge e memorizza

22
OutputStrem ha un metodo astratto abstract void write(int b) scrive un byte su una location di
output.
Sia per InputStream che per OutputStream esiste una vasta gerarchia di classi che ne derivano come
esiste una vasta gerarchia per le classi Reader e Writer.

Java inizia con il differenziare due categorie per l’input:

- FileInputStream: recupera byte da file o altre posizioni;


- DataInputStrem: permette di aggregare byte in tipi di dati più utili

Poi compaiono anche FilterInputStrem e FilterOutputStream che vengono utilizzate per aggiungere
funzionalità ai flussi input e output

Quando salviamo dei dati, è possibile scegliere in che formato salvarli, se in binario o di testo.
Quando viene utilizzato il formato testuale va considerato il character encoding (UTF-16).

La classe OutputStreamWriter trasforma un flusso di output di unità di codice in Unicode in un


flusso di byte secondo la codifica scelta.

La classe InputStramWriter trasforma un flusso di input contenente byte in un lettore che emette
unità di codice Unicode.

La classe PrintWriter ha metodi per stampare stringhe o numeri in formato testuale.

Il modo più veloce per leggere un input resta però con Scanner

try {
FileInputStream fin = new FileInputStream("traccia_B.txt");

Scanner scanner = new Scanner(fin);

//itero in hasNextLine per verificare la correttezza e se sarà possibile


l’uso di nextLine

while (scanner.hasNextLine()) {
String riga = scanner.nextLine();

altro metodo per leggere da un file può essere BufferedReader, però non permette la lettura di
numeri.

try {
FileInputStream fin = new FileInputStream("traccia_B.txt");

//avvio la lettura del file


var in = new BufferedReader(new InputStreamReader(inputStream, charset))){

String line;
while ((line = in.readLine()) != null) {
//fai qualcosa con la riga letta

A volte potrebbe risultare utile salvare gli oggetti creati in file di contenuto testuale. Per scrivere su
un text file usiamo PrintWriter, e può risultare conveniente abbinare all’interno della classe un
metodo per la scrittura come ad esempio scriviEmployee
23

Potrebbero piacerti anche