Esercizi
- Home
- Esercizi del capitolo 16
Esercizi del capitolo 16
Moduli
Non sarà facile iniziare ad utilizzare i moduli per i programmatori. Si tratta di un concetto che abbraccia una branca dell'informatica diversa: l'architettura software. Gli esercizi presentati di seguito puntano prima a chiarire tutti i concetti teorici, per poi creare moduli in maniera progressiva sino a creare un'architettura sostenibile, per l'applicazione che simula una rubrica creata con gli esercizi del modulo precedente (e di quello successivo).
Di seguito trovate gli esercizi del capitolo.
Per ogni esercizio, cliccando sulla traccia potete vedere la relativa soluzione.
Gli esercizi caratterizzati dall'icona sono
considerati i più complessi relativamente agli argomenti trattati.
Se preferite lavorare offline, è possibile scaricare tutti gli esercizi e le relative
soluzioni in formato PDF nella sezione download.
-
Esercizio 16.a)
Quali delle eseguenti affermazioni sono corrette:
1. Con il sistema modulare possiamo evitare eccezioni al runtime comeClassNotFoundException
.
2. Il forte incapsulamento permette di rendere accessibile un certo package solo ai package specificati.
3. Nel JDK 9 il filert.jar
è stato eliminato.
4. La JVM della versione 9 quando esegue un programma che usa i moduli deve gestirli, e quindi risulterà meno performante rispetto a quando eseguirà un programma che non utilizza i moduli.
5. Possiamo evitare di utilizzare i moduli perché esiste il concetto di modulo anonimo.
Soluzione
Le affermazioni corrette sono le numero 1, 3 e 5. L'affermazione numero 2 è scorretta perché il forte incapsulamento permette di rendere accessibile un certo package solo ai moduli specificati (non ai package specificati). L'affermazione numero 4 è scorretta perché come affermato nel paragrafo 16.1.1, le tecniche di ottimizzazione della JVM sono più efficaci nel caso siano noti a priori i tipi che saranno utilizzati nell'applicazione.
-
Esercizio 16.b)
Quali delle seguenti affermazioni sono vere?
1. Un file JAR modulare ha estensione.jmod
.
2. Il JDK 9 ha definito nuove applicazioni comejlink
e molte altre applicazioni comejdeps
del JDK sono state modificate per supportare l'introduzione dei moduli.
3. Un modulo è costituito da un unico file.
4. Le classi di alcuni package comesun.*
, e*.internal.*
sono state eliminate.
5. Un modulo automatico è un file JAR puntato dal modulepath.
Soluzione
Le uniche affermazioni corrette sono le numero 2 e 5. La numero 1 non è corretta perché un JAR modulare ha un'estensione
.jar
. Un file con estensione.jmod
invece è caratterizzato dal fatto di contenere anche risorse native. L'affermazione numero 3 è palesemente falsa: ad essere un unico file è il descrittore del modulo, non il modulo stesso. Infine l'affermazione 4 è anch'essa falsa perché i package in questione non sono stati eliminati, bensì nascosti all'utilizzo mediante il forte incapsulamento. -
Esercizio 16.c)
Quali delle seguenti affermazioni sono vere?
1. Le parole che definiscono le direttive del modulo (module
,open
,opens
,provides
,requires
,to
,transitive
,uses
ewith
) sono dette "restricted word".
2. Sia i package sia i moduli seguono la stessa convenzione per i nomi.
3. È possibile annotare il descrittore di un modulomodule-info.java
con alcune annotazioni.
4. Il descrittore di un modulomodule-info.class
deve trovarsi nella directory radice del modulo.
Soluzione
Tutte le affermazioni sono corrette.
-
Esercizio 16.d)
Spiegare cosa significa eseguire il seguente comando:
javac -d mods/com.domain.mymodule src/com.domain.mymodule/com/domain/* src/com.domain.mymodule/module-info.java
Soluzione
Con il seguente comando:
javac -d mods/com.domain.mymodule src/com.domain.mymodule/com/domain/* src/com.domain.mymodule/module-info.java
stiamo compilando un modulo di nomecom.domain.mymodule
.
In particolare con l'opzione:
-d mods/com.domain.mymodule
stiamo ordinando al comandojavac
che il risultato della compilazione dovrà essere posizionato all'interno della sottocartellacom.domain.mymodule
della cartellamods
(cartella che deve essere presente nella stessa posizione da dove stiamo eseguendo il comando di compilazione).
Con l'argomento:
src/com.domain.mymodule/com/domain/*
stiamo invece specificando che devono essere compilati tutti i file presenti nel percorsosrc/com.domain.mymodule/com/domain
.
Infine con l'argomento:
src/com.domain.mymodule/module-info.java
specifichiamo che deve essere compilato anche il descrittore del modulomodule-info.java
presente nella cartellasrc/com.domain.mymodule
. -
Esercizio 16.e)
Spiegare cosa significa eseguire il seguente comando:
java --module-path mods -m com.domain/com.domain.HelloModularWorld
Soluzione
Con il seguente comando:
java --module-path mods -m com.domain/com.domain.mymodule.HelloModularWorld
stiamo eseguendo la classe contenente il metodomain
com.domain.mymodule.HelloModularWorld
del modulo di nomecom.domain.mymodule
, specificando come modulepath la cartellamods
. -
Esercizio 16.f)
Definire i seguenti concetti riguardante l'architettura software:
1. Componente software
2. Partizionamento verticale
3. Framework (partizionamento orizzontale)
4. Coesione
5. Accoppiamento
6. Sottosistema
7. Principio di inversione della dipendenza
Soluzione
Un componente software è un insieme di classi con un'interfaccia ben definita, che mette a disposizione funzionalità eseguibili indipendentemente dal contesto. Un componente software è quindi un sottosistema eseguibile e riutilizzabile.
In un partizionamento verticale l'applicazione viene divisa per funzionalità della stessa importanza, e ognuna di queste può essere sviluppata indipendentemente dall'altra.
Un partizionamento orizzontale è un partizionamento basato soprattutto sull'ereditarietà. Un tipico esempio è quello di un framework, ovvero una micro-architettura che mette a disposizione un modello estendibile per realizzare applicazioni nell'ambito di uno specifico dominio.
La coesione è la misura di quanto un certo elemento (classe, metodo, package, sottosistema etc.) contribuisce a realizzare un certo scopo all'interno del sistema.
L'accoppiamento è la metrica che misura la dipendenza tra classi, tra package, tra metodi e così via.
Un sottosistema è un insieme di classi relazionate da associazioni, eventi e vincoli, e per il quale è possibile uno sviluppo indipendente dagli altri sottosistemi. I sottosistemi sono altamente coesi (ovvero dichiara solo tipi che collaborano tra loro per realizzare un determinato scopo), internamente sono presenti tipi altamente accoppiati, esternamente sono bassamente accoppiati con tipi di altre classi.
Il principio di inversione della dipendenza afferma che le classi devono dipendere dalle astrazioni e non dalle implementazioni. -
Esercizio 16.g)
Immaginiamo di avere creato un software gestionale per un negozio di computer. Immaginiamo anche di avere un modulo che contiene il codice che gestisce la fatturazione del negozio, e chiamiamolo
negozio.fatturazione
. Questo modulo contiene i packagenegozio.fatturazione.articoli
,negozio.fatturazione.algoritmiinterni
enegozio.fatturazione.funzionidisponibili
. Poi consideriamo un altro modulo che contiene il codice che rappresenta l'interfaccia grafica dell'applicazione, e chiamiamolonegozio.interfacciagrafica
. Dichiarare i relativi descrittori che esplicitano la dipendenza che esiste tra questi due moduli.
Soluzione
Una soluzione potrebbe essere la seguente:
module negozio.interfacciagrafica { requires negozio.fatturazione; }
e
module negozio.fatturazione { exports negozio.fatturazione.documenti; exports negozio.fatturazione.funzionidisponibili; //exports negozio.fatturazione.algoritmiinterni; }
Ovvero il modulonegozio.interfacciagrafica
legge il modulonegozio.fatturazione
, che a sua volta gli espone solo due dei tre package (si noti che la terza direttiva è commentata). Infatti il packagenegozio.fatturazione.algoritmiinterni
, considerato il nome, con tutta probabilità potrebbe non essere utilizzato direttamente dall'esterno. -
Esercizio 16.h)
Partendo dalla soluzione dell'esercizio precedente dichiarare il descrittore di un modulo che contiene il codice che permetta la vendita di articoli chiamato
negozio.vendita
, ed eventualmente modificare i descrittori già definiti. Tenere presente che:• Il modulonegozio.vendita
contiene i package chiamatinegozio.vendita.funzionidisponibili
,negozio.vendita.articoli
enegozio.vendita.algoritmiinterni
.
• Dall'interfaccia grafica sarà possibile vendere articoli del negozio.
• Contestualmente alla vendita deve essere possibile fatturare l'articolo venduto.
Soluzione
Muovendoci in un contesto tanto astratto, è possibile ipotizzare diverse soluzioni. Partendo dal presupposto che il descrittore del modulo
negozio.fatturazione
non sarà modificato, potremmo semplicemente esportare i giusti package dal modulonegozio.vendita
:
module negozio.vendita { exports negozio.vendita.articoli; exports negozio.vendita.funzionidisponibili; //exports negozio.vendita.algoritmiinterni; }
e far in modo che il modulonegozio.interfacciagrafica
, legga anchenegozio.vendita
:
module negozio.interfacciagrafica { requires negozio.fatturazione; requires negozio.vendita; }
delegando così all'interfaccia grafica l'onere di definire una funzione che vada a contestualizzare la vendita e la fatturazione di un articolo in un'unica richiesta da parte dell'utente. Siccome non è una buona pratica assegnare delle regole di business ad un'interfaccia grafica (che deve già definire le regole di presentazione dell'interfaccia stessa), decidiamo di rendere la situazione più flessibile, in modo tale da poter prendere delle decisioni più tardi. Allora utilizzando la direttivarequires transitive
, andiamo a modificare il descrittore del modulonegozio.vendita
, in modo tale che possa leggere (e far leggere) il modulonegozio.fatturazione
:
module negozio.vendita { exports negozio.vendita.articoli; exports negozio.vendita.funzionidisponibili; //exports negozio.vendita.algoritmiinterni; requires transitive negozio.fatturazione; }
A questo punto il modulonegozio.interfacciagrafica
può leggere solonegozio.vendita
, visto che transitivamente leggerà anchenegozio.fatturazione
:
module negozio.interfacciagrafica { //requires negozio.fatturazione; requires negozio.vendita; }
-
Esercizio 16.i)
Partendo dalla soluzione dell'esercizio precedente, cosa possiamo fare se volessimo che il modulo
negozio.fatturazione
legganegozio.interfacciagrafica
?
Soluzione
In realtà non si può fare nulla a meno che non si vogliano rivalutare tutte le dipendenze già specificate. Se
negozio.fatturazione
volesse leggerenegozio.interfacciagrafica
, otterremmo un errore di dipendenza ciclica. -
Esercizio 16.l)
Partendo dalla soluzione dell'esercizio 16.h, cosa possiamo fare per permettere al modulo
negozio.interfacciagrafica
di accedere tramite reflection ad un metodo privato definito nel packagenegozio.fatturazione.algoritmiinterni
?
Soluzione
La soluzione consiste nel rivedere il descrittore del modulo
negozio.fatturazione
esportando il packagenegozio.vendita.algoritmiinterni
, e contemporaneamente aprirlo al modulonegozio.interfacciagrafica
:
module negozio.fatturazione { exports negozio.fatturazione.articoli; exports negozio.fatturazione.funzionidisponibili; exports negozio.fatturazione.algoritmiinterni; opens negozio.fatturazione.algoritmiinterni to negozio.interfacciagrafica; }
-
Esercizio 16.m)
Consideriamo la soluzione dell'esercizio precedente. Se non è possibile modificare più il nostro codice, ma ci siamo accorti che una classe del package
negozio.vendita
deve utilizzare un metodo privato del packagenegozio.fatturazione.algoritmiinterni
tramite reflection, cosa possiamo fare?
Soluzione
L'unica soluzione è eseguire il programma specificando da riga di comando l'apertura del modulo
negozio.fatturazione
al modulonegozio.vendita
con la seguente sintassi:
-add-opens negozio.fatturazione/negozio.fatturazione.algoritmiinterni=negozio.vendita
-
Esercizio 16.n)
Quali delle seguenti affermazioni è corretta:
1. La classeServiceLoader
è stata introdotta già nella versione 6 di Java.
2. Il componente service provider interface (SPI) dipende dalle sue implementazioni.
3. Con la classeServiceLoader
possiamo eliminare completamente la dipendenza tra moduli.
4. Per implementare un servizio conServiceLoader
, le implementazioni di una service provider interface devono essere esportate dai rispettivi moduli.
5. Un metodo provider è una sorta di metodo factory.
Soluzione
Le affermazioni corrette sono le numero 1, e 5. L'affermazione numero 2 è falsa, perché sono le implementazioni a dipendere dal service provider interface. L'affermazione numero 4 è falsa in quanto non è necessario esportare le implementazioni del service provider interface, bensì bisogna usare la direttiva
provides to
. -
Esercizio 16.o)
Partendo dalla soluzione dell'esercizio 15.n, definire dei package per le varie classi. Poi creare e compilare un modulo che esporta il package che contiene tutte le classi che rappresentano dati.
Soluzione
Una possibile soluzione è quella di creare un modulo che chiameremo
com.claudiodesio.rubrica.dati
. All'interno inseriremo le classiContatto
eDato
, a loro volta modificati per appartenere ad un package che coincide con il nome del modulo:
package com.claudiodesio.rubrica.dati; import java.io.Serializable; public interface Dato extends Serializable { }
e
package com.claudiodesio.rubrica.dati; import java.io.Serializable; public class Contatto implements Dato { private static final long serialVersionUID = 8942402240056525661L; private String nome; private String indirizzo; private String numeroDiTelefono; public Contatto (String nome, String indirizzo, String numeroDiTelefono) { this.nome = nome; this.indirizzo = indirizzo; this.numeroDiTelefono = numeroDiTelefono; } public void setNumeroDiTelefono(String numeroDiTelefono) { this.numeroDiTelefono = numeroDiTelefono; } public String getNumeroDiTelefono() { return numeroDiTelefono; } public void setIndirizzo(String indirizzo) { this.indirizzo = indirizzo; } public String getIndirizzo() { return indirizzo; } public void setNome(String nome) { this.nome = nome; } public String getNome() { return nome; } @Override public String toString() { return "Nome:\t" + nome + "\nIndirizzo:\t" + indirizzo + "\nTelefono:\t" + numeroDiTelefono; } }
I package delle altre classi li potremo vedere nelle soluzioni dei prossimi esercizi.
Il descrittore del modulo sarà il seguente:
module com.claudiodesio.rubrica.dati { exports com.claudiodesio.rubrica.dati; }
Per compilare il modulo utilizzeremo il seguente comando utilizzando la solita struttura di cartelle che abbiamo utilizzato sinora (l'esercizio completo è nella cartella Codice\capitolo_16\esercizi\16.o del file contenente gli esempi di codice che avete probabilmente scaricato nella sezione download di questo sito):
javac -d mods/com.claudiodesio.rubrica.dati src/com.claudiodesio.rubrica.dati/module-info.java src/com.claudiodesio.rubrica.dati/com/claudiodesio/rubrica/dati/*.java
-
Esercizio 16.p)
Partendo dalla soluzione dell'esercizio 15.o, creare un modulo che esporti il package che contiene le eccezioni.
Per compilare con un unico comando più moduli solitamente si utilizza l'attributo--module-source-path
. Per esempio per compilare l'esercizio del paragrafo 16.3 sulServiceLoader
, abbiamo usato il seguente comando:
javac -d mods --module-source-path src src/com.claudiodesio.spi/module-info.java src/com.claudiodesio.spi/com/claudiodesio/spi/* src/com.claudiodesio.invs/module-info.java src/com.claudiodesio.invs/com/claudiodesio/invs/* src/com.claudiodesio.certs/module-info.java src/com.claudiodesio.certs/com/claudiodesio/certs/* src/com.claudiodesio.factory/module-info.java src/com.claudiodesio.factory/com/claudiodesio/factory/* src/com.claudiodesio.handlers/module-info.java src/com.claudiodesio.handlers/com/claudiodesio/handlers/* src/com.claudiodesio.client/module-info.java src/com.claudiodesio.client/com/claudiodesio/client/*
(scritto tutto su una riga senza andare a capo).
Soluzione
Una possibile soluzione è quella di creare un modulo che chiameremo
com.claudiodesio.rubrica.eccezioni
. All'interno inseriremo le classiContattoInesistenteException
eContattoEsistenteException
, a loro volta modificate per appartenere ad un package che coincide con il nome del modulo:
package com.claudiodesio.rubrica.eccezioni; import java.io.IOException; public class ContattoInesistenteException extends IOException { private static final long serialVersionUID = 8942402240056525663L; public ContattoInesistenteException(String message) { super(message); } }
e
package com.claudiodesio.rubrica.eccezioni; import java.io.IOException; public class ContattoEsistenteException extends IOException { private static final long serialVersionUID = 8942402240056525662L; public ContattoEsistenteException (String message) { super(message); } }
Il descrittore del modulo sarà il seguente:
module com.claudiodesio.rubrica.eccezioni { exports com.claudiodesio.rubrica.eccezioni; }
javac -d mods --module-source-path src src/com.claudiodesio.rubrica.dati/module-info.java src/com.claudiodesio.rubrica.dati/com/claudiodesio/rubrica/dati/*.java src/com.claudiodesio.rubrica.eccezioni/module-info.java src/com.claudiodesio.rubrica.eccezioni/com/claudiodesio/ rubrica/eccezioni/*.java
-
Esercizio 16.q)
Partendo dalla soluzione dell'esercizio 16.p, si crei un modulo che esporti il package contenente le classi di utilità.
Soluzione
Una possibile soluzione è quella di creare un modulo che chiameremo
com.claudiodesio.rubrica.util
. All'interno abbiamo deciso di inserire le classiRetriever
,Executor
eFileUtils
, a loro volta modificate per appartenere ad un package che coincide con il nome del modulo. Seguono le dichiarazioni delle tre classi:
package com.claudiodesio.rubrica.util; @FunctionalInterface public interface Retriever<O> { O esegui() throws Exception; }
e
package com.claudiodesio.rubrica.util; @FunctionalInterface public interface Executor { void esegui() throws Exception; }
e
package com.claudiodesio.rubrica.util; public class FileUtils { public static final String SUFFIX = ".con"; public static String getNomeFile(String nome) { return nome + SUFFIX; } }
Il descrittore del modulo sarà il seguente:
module com.claudiodesio.rubrica.util { exports com.claudiodesio.rubrica.util; }
Per compilare i tre moduli definiti, utilizzeremo il seguente comando utilizzando la solita struttura di cartelle che abbiamo utilizzato sinora (l'esercizio completo è nella cartella Codice\capitolo_16\esercizi\16.q del file contenente gli esempi di codice):
javac -d mods --module-source-path src src/com.claudiodesio.rubrica.dati/module-info.java src/com.claudiodesio.rubrica.dati/com/claudiodesio/rubrica/dati/*.java src/com.claudiodesio.rubrica.eccezioni/module-info.java src/com.claudiodesio.rubrica.eccezioni/com/claudiodesio/rubrica/ eccezioni/*.java src/com.claudiodesio.rubrica.util/module-info.java src/com.claudiodesio.rubrica.util/com/claudiodesio/rubrica/util/*.java
-
Esercizio 16.r)
Partendo dalla soluzione dell'esercizio 16.q, creare un modulo che esporti il package contenente la classe che gioca il ruolo di service provider interface. Il nostro obiettivo è quello di trasformare il modo in cui serializziamo i contatti con servizi di
ServiceLoader
.
Soluzione
La classe che farà da service provider interface è
GestioneSerializzazione
, che faremo appartenere al modulocom.claudiodesio.rubrica.spi
. Abbiamo modificato la classe per appartenere al packagecom.claudiodesio.rubrica.spi
:
package com.claudiodesio.rubrica.spi; import com.claudiodesio.rubrica.dati.Dato; import java.io.*; import java.util.*; public interface GestoreSerializzazione<T extends Dato> { void inserisci(T dato) throws IOException; T recupera(String id) throws IOException, ClassNotFoundException; void modifica(T dato) throws IOException; void rimuovi(String id) throws IOException; }
Il descrittore del modulo sarà il seguente:
module com.claudiodesio.rubrica.spi { exports com.claudiodesio.rubrica.spi; requires com.claudiodesio.rubrica.dati; }
In questo caso c'è stato bisogno di leggere il modulocom.claudiodesio.rubrica.dati
, visto che la classeGestoreSerializzazione
utilizza l'interfacciaDato
. Per compilare i quattro moduli definiti sinora utilizzeremo il seguente comando utilizzando la solita struttura di cartelle (l'esercizio completo è nella cartella Codice\capitolo_16\esercizi\16.r del file contenente gli esempi di codice):
javac -d mods --module-source-path src src/com.claudiodesio.rubrica.dati/module-info.java src/com.claudiodesio.rubrica.dati/com/claudiodesio/rubrica/dati/*.java src/com.claudiodesio.rubrica.eccezioni/module-info.java src/com.claudiodesio.rubrica.eccezioni/com/claudiodesio/rubrica/ eccezioni/*.java src/com.claudiodesio.rubrica.util/module-info.java src/com.claudiodesio.rubrica.util/com/claudiodesio/rubrica/util/*.java src/com.claudiodesio.rubrica.spi/module-info.java src/com.claudiodesio.rubrica.spi/com/claudiodesio/rubrica/spi/*.java
-
Esercizio 16.s)
Riportiamo di seguito la classe
Esercizio16N
che rappresenta il client della rubrica creata negli esercizi del capitolo 15:
import java.util.function.*; public class Esercizio15N { private GestoreSerializzazione<Contatto> gestoreFile; private Contatto[] contatti; Esercizio15N() { contatti = getContatti(); gestoreFile = new GestoreFileNIO2(); } private void eseguiTest() { System.out.println("TESTIAMO LA CREAZIONE DEI TRE CONTATTI"); creaContatti(); System.out.println("RECUPERIAMO I TRE CONTATTI"); recuperaContatti(); System.out.println( "TESTIAMO LA CREAZIONE DI UN CONTATTO GIÀ ESISTENTE"); creaContattoEsistente(); System.out.println("PROVIAMO A RECUPERARE UN CONTATTO NON ESISTENTE"); recuperaContattoNonEsistente(); System.out.println("MODIFICHIAMO UN CONTATTO ESISTENTE"); modificaContattoEsistente(); System.out.println("RIMUOVIAMO UN CONTATTO ESISTENTE"); rimuoviContattoEsistente(); System.out.println("MODIFICHIAMO UN CONTATTO NON ESISTENTE"); modificaContattoNonEsistente(); System.out.println("RIMUOVIAMO UN CONTATTO NON ESISTENTE"); rimuoviContattoNonEsistente(); } public void creaContattoEsistente() { esegui(()->gestoreFile.inserisci(contatti[0])); } public void modificaContattoEsistente() { esegui(()->gestoreFile.modifica(new Contatto( "Daniele","Via dei microfoni 1","07890"))); } public void rimuoviContattoEsistente() { esegui(()->gestoreFile.rimuovi(contatti[2].getNome())); } public void modificaContattoNonEsistente() { esegui(()->gestoreFile.modifica( new Contatto("Pluto","Via dei microfoni 1","07890"))); } public void rimuoviContattoNonEsistente() { esegui(()->gestoreFile.rimuovi("Ligeia")); } public void recuperaContattoNonEsistente() { esegui(()->gestoreFile.recupera("Pippo")); } public void creaContatti() { for (Contatto contatto : contatti) { System.out.println("Creazione contatto:\n"+ contatto); creaContatto(contatto); } } public void recuperaContatti() { for (Contatto contatto : contatti) { System.out.println("Recupero contatto: "+ contatto.getNome()); recuperaContatto(contatto.getNome()); } } public void recuperaContatto(String nomeContatto) { esegui(()->gestoreFile.recupera(nomeContatto)); } private void creaContatto(Contatto contatto) { esegui(()->gestoreFile.inserisci(contatto)); } private Contatto[] getContatti() { Contatto contatto1 = new Contatto( "Daniele","Via delle chitarre 1","01234560"); Contatto contatto2 = new Contatto( "Giovanni","Via delle scienze 2","0565432190"); Contatto contatto3 = new Contatto( "Ligeia","Via dei segreti 3","07899921"); Contatto[] contatti = { contatto1, contatto2, contatto3 } ; return contatti; } public <O> O esegui(Retriever<O> retriever) { O output = null; try { output = retriever.esegui(); } catch (Exception exc) { System.out.println(exc.getMessage()); } return output; } public void esegui(Executor executor) { try { executor.esegui(); } catch (Exception exc) { System.out.println(exc.getMessage()); } } public static void main(String args[]) { Esercizio15N esercizio15N = new Esercizio15N(); esercizio15N.eseguiTest(); } }
Concentriamoci sulla variabile d'istanza
GestoreFile
la sua creazione nel costruttore. Si noti come nel costruttore viene creato esplicitamente un oggetto della classeGestoreNIO2
, ed assegnato ad un reference di tipoGestoreSerializzazione
. Il nostro obiettivo per questo esercizio sarà quello di creare due moduli diversi per trasformare in servizi i due modi in cui abbiamo serializzato gli oggettiContatto
nel capitolo 15GestoreFile
eGestoreNIO2
. Poi nel prossimo esercizio li utilizzeremo.
Soluzione
Per prima cosa abbiamo dovuto aggiungere alle classi
GestoreFile
eGestoreFileNIO2
, oltre alla dichiarazione dei package, diversiimport
:
package com.claudiodesio.rubrica.io; import com.claudiodesio.rubrica.spi.GestoreSerializzazione; import com.claudiodesio.rubrica.dati.Contatto; import com.claudiodesio.rubrica.eccezioni.*; import com.claudiodesio.rubrica.util.*; import java.util.*; import java.io.*; public class GestoreFile implements GestoreSerializzazione<Contatto> { @Override public void inserisci(Contatto contatto) throws ContattoEsistenteException, FileNotFoundException, IOException { Contatto contattoEsistente = getContatto(contatto.getNome()); if (contattoEsistente != null) { throw new ContattoEsistenteException( contatto.getNome() + ": contatto già esistente!"); } registra( contatto); } @Override public Contatto recupera(String nome) throws ContattoInesistenteException, ContattoEsistenteException { Contatto contatto = getContatto(nome); if (contatto == null) { throw new ContattoInesistenteException( nome + ": contatto non trovato!"); } return contatto; } @Override public void modifica(Contatto contatto) throws ContattoInesistenteException, ContattoEsistenteException, FileNotFoundException, IOException { if (isContattoEsistente(contatto.getNome())) { registra(contatto); } else { throw new ContattoInesistenteException( contatto.getNome() + ": contatto non trovato!"); } } @Override public void rimuovi(String nome) throws ContattoInesistenteException, ContattoEsistenteException, FileNotFoundException, IOException { File file = new File(FileUtils.getNomeFile(nome)); if (file.delete()) { System.out.println("Contatto " + nome + " cancellato!"); } else { throw new ContattoInesistenteException( nome + ": contatto non trovato!"); } } private void registra(Contatto contatto) throws FileNotFoundException, IOException { try (FileOutputStream fos = new FileOutputStream (new File( FileUtils.getNomeFile(contatto.getNome()))); ObjectOutputStream s = new ObjectOutputStream (fos);) { s.writeObject (contatto); System.out.println("Contatto registrato:\n" + contatto); } } private boolean isContattoEsistente(String nome) { File file = new File(FileUtils.getNomeFile(nome)); return file.exists(); } private Contatto getContatto(String nome) { try (FileInputStream fis = new FileInputStream ( new File(FileUtils.getNomeFile(nome))); ObjectInputStream ois = new ObjectInputStream (fis);) { Contatto contatto = (Contatto)ois.readObject(); System.out.println("Contatto recuperato:\n"+ contatto); return contatto; } catch (Exception exc) { return null; } } }
e
package com.claudiodesio.rubrica.nio; import com.claudiodesio.rubrica.spi.GestoreSerializzazione; import com.claudiodesio.rubrica.dati.Contatto; import com.claudiodesio.rubrica.eccezioni.*; import com.claudiodesio.rubrica.util.*; import java.util.*; import java.io.*; import java.util.*; import java.io.*; import java.nio.file.*; public class GestoreFileNIO2 implements GestoreSerializzazione<Contatto> { @Override public void inserisci(Contatto contatto) throws ContattoEsistenteException, FileNotFoundException, IOException { Path path = Paths.get(FileUtils.getNomeFile(contatto.getNome())); if (Files.exists(path)) { throw new ContattoEsistenteException( contatto.getNome() + ": contatto già esistente!"); } registra(contatto); } @Override public Contatto recupera(String nome) throws ContattoInesistenteException, ContattoEsistenteException { Contatto contatto = getContatto(nome); if (contatto == null) { throw new ContattoInesistenteException( nome + ": contatto non trovato!"); } return contatto; } @Override public void modifica(Contatto contatto) throws ContattoInesistenteException, ContattoEsistenteException, FileNotFoundException, IOException { if (isContattoEsistente(contatto.getNome())) { registra(contatto); } else { throw new ContattoInesistenteException( contatto.getNome() + ": contatto non trovato!"); } } @Override public void rimuovi(String nome) throws ContattoInesistenteException, ContattoEsistenteException, FileNotFoundException, IOException { Path path = Paths.get(FileUtils.getNomeFile(nome)); if (Files.exists(path)) { Files.delete(path); System.out.println("Contatto " + nome + " cancellato!"); } else { throw new ContattoInesistenteException( nome + ": contatto non trovato!"); } } private void registra(Contatto contatto) throws FileNotFoundException, IOException { Path path = Paths.get(FileUtils.getNomeFile(contatto.getNome())); Files.write(path, getBytesDaOggetto(contatto)); System.out.println("Contatto registrato:\n" + contatto); } private byte[] getBytesDaOggetto(Object object) throws IOException { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = new ObjectOutputStream(bos)) { out.writeObject(object); return bos.toByteArray(); } } private Object getOggettoDaByte(byte[] bytes) throws IOException, ClassNotFoundException { try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInput in = new ObjectInputStream(bis)) { return in.readObject(); } } private boolean isContattoEsistente(String nome) { Path path = Paths.get(FileUtils.getNomeFile(nome)); return Files.exists(path); } private Contatto getContatto(String nome) { Path path = Paths.get(FileUtils.getNomeFile(nome)); byte[] bytes = null; Contatto contatto = null; try { bytes = Files.readAllBytes(path); contatto = (Contatto)getOggettoDaByte(bytes); System.out.println("Contatto recuperato:\n" + contatto); } catch (Exception exc) { return null; } return contatto; } }
Il primo modulo conterràGestoreFile
e sarà chiamatocom.claudiodesio.rubrica.io
, mentreGestoreFileNIO2
sarà contenuto nel modulocom.claudiodesio.rubrica.nio
.
Per il primo modulo possiamo creare il seguente descrittore:
module com.claudiodesio.rubrica.io { //exports com.claudiodesio.rubrica.io; provides com.claudiodesio.rubrica.spi.GestoreSerializzazione with com.claudiodesio.rubrica.io.GestoreFile; requires com.claudiodesio.rubrica.dati; requires com.claudiodesio.rubrica.eccezioni; requires com.claudiodesio.rubrica.spi; requires com.claudiodesio.rubrica.util; }
Mentre per il secondo possiamo creare quest'altro:
module com.claudiodesio.rubrica.nio { //exports com.claudiodesio.rubrica.nio; provides com.claudiodesio.rubrica.spi.GestoreSerializzazione with com.claudiodesio.rubrica.nio.GestoreFileNIO2; requires com.claudiodesio.rubrica.dati; requires com.claudiodesio.rubrica.eccezioni; requires com.claudiodesio.rubrica.spi; requires com.claudiodesio.rubrica.util; }
Per compilare i sei moduli definiti sinora, utilizzeremo il seguente comando utilizzando la solita struttura di cartelle (l'esercizio completo è nella cartella Codice\capitolo_16\esercizi\16.s del file contenente gli esempi di codice):
javac -d mods --module-source-path src src/com.claudiodesio.rubrica.dati/module-info.java src/com.claudiodesio.rubrica.dati/com/claudiodesio/rubrica/dati/*.java src/com.claudiodesio.rubrica.eccezioni/module-info.java src/com.claudiodesio.rubrica.eccezioni/com/claudiodesio/rubrica/ eccezioni/*.java src/com.claudiodesio.rubrica.util/module-info.java src/com.claudiodesio.rubrica.util/com/claudiodesio/rubrica/util/*.java src/com.claudiodesio.rubrica.spi/module-info.java src/com.claudiodesio.rubrica.spi/com/claudiodesio/rubrica/spi/*.java src/com.claudiodesio.rubrica.io/module-info.java src/com.claudiodesio.rubrica.io/com/claudiodesio/rubrica/io/*.java src/com.claudiodesio.rubrica.nio/module-info.java src/com.claudiodesio.rubrica.nio/com/claudiodesio/rubrica/nio/*.java
-
Esercizio 16.t)
Partendo dalla soluzione dell'esercizio 16.s, rinominare la classe
Esercizio15N
inEsercizio16T
, e sfruttare ilServiceLoader
per caricare i servizi. Fare in modo che sia possibile specificare da riga di comando quale gestore di serializzazione si deve usare. Poi creare un modulo che contenga tale classe ed eseguire l'applicazione.
Soluzione
Modifichiamo la classeEsercizio15N
inEsercizio16T
per sfruttare ilServiceLoader
e caricare i servizi dichiarati nell'esercizio precedente. Inoltre abbiamo rielaborato i diversiimport
e dichiarato il package di appartenenza.
package com.claudiodesio.rubrica.test; import java.util.function.*; import com.claudiodesio.rubrica.spi.GestoreSerializzazione; import com.claudiodesio.rubrica.dati.Contatto; import com.claudiodesio.rubrica.util.*; import java.util.Iterator; import java.util.ServiceLoader; public class Esercizio16T { private GestoreSerializzazione<Contatto> gestoreFile; private Contatto[] contatti; public Esercizio16T(String className) { contatti = getContatti(); gestoreFile = getGestoreSerializzazione(className); } public GestoreSerializzazione<Contatto> getGestoreSerializzazione(String className) { ServiceLoader<GestoreSerializzazione> serviceLoader = ServiceLoader.load( com.claudiodesio.rubrica.spi.GestoreSerializzazione.class); for (GestoreSerializzazione<Contatto> gestoreSerializzazione : serviceLoader) { if (gestoreSerializzazione.getClass().getSimpleName(). equals(className)) { return gestoreSerializzazione; } } throw new IllegalArgumentException( "Nessun gestore di serializzazione trovato per classe =" + className); } private void eseguiTest() { System.out.println("TESTIAMO LA CREAZIONE DEI TRE CONTATTI"); creaContatti(); System.out.println("RECUPERIAMO I TRE CONTATTI"); recuperaContatti(); System.out.println( "TESTIAMO LA CREAZIONE DI UN CONTATTO GIÀ ESISTENTE"); creaContattoEsistente(); System.out.println("PROVIAMO A RECUPERARE UN CONTATTO NON ESISTENTE"); recuperaContattoNonEsistente(); System.out.println("MODIFICHIAMO UN CONTATTO ESISTENTE"); modificaContattoEsistente(); System.out.println("RIMUOVIAMO UN CONTATTO ESISTENTE"); rimuoviContattoEsistente(); System.out.println("MODIFICHIAMO UN CONTATTO NON ESISTENTE"); modificaContattoNonEsistente(); System.out.println("RIMUOVIAMO UN CONTATTO NON ESISTENTE"); rimuoviContattoNonEsistente(); } public void creaContattoEsistente() { esegui(()->gestoreFile.inserisci(contatti[0])); } public void modificaContattoEsistente() { esegui(()->gestoreFile.modifica( new Contatto("Daniele","Via dei microfoni 1","07890"))); } public void rimuoviContattoEsistente() { esegui(()->gestoreFile.rimuovi(contatti[2].getNome())); } public void modificaContattoNonEsistente() { esegui(()->gestoreFile.modifica( new Contatto("Pluto","Via dei microfoni 1","07890"))); } public void rimuoviContattoNonEsistente() { esegui(()->gestoreFile.rimuovi("Ligeia")); } public void recuperaContattoNonEsistente() { esegui(()->gestoreFile.recupera("Pippo")); } public void creaContatti() { for (Contatto contatto : contatti) { System.out.println("Creazione contatto:\n" + contatto); creaContatto(contatto); } } public void recuperaContatti() { for (Contatto contatto : contatti) { System.out.println("Recupero contatto: " + contatto.getNome()); recuperaContatto(contatto.getNome()); } } public void recuperaContatto(String nomeContatto) { esegui(()->gestoreFile.recupera(nomeContatto)); } private void creaContatto(Contatto contatto) { esegui(()->gestoreFile.inserisci(contatto)); } private Contatto[] getContatti() { Contatto contatto1 = new Contatto("Daniele","Via delle chitarre 1","01234560"); Contatto contatto2 = new Contatto("Giovanni","Via delle scienze 2","0565432190"); Contatto contatto3 = new Contatto("Ligeia","Via dei segreti 3","07899921"); Contatto[] contatti = {contatto1, contatto2, contatto3} ; return contatti; } public <O> O esegui(Retriever<O> retriever) { O output = null; try { output = retriever.esegui(); } catch (Exception exc) { System.out.println(exc.getMessage()); } return output; } public void esegui(Executor executor) { try { executor.esegui(); } catch (Exception exc) { System.out.println(exc.getMessage()); } } public static void main(String args[]) { Esercizio16T esercizio16T = new Esercizio16T(args[0]); esercizio16T.eseguiTest(); } }
Notare il metodo factorygetGestoreSerializzazione
che gestisce quale servizio utilizzare (si basa sulla specifica del nome della classe). Poi abbiamo definito il relativo modulo di cui riportiamo il descrittore di seguito, che può accedere tramite reflection aGestoreSerializzazione
:
modules com.claudiodesio.rubrica.test { uses com.claudiodesio.rubrica.spi.GestoreSerializzazione; requires com.claudiodesio.rubrica.spi; requires com.claudiodesio.rubrica.dati; requires com.claudiodesio.rubrica.util; }
Possiamo adesso compilare i sette moduli definiti sinora con il seguente comando (come sempre l'esercizio completo è nella cartella Codice\capitolo_16\esercizi\16.t del file contenente gli esempi di codice):
javac -d mods --module-source-path src src/com.claudiodesio.rubrica.dati/module-info.java src/com.claudiodesio.rubrica.dati/com/claudiodesio/rubrica/dati/*.java src/com.claudiodesio.rubrica.eccezioni/module-info.java src/com.claudiodesio.rubrica.eccezioni/com/claudiodesio/rubrica/ eccezioni/*.java src/com.claudiodesio.rubrica.util/module-info.java src/com.claudiodesio.rubrica.util/com/claudiodesio/rubrica/util/*.java src/com.claudiodesio.rubrica.spi/module-info.java src/com.claudiodesio.rubrica.spi/com/claudiodesio/rubrica/spi/*.java src/com.claudiodesio.rubrica.io/module-info.java src/com.claudiodesio.rubrica.io/com/claudiodesio/rubrica/io/*.java src/com.claudiodesio.rubrica.nio/module-info.java src/com.claudiodesio.rubrica.nio/com/claudiodesio/rubrica/nio/*.java src/com.claudiodesio.rubrica.test/module-info.java src/com.claudiodesio.rubrica.test/com/claudiodesio/rubrica/test/*.java
Per eseguire l'applicazione possiamo utilizzare il seguente comando:
java --module-path mods -m com.claudiodesio.rubrica.test/ com.claudiodesio.rubrica.test.Esercizio16T GestoreFile
Che stamperà lo stesso output visto nella soluzione dell'esercizio 15.n e che riportiamo di seguito per comodità:
TESTIAMO LA CREAZIONE DEI TRE CONTATTI Creazione contatto: Nome: Daniele Indirizzo: Via delle chitarre 1 Telefono: 01234560 Daniele: contatto già esistente! Creazione contatto: Nome: Giovanni Indirizzo: Via delle scienze 2 Telefono: 0565432190 Giovanni: contatto già esistente! Creazione contatto: Nome: Ligeia Indirizzo: Via dei segreti 3 Telefono: 07899921 Contatto registrato: Nome: Ligeia Indirizzo: Via dei segreti 3 Telefono: 07899921 RECUPERIAMO I TRE CONTATTI Recupero contatto: Daniele Contatto recuperato: Nome: Daniele Indirizzo: Via dei microfoni 1 Telefono: 07890 Recupero contatto: Giovanni Contatto recuperato: Nome: Giovanni Indirizzo: Via delle scienze 2 Telefono: 0565432190 Recupero contatto: Ligeia Contatto recuperato: Nome: Ligeia Indirizzo: Via dei segreti 3 Telefono: 07899921 TESTIAMO LA CREAZIONE DI UN CONTATTO GIÀ ESISTENTE Daniele: contatto già esistente! PROVIAMO A RECUPERARE UN CONTATTO NON ESISTENTE Pippo: contatto non trovato! MODIFICHIAMO UN CONTATTO ESISTENTE Contatto registrato: Nome: Daniele Indirizzo: Via dei microfoni 1 Telefono: 07890 RIMUOVIAMO UN CONTATTO ESISTENTE Contatto Ligeia cancellato! MODIFICHIAMO UN CONTATTO NON ESISTENTE Pluto: contatto non trovato! RIMUOVIAMO UN CONTATTO NON ESISTENTE Ligeia: contatto non trovato!
Per utilizzare la classeGestoreFileNIO2
basterà specificarla come argomento da riga di comando al posto diGestoreFile
. -
Esercizio 16.u)
Partendo dalla soluzione dell'esercizio 16.t, creare una classe factory e spostare al suo interno il metodo
getGestoreSerializzazione
presente nella classeEsercizio16T
e il relativo modulo. Modificare di conseguenza anche il descrittore del modulocom.claudiodesio.rubrica.test
.
Soluzione
Riscriviamo così il costruttore della classe
Esercizio16U
:
public Esercizio16U(String className) { contatti = getContatti(); gestoreFile = GestoreSerializzazioneFactory.getGestoreSerializzazione(className); }
nel quale si invoca il metodo (reso statico)getGestoreSerializzazione
della classeGestoreSerializzazioneFactory
. Infatti ecco la classeGestoreSerializzazioneFactory
:
package com.claudiodesio.rubrica.factory; import com.claudiodesio.rubrica.spi.GestoreSerializzazione; import com.claudiodesio.rubrica.dati.Contatto; import java.util.Iterator; import java.util.ServiceLoader; public class GestoreSerializzazioneFactory { public static GestoreSerializzazione<Contatto> getGestoreSerializzazione(String className) { ServiceLoader<GestoreSerializzazione> serviceLoader = ServiceLoader.load( com.claudiodesio.rubrica.spi.GestoreSerializzazione.class); for (GestoreSerializzazione gestoreSerializzazione : serviceLoader) { if (gestoreSerializzazione.getClass().getSimpleName() .equals(className)) { return gestoreSerializzazione; } } throw new IllegalArgumentException( "Nessun gestore di serializzazione trovato per classe =" + className); } }
Il descrittore del nuovo modulocom.claudiodesio.rubrica.factory
è il seguente:
module com.claudiodesio.rubrica.factory { exports com.claudiodesio.rubrica.factory class com.claudiodesio.rubrica.test; requires com.claudiodesio.rubrica.spi; requires com.claudiodesio.rubrica.dati; uses com.claudiodesio.rubrica.spi.GestoreSerializzazione; }
Mentre il descrittorecom.claudiodesio.rubrica.test
si può semplificare in questo modo:
module com.claudiodesio.rubrica.test { requires com.claudiodesio.rubrica.spi; requires com.claudiodesio.rubrica.factory; requires com.claudiodesio.rubrica.dati; requires com.claudiodesio.rubrica.util; }
Il comando per compilare tutto sarà:
javac -d mods --module-source-path src src/com.claudiodesio.rubrica.dati/module-info.java src/com.claudiodesio.rubrica.dati/com/claudiodesio/rubrica/dati/*.java src/com.claudiodesio.rubrica.eccezioni/module-info.java src/com.claudiodesio.rubrica.eccezioni/com/claudiodesio/rubrica/ eccezioni/*.java src/com.claudiodesio.rubrica.util/module-info.java src/com.claudiodesio.rubrica.util/com/claudiodesio/rubrica/util/*.java src/com.claudiodesio.rubrica.spi/module-info.java src/com.claudiodesio.rubrica.spi/com/claudiodesio/rubrica/spi/*.java src/com.claudiodesio.rubrica.io/module-info.java src/com.claudiodesio.rubrica.io/com/claudiodesio/rubrica/io/*.java src/com.claudiodesio.rubrica.nio/module-info.java src/com.claudiodesio.rubrica.nio/com/claudiodesio/rubrica/nio/*.java src/com.claudiodesio.rubrica.test/module-info.java src/com.claudiodesio.rubrica.test/com/claudiodesio/rubrica/test/*.java src/com.claudiodesio.rubrica.factory/com/claudiodesio/rubrica/ factory/*.java src/com.claudiodesio.rubrica.factory/module-info.java
Il comando di esecuzione non cambia rispetto a quell'usato nell'esercizio precedente, così come l'output. -
Esercizio 16.v)
Impacchettare i moduli creati nei rispettivi JAR modulari ed eseguire l'applicazione.
Soluzione
Intanto abbiamo rinominato la classe
Esercizio16U
inEsercizio16V
e ricompilato i moduli. Abbiamo poi creato gli otto file modulari con questo comando:
jar --create --file=lib/com.claudiodesio.rubrica.dati.jar --module-version=1.0 -C mods/com.claudiodesio.rubrica.dati . jar --create --file=lib/com.claudiodesio.rubrica.eccezioni.jar --module-version=1.0 -C mods/com.claudiodesio.rubrica.eccezioni . jar --create --file=lib/com.claudiodesio.rubrica.util.jar --module-version=1.0 -C mods/com.claudiodesio.rubrica.util . jar --create --file=lib/com.claudiodesio.rubrica.spi.jar --module-version=1.0 -C mods/com.claudiodesio.rubrica.spi . jar --create --file=lib/com.claudiodesio.rubrica.io.jar --module-version=1.0 -C mods/com.claudiodesio.rubrica.io . jar --create --file=lib/com.claudiodesio.rubrica.nio.jar --module-version=1.0 -C mods/com.claudiodesio.rubrica.nio . jar --create --file=lib/com.claudiodesio.rubrica.factory.jar --module-version=1.0 -C mods/com.claudiodesio.rubrica.factory . jar --create --file=lib/com.claudiodesio.rubrica.test.jar --module-version=1.0 --main-class=com.claudiodesio.rubrica.test.Esercizio16V -C mods/com.claudiodesio.rubrica.test .
Poi abbiamo eseguito il modulo con il comando:
java -p lib -m com.claudiodesio.rubrica.test GestoreFile
o alternativamente con:
java -p lib -m com.claudiodesio.rubrica.test GestoreFileNIO2
-
Esercizio 16.z)
Con
jlink
creare un ambiente personalizzato con il solo modulojava.base
. Poi utilizzando la cartellalib
dell'esercizio precedente eseguire il nostro JAR modulare eseguibile tramite il runtime appena creato.
Soluzione
Con il seguente comando creiamo il runtime richiesto:
jlink --module-path "C:/Program Files/Java/jdk-9.0.1/jmods" --add-modules java.base --output javabasert
Mentre con il seguente comando eseguiamo il nostro JAR modularecom.claudiodesio.rubrica.test
con il runtime creato:
javabasert\bin\java -p lib -m com.claudiodesio.rubrica.test GestoreFile
Come sempre basta sostituireGestoreFile
conGestoreFileNIO2
per ottenere la serializzazione nel modo alternativo:
javabasert\bin\java -p lib -m com.claudiodesio.rubrica.test GestoreFileNIO2