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


RIASSUNTO PROGRAMMAZIONE AD OGGETTI JAVA, Appunti di Programmazione Avanzata

Riassunto per l'esame di programmazione ad oggetti basato su Java

Tipologia: Appunti

2020/2021

In vendita dal 16/07/2021

gianluca-troisi-2
gianluca-troisi-2 🇮🇹

3

(1)

16 documenti

1 / 12

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
MODELLI DI CALCOLO – DESCRIZIONE DELL’ESECUTORE DI UN ALGORITMO
Il modello astratto è la macchina di Touring, in grado di eseguire algoritmi e dotata di un nastro
potenzialmente infinito su cui può leggere e/o scrivere dei simboli.
Il modello fisico è il calcolatore.
Il modello di riferimento per gli algoritmi è la RAM: Random Access Machine.
Il teorema di Jacopini-Bohn dice che: tutto ciò che è calcolabile con un programma con SALTI lo è anche con
un programma che usa unicamente i costrutti della programmazione strutturata: sequenza, scelta ed
iterazione.
La tesi di Church-Turing diche che: tutto ciò che è calcolabile, lo è anche da una macchina di Touring. Un
modello di calcolo è Touring-completo se ha una potenza espressiva uguale alla Macchina di Touring.
Con riferimento alla complessità computazionale di un algoritmo, usiamo la RAM. Linguaggi come il Java
hanno per un programma una complessità computazionale rispetto alla risorsa tempo e spazio che rispetto
alla RAM differisce al più per una costante moltiplicativa. Per questo siamo interessati a stimare la
complessità computazionale a meno di costanti, in funzione della dimensione dell’input (Numero di bit
necessari a codificare l’input).
TIPI PRIMITIVI E TIPI RIFERIMENTO
In Java esistono due tipi di dato: primitivi, come boolean-byte-short-int-long-char-float-double; riferimento,
come array e String. I tipi riferimento possono essere definiti dall’utente con il costrutto class.
Alle variabili di tipo riferimento sullo STACK è associato un riferimento/puntatore/indirizzo di memoria a
un’altra area di memoria (oggetto) nella HEAP. Mentre nel momento in cui viene dichiarata una variabile,
tipo primitivo, non viene creato il corrispondente oggetto.
Per creare un oggetto si usa la parola chiave NEW e per accedere ai campi di un oggetto si usa l’operatore
“.”.
Qualora una variabile di tipo riferimento assumesse il valore speciale null, indicherebbe che essa non si
riferisce a nessun oggetto.
Di seguito i valori di default: boolean false; byte-short-int-long-char 0; float-double 0.0; tipi riferimento null.
TIPI DEFINITI RICORSIVAMENTE
I tipi ricorsivi sono i tipi riferimento in cui nei campi compone lo stesso tipo che si sta definendo, come la
lista. L’Array ha una dimensione nota al momento della creazione ed un accesso in tempo costante a
qualsiasi posizione, la lista invece è completamente dinamica e con accesso in tempo lineare.
STRUTTURE DATI ELEMENTARI
Un dizionario è una struttura dati in cui: sono memorizzati elementi; ogni elemento ha una chiave univoca;
ogni elemento può contenere anche altre informazioni. Le operazioni che un dizionario mette a
disposizione dipendono dalla particolare struttura dati considerata e le principali sono: inserimento, ricerca
pf3
pf4
pf5
pf8
pf9
pfa

Anteprima parziale del testo

Scarica RIASSUNTO PROGRAMMAZIONE AD OGGETTI JAVA e più Appunti in PDF di Programmazione Avanzata solo su Docsity!

MODELLI DI CALCOLO – DESCRIZIONE DELL’ESECUTORE DI UN ALGORITMO

