Lo slogan, coniato originariamente da Sun Microsystems per illustrare i benefici del linguaggio Java, vale a patto che:
Inserire dei path assoluti nel proprio sorgente è sempre fonte di problemi quando si scrive software multipiattaforma:
C:\Users\UserName\file
— Non funzionerà su piattaforma *nix, e non funzionerà se l’utente Windows non è “UserName”.C:\MyProgram\file
— Non funzionerà su piattaforma *nix, e non funzionerà se
l’installazione di Windows è sana e il software non è avviato con diritti di amministratore./home/username/file
— Non funzionerà su piattaforma Windows, e non funzionerà se l’utente non è username
.Java fornisce nella classe System
un metodo
String getProperty(String p)
che consente di accedere a proprietà di sistema
file.separator
— Restituisce \
per Windows e /
per Unixjava.home
— La directory di installazione di Javauser.dir
— La directory da cui il comando java
è stato invocatouser.home
— Restituisce la home directory dell’utente che ha lanciato java
user.name
— Restituisce il nome utentepublic static final String PROP_FILE_SEPARATOR = "file.separator";
String separator = System.getProperty(PROP_FILE_SEPARATOR);
java.version
— La versione di java
in uso. Si potrebbe decidere di non usare una funzionalità che si sa non esistere o essere buggata.os.arch
— L’architettura della CPU come rilevata dall’OS (x86, i386, amd64, x86_64, IA64N, ARM, …)os.name
— Il nome del sistema operativo (Linux, MacOS X, MacOS, Windows 10, Solaris, FreeBSD, …)os.version
— Restituisce per Windows il numero di versione effettivo (per esempio, Windows 10 restituisce 10.0), per MacOS il numero di versione (per esempio, 10.3.4), per Linux la versione
del kernel (es. 6.1)Diversamente dagli anni 90, i dispositivi oggi hanno una densità di pixel per area estremamente variabile. Si va da 120 PPI (Pixel Per Inch) a 640 PPI, su schermi di dimensione estremamente variabile (da 3 a 200 pollici).
Piattaforme diverse, anche a parità di schermo, possono adottare diverse convenzioni:
Questi elementi sono stabiliti dal window manager (del windowing system del sistema utilizzato) e non dallo sviluppatore dell’applicazione. Come indicazione generale vale che un’applicazione ben sviluppata eredita il “look and feel” dal sistema su cui sta girando.
Sarebbe opportuno definire la UI una sola volta e cambiare dinamicamente le parti scritte (il testo) a seconda dell’impostazione della lingua di sistema (o della nostra applicazione).
In realtà anche per il formato dei numeri, la valuta, le convenzioni sulla data, …
en_US_UNIX
(lingua inglese, Stati Uniti, piattaforma UNIX)Java fornisce una architettura per l’internazionalizzazione (i18n = internationalization), che fa uso di ResourceBundle
e di una serie di file di supporto (properties files).
Per approfondimenti (per implementare il supporto multilingua):
├── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ ├── java
│ └── resources
├── build.gradle.kts
└── settings.gradle.kts
src/[main|test]/resources
contengono le risorse del progetto opportunamente organizzate
Per OOP, alla struttura del progetto Gradle andranno aggiunti almeno altri due file
├── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ ├── java
│ └── resources
├── build.gradle.kts
├── LICENSE
├── README.md
└── settings.gradle.kts
README.md
LICENSE
Le più note piattaforme utilizzano di default encoding diversi:
Solitamente, il codice sorgente si sviluppa utilizzando la codifica UTF-8
File -> Preferences -> Settings
Per l’opzione Text Editor -> Files -> Encoding
selezionare UTF-8
Per l’opzione Test Editor -> Files -> Eol
selezionare LF
In basso a destra nella finestra di VS Code c’è l’indicazione della codifica e di EOL per il file selezionato
Abbiamo visto finora il classpath come l’insieme dei percorsi dove la virtual machine va a cercare le classi da caricare
-cp
di java
e javac
, il classpath può contenere indifferentemente dei path o dei JAR (o anche degli zip)Esso includerà tipicamente anche le risorse del progetto, i JAR delle dipendenze importate, etc.
Come possiamo accedere a queste risorse in modo uniforme?
Java fornisce un’utilità per caricare risorse dal classpath
ClassLoader.getSystemResource
(AsStream
)(String)
public abstract class ClassLoader {
public static ClassLoader getSystemClassLoader();
public static URL getSystemResource(String name);
public static InputStream getSystemResourceAsStream(String name);
public URL getResource(String name);
// ...
ClassLoader
) è un’oggetto responsabile del caricamento di classi e risorse
CLASSPATH
getSystemResource
e getSystemResourceAsStream
è il nome di una risorsa (non un percorso del filesystem!), che è una stringa separata da /
che identifica la risorsa
ClassLoader.getSystemResource()
equivale a ClassLoader.getSystemClassLoader().getResource()
final InputStream in = ClassLoader.getSystemResourceAsStream("/settings/settings");
final BufferedReader br = new BufferedReader(new InputStreamReader(in));
final String line = br.readLine();
in.close();
final URL imgURL = ClassLoader.getSystemResource("/images/gandalf.jpg");
final ImageIcon icon = new ImageIcon(imgURL);
final JLabel lab1 = new JLabel(icon);
Progetto di esempio: https://github.com/unibo-oop/example-with-get-resources
Spesso un software ha necessità di caricare al primo avvio delle impostazioni di default, quindi lasciare l’utente libero di modificarle e, se avviato successivamente caricare quelle scelte dall’utente. In caso di sistema multiutente, le impostazioni saranno diverse per ciascuno.
.nomeprogramma
.getResource()
.nos esse quasi nanos gigantium humeris insidentes Bernardo di Chartres
Tutto il software moderno dipende da altro software!
java.*
e javax.*
)Tutto il software che costruiamo e usiamo dipende da altro sofware
$\Rightarrow$ Le applicazioni hanno un albero di dipendenze!
Proviamo a costruire una semplice applicazione che:
Una possibile soluzione: https://github.com/APICe-at-DISI/sample-gradle-project/blob/master/src/main/java/it/unibo/sampleapp/SimplerRateAMovie.java
È stata sfruttata una libreria per OMDB
Ma a sua volta, questa libreria usa librerie che usano librerie…
+--- com.omertron:API-OMDB:1.5
| +--- commons-codec:commons-codec:1.10
| +--- org.apache.commons:commons-lang3:3.4
| +--- com.fasterxml.jackson.core:jackson-core:2.8.7
| +--- com.fasterxml.jackson.core:jackson-annotations:2.8.7
| +--- com.fasterxml.jackson.core:jackson-databind:2.8.7
| | +--- com.fasterxml.jackson.core:jackson-annotations:2.8.0
| | \--- com.fasterxml.jackson.core:jackson-core:2.8.7
| +--- org.slf4j:slf4j-api:1.7.24
| \--- org.yamj:api-common:2.1
| +--- org.apache.httpcomponents:httpclient:4.5.3
| | +--- org.apache.httpcomponents:httpcore:4.4.6
| | +--- commons-logging:commons-logging:1.2
| | \--- commons-codec:commons-codec:1.9
| \--- org.slf4j:slf4j-api:1.7.24
Le dipendenze indirette (dipendenze di dipendenze) sono dette transitive
In progetti non giocattolo, le dipendenze transitive sono la maggioranza
Gestire il classpath diventa molto difficile! Ogni libreria va:
L’applicazione di prima viene lanciata con:
java -cp "build/classes/java/main:lib/API-OMDB-1.5.jar:lib/jool-0.9.14.jar:lib/logback-classic-1.4.1.jar:lib/api-common-2.1.jar:lib/slf4j-api-2.0.2.jar:lib/httpclient-4.5.3.jar:lib/commons-codec-1.10.jar:lib/commons-lang3-3.4.jar:lib/jackson-databind-2.8.7.jar:lib/jackson-core-2.8.7.jar:lib/jackson-annotations-2.8.7.jar:lib/logback-core-1.4.1.jar:lib/httpcore-4.4.6.jar:lib/commons-logging-1.2.jar" it.unibo.sampleapp.SimplerRateAMovie
Ci servirebbe uno strumento capace di:
Per farlo, però, abbiamo bisogno di conoscere qualche archivio ("repository") di librerie, e di sapere come reperirle, ossia conoscere il loro nome e versione…
Al compilatore Java e alla JVM (a differenza di quello che accade con altri linguaggi) è ignoto il concetto di “libreria”. L’unica astrazione che abbiamo in mano è quella di classpath, ma è troppo grezza!
Quando Java ha preso piede, è stato necessario sopperire a questa mancanza. Un particolare build system, Apache Maven, ha elaborato una propria convenzione per i nomi, divenuta oggi sostanzialmente standard (qualunque build system per Java la adotta).
Una libreria Java in formato compatibile con Maven si compone di:
it.unibo
, com.google
, io.github
commons-math
, guava
, junit-jupiter-assertions-jvm
.
, -
, o +
(solitamente numeri e punti)
1.0
, 1.0.1
, 2.3.5-beta4
, 28ae10dd
, 4.0.2-alpha+28ae10dd
Per riferirsi ad una libreria specifica, si usa la sintassi: groupId:artifactId:version
com.google.guava:guava:32-jre
it.unibo.alchemist:alchemist-api:25.0.1
Ora sappiamo come si chiamano, ma non dove trovarle…
Assieme alla convenzione per i nomi, Maven definì un repository (archivio) dove i creatori di software Java open source potessero:
La disponibilità e la possibilità di riuso ha consentito la nascita dell’“ecosistema” Java, rendendolo uno dei linguaggi/piattaforme di più ampio successo di sempre.
Sappiamo dove trovare le librerie e come riferirle, ma ci serve ancora uno strumento per:
Gradle consente di gestire le dipendenze, specificando:
In Gradle è possibile “puntare” ad archivi di librerie specificandolo in un blocco repositories
Per dire a Gradle di:
è sufficiente configurare build.gradle.kts
come segue:
plugins { java } // Carica il necessario per Java
repositories { mavenCentral() } // Configura Gradle per cercare e scaricare da Maven Central
Siamo pronti per importare le librerie che vogliamo! Dobbiamo solo:
groupId
, artifactId
, e version
com.omertron:API-OMDB:1.5
Gradle consente di (costringe a) dire chiaramente “a cosa serve” una certa libreria. Noi vedremo solo alcuni degli scope disponibili:
implementation
: la libreria ci serve sia per compilare che per eseguire la nostra applicazione
testImplementation
: la libreria ci serve per compilare ed eseguire i test
testRuntimeOnly
: la libreria ci serve per eseguire i test (sarà nel -cp
di java
), ma non per compilarli (non sarà nel -cp
di javac
)Una volta identificata la libreria
com.omertron:API-OMDB:1.5
e scelto lo scope che vogliamo usare
implementation
Possiamo semplicemente configurare Gradle per importarla dentro il blocco dependencies
:
plugins { java } // Carica il necessario per Java
repositories { mavenCentral() } // Configura Gradle per cercare e scaricare da Maven Central
dependencies {
implementation("com.omertron:API-OMDB:1.5")
}
Quando lanceremo il task compileJava
, Gradle si occuperà di:
Google Guava (https://github.com/google/guava)
Apache Commons (https://commons.apache.org)
Static Logger Facade for Java (SLF4J) (http://www.slf4j.org)
println
)Esiste una lista, costantemente manutenuta, che elenca le più comuni, diffuse e stabili librerie per una pletora di usi: https://bit.ly/awesome-java
Alcune librerie sono costruite come Framework, ossia come ossature di applicazioni, pensate per velocizzare la costruzione di un certo tipo di software
Uno degli scopi del progetto di OOP è quello di misurare se siate bravi designer, ma per farlo è necessario che il design della vostra applicazione l’abbiate fatto voi e non chi ha costruito il framework.
Vi raccomandiamo quindi di evitare i framework! O, al più, usarli solo dopo che il progetto è avviato come semplice libreria (non semplice e non sempre possibile)