Il modello astratto è la macchina di Touring, in grado di eseguire algoritmi e dotata di un nastro potenzialmente infinito su cui può leggere e/o scrivere dei simboli. Il modello fisico è il calcolatore. Il modello di riferimento per gli algoritmi è la RAM: Random Access Machine. Il teorema di Jacopini-Bohn dice che: tutto ciò che è calcolabile con un programma con SALTI lo è anche con un programma che usa unicamente i costrutti della programmazione strutturata: sequenza, scelta ed iterazione. La tesi di Church-Turing diche che: tutto ciò che è calcolabile, lo è anche da una macchina di Touring. Un modello di calcolo è Touring-completo se ha una potenza espressiva uguale alla Macchina di Touring. Con riferimento alla complessità computazionale di un algoritmo, usiamo la RAM. Linguaggi come il Java hanno per un programma una complessità computazionale rispetto alla risorsa tempo e spazio che rispetto alla RAM differisce al più per una costante moltiplicativa. Per questo siamo interessati a stimare la complessità computazionale a meno di costanti, in funzione della dimensione dell’input (Numero di bit necessari a codificare l’input). TIPI PRIMITIVI E TIPI RIFERIMENTO In Java esistono due tipi di dato: primitivi, come boolean-byte-short-int-long-char-float-double; riferimento, come array e String. I tipi riferimento possono essere definiti dall’utente con il costrutto class. Alle variabili di tipo riferimento sullo STACK è associato un riferimento/puntatore/indirizzo di memoria a un’altra area di memoria (oggetto) nella HEAP. Mentre nel momento in cui viene dichiarata una variabile, tipo primitivo, non viene creato il corrispondente oggetto. Per creare un oggetto si usa la parola chiave NEW e per accedere ai campi di un oggetto si usa l’operatore “.”. Qualora una variabile di tipo riferimento assumesse il valore speciale null, indicherebbe che essa non si riferisce a nessun oggetto. Di seguito i valori di default: boolean false; byte-short-int-long-char 0; float-double 0.0; tipi riferimento null. TIPI DEFINITI RICORSIVAMENTE I tipi ricorsivi sono i tipi riferimento in cui nei campi compone lo stesso tipo che si sta definendo, come la lista. L’Array ha una dimensione nota al momento della creazione ed un accesso in tempo costante a qualsiasi posizione, la lista invece è completamente dinamica e con accesso in tempo lineare. STRUTTURE DATI ELEMENTARI Un dizionario è una struttura dati in cui: sono memorizzati elementi; ogni elemento ha una chiave univoca; ogni elemento può contenere anche altre informazioni. Le operazioni che un dizionario mette a disposizione dipendono dalla particolare struttura dati considerata e le principali sono: inserimento, ricerca

ed eliminazione. Le strutture dati elementari sono: Pile (last in first out); Code (first in first out); Liste; Alberi. static void prova () { Intero pippo = new Intero (); // nuovo oggetto di nome pippo pippo.n=5; //assegno 5 allo spazio di memoria pippo (nello STACK) che è n (nell’HEAP) pippo.n=pippo.n+1; //assegno al valore nell’indirizzo pippo il suo successivo Intero pluto = pippo; // nuovo oggetto di nome pluto pluto.n=7; System.out.println (pippo.n); // stampo System.out.println (pluto.n); } LISTE Le liste possono avere due implementazioni: singolarmente concatenate (sequenza lineare in un solo senso) o doppiamente concatenate (entrambi i sensi). Le liste possono essere inoltre lineari (ordinarie) o circolari (non ordinarie). Class Lista { Int elem; Lista next; } ALBERI L’albero è una struttura dati formata da nodi e archi. I nodi sono collegati tra loro in modo gerarchico tramite l’arco, che identifica la relazione padre-figlio. L’unico nodo che non ha padre è il nodo particolare “radice”. I nodi senza figli son detti nodi “foglia”. Il grado di un nodo è il numero di figli che ha. Il grado dell’albero è il massimo grado di un nodo. Per tutti metodi di definizione di un albero si può scrivere: class Tree { Node root; } Modo 1 di definizione quando è fissato il grado: class Node { int Key; Node p; Node c1; … Node c5; } Modo 2 di definizione quando non è fissato il grado: class Node { int Key; Node p; Node [] c; } Modo 3 di definizione quando non è fissato il grado e c’è distinzione tra figlio sinistro e destro:

 MAX-HEAP: ogni nodo ha una chiave >= di quella dei suoi figli;  MIN-HEAP: ogni nodo ha una chiave <= di quella dei suoi figli. Un Heap si potrebbe implementare in Java con strutture dinamiche simili a quelle degli ABR, ma solitamente si fa con gli array. Infatti l’unico svantaggio è che devo scegliere in anticipo il medesimo numero di elementi nell’array. static int left (int i) { Return 2I+1; } static int right (int i) { return 2i+2; } static int parent (int i) { return (i-1)/2; } class Heap { int [] a; int size; } static Heap areaHeap (int maxSize) { Heap h=new Heap(); h.a=new int[maxSize]; h.size=0; return h; } Inserimento di una chiave (faccio al più un numero di scambi uguale all’altezza dell’albero) static void heapInsert (Heap h, int Key) { if (h==null !! h.size==h.a.length) { System.out.println (“ERRORE: heap inesistente a priori”); } h.a[h.size] = Key; h.size++; int i=h.size-1; while (i>0 && h.a[i]>h.a[parent(i)]{ int temp=h.a[i]; h.a[i]=h.a[parent(i)]; h.a[parent(i)=temp; i=parent(i); } } Operazioni sul massimo elemento LETTURA DEL MASSIMO static int readMax (Heap h) { if (h==null || h.size==0) { System.out.println (“ERRORE: heap inesistente o vuoto”); return 0; } Return h.a[0]; } ESTRAZIONE DEL MASSIMO

static int exstractMax (Heap h) { if (h==null || h.size==0) { System.out.println (“ERRORE: heap inesistente o vuoto”); return 0; } Int max=h.a[0]; h.a[0]=h.a[h.size-1]; h.size--; maxHeapify (h,0); return max; } static void MaxHeapify (Heap h, int i) { int l=left(i); int r=right(i); int posMax=i; if (l<h.size && h.a[l]>h.a[posMax] { posMax=r; } if (posMax!=i) { int temp=h.a[i]; h.a[i]=h.a[posMax]; h-a[posMax]=temp; maxHeapify (h, posMax); } } HEAPSORT Per costruire l’heap-sorto occorrono due passi: costituire un heap da un array (che si vuole ordinare) dato in input e sfruttare l’heap per eseguire in loco (senza usare memoria aggiuntiva in quantità dipendente da n) l’ordinamento. Per farlo diventare un max-heap applico max-heapify a ritroso a partire dall’ultimo padre dell’albero fino alla radice. PROGRAMMAZIONE A OGGETTI La classe può essere sia contenitrice di metodi sia definitrice di nuovi tipi riferimento. Di seguito le caratteristiche:  Incapsulamento e information hiding: i metodi della classe hanno in accesso privilegio ai dati degli oggetti della stessa classe. Al contrario, questi dati possono essere nascosti alle altre classi;  Ereditarietà: classi organizzate in una gerarchia;  Polimorfismo: overloading (sovracaricamento) e overiding (sovrascrittura);  Astrazione : classi astratte e interfacce. I campi di classe si riferiscono alla classe in generale; i campi di istanza si riferiscono a una particolare istanza/oggetto della classe. I metodi di classe hanno accesso solo ai campi di classe; i metodi di istanza hanno accesso ai campi di istanza e, all’occorrenza, anche ai campi di classe. COSTRUTTORI E MODIFICATORI

 xx.length()  lunghezza della stringa;  xx.charAt(int posizione)  posizione di una lettera;  xx.concat(String yy)  unisce due stringhe;  subString (int inizio, int fine)  sottostringa da posizione inizio a posizione fine;  xx.indesOf (char c)  la posizione della prima occorrenza di “c” nella stringa. ARRAYLIST e classi involucro L’arrayList è la versione dinamica dei classici array, implementati in una libreria del Java (.Util), I metodi principali sono:  costruttore senza parametri e con la dimensione presunta;  .get(i)  accede all’elemento in posizione i;  .set(i, elem)  sostituisce l’elemento elem nella posizione i;  .add (elem)  aggiunge un elemento;  .size ()  il numero di elemento presenti nell’arraylist. Mentre gli array comuni possono contenere elementi di qualsiasi tipo (primitivo o riferimento), gli arrayList possono contenere solo elementi di tipo riferimento. Per realizzare un arrayList si usano le classi involucro: classi con oggetti immutabili che contengono in un unico campo di istanza, del tipo primitivo corrispondente: int  Integer; byte  Byte; char  Character; short  Short; long  Long; float  Float; double  Double; boolean  Boolean. La conversione automatica dei tipi di dati primitivi nell’equivalente è nota come boxing e l'operazione opposta è nota come unboxing. GARBAGE COLLECTOR = SPAZZINO Il java (la JVM) ha un sistema automatico per capire se gli oggetti creati in memoria sono ancora accessibili. Laddove non lo siano, vengono marcati per una possibile liberazione di memoria. Quando c’è necessità, viene liberata la memoria non più accessibile. EREDITARIETA’ E GERARCHIA DELLE CLASSI L’ereditarietà è la caratteristica fondamentale dei linguaggi Object Oriented. I vantaggi sono: riutilizzo del codice; astrazione; mantenibilità del codice. Le relazioni tra classi sono genitore-figlio. La classe figlia estende o specializza la classe madre ed eredita da essa tutti i campi e metodi. Un’istanza della sottoclasse è un’istanza anche della superclasse (il contrario non vale). Il tipo riferimento della sottoclasse è compatibile verso il tipo riferimento della superclasse (cast automatico). class Quadrilatero extend Poligono { … } La parola chiave super si riferisce sempre all’oggetto corrente (come this) ma nel contesto della superclasse, non è iterabile. Quando costruisco un oggetto di una sottoclasse, la prima cosa è l’invocazione del costruttore della superclasse. Ciò si traduce: se la pima riga di un costruttore della sottoclasse non è “this (…)”, allora deve esserci (esplicitamente o implicitamente ) la chiamata al costruttore della superclasse.

In Java, ogni classe ha esattamente (e sempre) una classe genitrice; non c’è ereditarietà multipla e c’è sempre una classe madre. Tutte le classi, indirettamente (se estende un’altra classe) o implicitamente (se non sempre estendibile), estendono la classe Object che è la radice della gerarchia. POLIMORFISMO Il polimorfismo è la caratteristica degli OOP che fa sì che a un “nome di metodo” corrispondano potenzialmente più comportamenti possibili. L’overloading (sovraccaricamento) si ha quando in una stessa classe possono essere presenti metodi con lo stesso nome ma con lista di parametri differente. L’overridding (sottoscrittura) si ha quando in una sottoclasse può essere presente u metodo con lo stesso nome e la stessa lista di parametri di un metodo creditato da una superclasse (stessa firma). Attenzione:  Al tipo di ritorno: deve essere compatibile verso il tipo di ritorno del metodo sovrascritto;  Al modificatore di visibilità: posso solo ampliare la visibilità nel metodo che sovrascrive. La classe Object ha alcuni metodi che vengono ereditati da tutte le classi:  toString(): o restituisce una stringa e serve a fornire una descrizione testuale dell’oggetto; o è invocato automaticamente ogni qualvolta scrive di convertire un oggetto in stringa.  Equals (Object o) che restituisce:  True se e solo se l’oggetto di cui è invocato è uguale all’oggetto passato come parametro. Implementazione: se il parametro vale null, return false. Controllo se il tipo dell’oggetto passato come parametro è quello giusto. In caso positivo, controllo se gli oggetti sono uguali. IMPORT DI PACKAGE  Classi: o import java.util.ArrayList;  importa la classe ArrayList; o import java.util.;  importa tutte le classi del package java.util; o import java.;  errore  metodi statici: o import static topolino.Pippo.pluto;  importo il metodo statico pluto della classe Pippo del package topolino; o import static topolino.Pippo.;  importa tutti i metodi statici della classe pippo del package topolino. C’è un package particolare, java.lang, che è automaticamente importato (import java.lang.). FINAL:  con la variabile locale di un metodo, rende la variabile non modificabile dopo il primo assegnamento;  con i campi di istanza, rende i campi non modificabili dopo la prima assegnazione; se tutti i campi sono final, ho degli oggetti immutabili;  con i campi static, rende i campi non modificabili. (es. Math.PI) Sono delle costanti che si inizializzano contestualmente alla dichiarazione;  con i metodi di istanza, rende i metodi non sovrascrivbili dalle sottoclassi (no overridding). TIPO STATICO E TIPO DINAMICO

 a definire dei tipi di dato astratto;  come contratto tra chi implementa una classe e chi la usa;  a simulare l’ereditarietà multipla;  ad evidenziare e sfruttare caratteristiche e funzionalità di una classe. TABELLE HASH Le tabelle hash, sia semplici che potenti molto utilizzate, hanno un contesto di riferimento: dizionari 3 operazioni fondamentali: inserimento, ricerca, cancellazione. Sono tabelle (array) a indirizzamento diretto. Risolvono il problema della lunghezza dell’array che non è possibile pensare di mantenere in memoria. Le tecniche di hashing infatti permettono di mappare elementi di un insieme grande ad elementi di un insieme piccolo. Funzione hash - - > h:U{0,1,…,k-1} con k<<|U| H mi dice in che posizione della tabella finisce un elemento dell’insieme universo. Il problema che sussiste è la collisione: poiché |U|>>k, è impossibile garantire l’assenza di collisioni. Le collisioni possono così essere gestite:  hashing esterno: liste di trabocco per ogni posizione della tabella: tutti gli elementi mappati da h in una stessa posizione della tabella sono messi in una lista di trabocco;  hashing interno: se un elemento è mappato in una posizione della tabella già occupata, a questo elemento viene assegnato un’altra posizione sempre all’interno della tabella. Le funzioni di hashing sono:

  1. modulo: h(x)=x mod k con k numero primo;
  2. moltiplicazione: h(x)=k(xA-LxA) dove 0<A<1 costante. Come definire la funzione h per l’hashing interno?
  3. Ispezione lineare: a partire dalla casella x le scelte succeddive sono le caselle successive. Può causarsi il problema dell’addensamento primario perché tendono a formarsi lunghi tratti di caselle occupate;
  4. Hashing doppio: usa due funzioni di hash ECCEZZIONI Quando in java si hanno problemi a tempo di compilazione si parla di errori: impediscono la compilazione del programma java e non viene creato il bytecode. Quando in java si hanno problemi a tempo di esecuzione si parla di eccezioni: sono delle classi con una gerarchia e ci sono dei meccanismi per sollevare o gestire le eccezioni. La gerarchia delle eccezioni è dall’alto: object  throwable  error (errori

imprevedibili) – excepion i/o exception (situazioni ordinarie) – runtime exception (errori di programmazione). Le eccezioni RuntimeException possono essere: NullPointerException, ArrayIndesOutOfBoundsExcepion, ArithimetrueException. Le eccezioni possono essere create ad-hoc dal programmatore come sottoclasse di un nodo della gerarchia. Inoltre possono essere controllate (è obbligatoria la gestione) e non controllate (derivato da RuntimeException e error). Le eccezioni si creano così come le classi. Possiamo aggiungere alle eccezioni esistenti una descrizione testuale usando il costruttore che prende una stringa. Quando definiamo una nostra eccezione, possiamo richiamare il costruttore che prende una stringa e sta nella superclasse con la parola chiave super(…). Le eccezioni si sollevano/lanciano con la sintassi: throw ; Le eccezioni si gestiscono:

  1. Catturandole;
  2. Dichiarando che vengono rilanciate verso il metodo chiamante. PROGRAMMAZIONE DINAMICA La programmazione dinamica è una tecnica di progettazione di algoritmi e si basa sulla ricorsione in modo diverso rispetto al divide et impera (soluzioni indipendenti tra loro le cui soluzioni vengono poi ricombinate). Nella programmazione dinamica si sfrutta il fatto che i sotto problemi sono correlati: la soluzione a un problema più grande è ottenuta espandendo o comunque sfruttando le soluzioni di un suo sottoproblema più piccolo. Ci sono due tecniche: memorization e top-down. SCHEDULING Lo scheduler dei processi è quel componente del sistema operativo che si occupa di decidere quale processo va mandato in esecuzione. Nell'ambito della multiprogrammazione è pensato per mantenere la CPU occupata il più possibile, ad esempio avviando un processo mentre un altro è in attesa del completamento di un'operazione di I/O. ALGORITMI GREEDY Dal termine algoritmi golosi, ad ogni passo si fa la scelta che in quel momento sembra essere la migliore. Essi sono algoritmi iterativi, semplici ed efficienti. Costituiscono la soluzione in modo bottom-up e non sempre sono applicabili (ci sono problemi per i quali non restituiscono l’ottimo). Quando e come possono essere applicati:  Deve valere la proprietà della sottostruttura ottima;  Una volta fatta una scelta iniziale greedy, deve rimanere un solo sottoproblema da risolvere;  Deve esistere sempre una soluzione ottima che contiene la scelta greedy iniziale. GRAFI I grafi sono lo strumento teorico che ci permette di modellare molte situazioni reali e risolvere dei problemi. Ci sono molte varianti di un grafo: