Packages e import

I package

Un package è un insieme di entità di Java (classi, interface, enum, exceptions e errors) che si pensa siano correlate. I package sono usati per:

  1. Risolvere problemi di conflitti di classi mettendo il nome del package come prefisso al nome della classe. Per esempio com.zzz.Circle e com.yyy.Circle sono due classi distinte. Sebbene condividano lo stesso nome di classe Circle ma appartengono a due packega differenti: com.zzz e com.yyy. Queste due classi possono essere usate nello stesso programma e distinte utilizzando il nome completo (fully-qualified class name), il nome del pacchetto più il nome della classe.

  2. Controllo visibilità: oltre a public e private per i modificatori di accesso Java ha protected e default che sono collegati alla funzionalità di package. Una entity protected (attributo e metodo di un oggetto) di una classe è accedibile da classi nello stesso package e da sottoclassi della medesima classe. Una entità senza nessun specificatore d'accesso (default) è accedibile da solo le classi nello stesso package.

  3. Per distribuire una collezione di classi riutilizzabili in un formato conosciuto come Java Archive (JAR).

Package name & struttura delle directory

Il nome del package rispecchia la struttura delle directory utilizzate per salvare la classe. Per esempio il codice sorgente della classe Circle del package com.zzz è salvato in "$BASE_DIR/com/zzz/Circle.java", dove $BASE_DIR denota la directory base del pacchetto. Chiaramente, al "punto" nel nome del package corrisponde una sub directory nel file system.

La directory base ($BASE_DIR) può essere stabilita ovunque nel vostro file system. Il compilatore Java e il runtime deve essere informato della posizione della $BASE_DIR così che può trovare le classi. Questo avviene tramite la variabile d'ambiente chiamata CLASSPATH. (CLASSPATH è simile ad un altra variabile d'ambiente PATH, che è usata dalla shell dei comandi per cercare gli eseguibili).

Creazione dei package

Per far si che una classe appartenga a un package, bisogna mettere lo statement del package come prima riga del codice sorgente. Esempio della classe com.zzz.Circle:

com/zzz/Circle.java
package com.zzz;

public class Circle {

}

Notate lo statement package all'inizio del codice sorgente della classe.

Come già detto un' altra classe Circle potrebbe essere definita ma dovrebbe stare in un altro package, ad esempio, com.yyy:

com/yyy/Circle.java
package com.yyy;

public class Circle {

}

Come si può notare, la classe è stata definita in un altro package.

Se volessimo utilizzare una delle due classi all'interno di un altra classe, ad esempio di nome TestCircle, dobbiamo:

  • Se la classe è nello stesso package di una delle due Circle, se non specificato diversamente (tramite istruzione import), utilizza la classe del suo stesso package - perché di default gli è visibile; per utilizzare la classe nell'altro package devo fare l'import della classe nell'altro package in modo da specificare il nome completo e renderla visibile (in questo modo non c'è ambiguità);

  • Se la classe TestCircle è in altro package rispetto alla classe Circle che si vuole utilizzare, è obbligatorio eseguire l'import per renderla visibile;

  • Se la classe TestCircle vuole utilizzare contemporaneamente le due versioni di Circle, quella in package com.zzz e com.yyy, deve specificare ogni volta il nome completo, per togliere l'ambiguità su quella a cui ci si riferisce.

Alcuni casi, esemplificativi, qui sotto, con due classi Circle in due package diversi:

com/yyy/Circle.java
package com.yyy;

public class Circle {
	
	public Circle() {
		System.out.println(this.getClass());
	}
}
com/zzz/Circle.java
package com.zzz;

public class Circle {
	
	public Circle() {
		System.out.println(this.getClass());
	}
}

Eseguendo il programma com.yyy.TestCircle, utilizzo le due versioni di Circle:

com/yyy/TestCircle.java
package com.yyy;

public class TestCircle {

	public static void main(String[] args) {
		
		// com.yyy.Circle perchè nello stesso package
		new Circle();
        
		// utilizzo anche versione altro package
		new com.zzz.Circle();
	}

}

ottengo come output:

class com.yyy.Circle
class com.zzz.Circle

Eseguendo il programma com.zzz.TestCircle, per utilizzare la versione di Circle di un altro package, devo importarla esplicitamente::

com/zzz/TestCircle.java
package com.zzz;

import com.yyy.Circle;

public class TestCircle {

	public static void main(String[] args) {
		
		new Circle();

	}

}

ottengo a console;

class com.yyy.Circle

Mentre, eseguendo il programma com.ppp.TestCircle:

com/ppp/TestCircle.java
package com.ppp;

public class TestCircle {

	public static void main(String[] args) {
		
		new com.yyy.Circle();
		
		new com.zzz.Circle();

	}

}

ottengo l'output:

class com.yyy.Circle
class com.zzz.Circle

Mentre non si sarebbe potuto eseguire due import perché avrebbe generato ambiguità:

package com.ppp;

import com.yyy.Circle;
import com.zzz.Circle; // error collision

public class TestCircle2 {

	public static void main(String[] args) {
		
		// che Circle è? com.yyy o com.zzz
		new Circle();
		new Circle();

	}

}

Esercizi sui package

http://www3.ntu.edu.sg/home/ehchua/programming/java/J9c_PackageClasspath.html

I package della Standard library

I package principali del JDK (detta anche la standard library, libreria standard che si ha a disposizione installato il Java Development Kit ) sono:

Per utilizzare queste classi è necessario fare l'import dando tutto il nome compreso il package.

Come per qualsiasi altra classe di cui avessi bisogno e non è nello stesso package della classe utilizzatrice, ho bisogno di scrivere nel codice import <Class-name> dove classe name comprende il package ad esempio, import java.io.FileInputStream, qualora ti servisse la classe FileInputStream per leggere il contenuto dei file.

Esempio programma, che legge il contenuto di un file di testo e lo stampa a video:

package prova;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class ProgramReader {

	public static void main(String[] args) {
		
		try {
			BufferedReader in
			   = new BufferedReader(new FileReader("./test/divina_commedia.txt"));
			
			String line = null;
			while((line = in.readLine()) != null) {
				System.out.println(line);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

}

In questo caso ho importato le classi java.io.BufferedReader, java.io.FileReader e java.io.IOException, per le operazioni di input. Le classi String e System non è necessario importarle in quanto sono del package java.lang e sono sempre importate.

Se volessi importare tutte la classi del package java.io potrei anche scrivere java.io.* . Quindi il programma precedente può anche essere scritto nel seguente modo:

package prova;

import java.io.*;

public class ProgramReader {

	public static void main(String[] args) {
		
		try {
			BufferedReader in
			   = new BufferedReader(new FileReader("./test/divina_commedia.txt"));
			
			String line = null;
			while((line = in.readLine()) != null) {
				System.out.println(line);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

}

Nota bene:

  1. non c'è distinzione tra classi standard (della standard library) e classi scritte da noi o prese utilizzando librerie di terze parti: se ho bisogno di una classe che sia richiamata dalle mia classe e la classe di cui ho bisogno si trova in un altro package, allora per accedere alla classe devo importarla (import <Class-name>), comprensiva del nome del package;

  2. per le classi sotto java.lang.* non è necessario eseguire l'import in quanto sono sempre importate;

Vedete ad esempio le classi della libreria standard https://docs.oracle.com/javase/7/docs/api/ come sono organizzate in package. Selezionando il package nella documentazione accederete alle diverse classi del package. Vedete anche che spiega come è organizzata la documentazione della libraria standard Documentation & JavaDoc.

Classi di terze parti

Finora abbiamo parlato di utilizzo di classi scritte da noi e l'utilizzo di classi della standard library, ad esempio, java.util.Vector, cosa dobbiamo fare per utilizzare una libraria scritta da altri, ad esempio, che è possibile scaricare da internet e ci fornisce una funzionalità di cui abbiamo bisogno ( e non abbiamo il tempo o la voglia o richiede competenze così specifiche che non abbiamo)?

Concetto di CLASSPATH

Import e Static import

Vediamo ora un'estensione della direttiva static che può essere comoda. Abbiamo visto che import rende possibile riferire una classe come java.util.Scanner utilizzando il nome semplice Scanner. Ma dobbiamo ancora utilizzare i nomi composti alle variabili statiche come System.out o ai metodi statici come Math.sqrt.

C'è un'altra forma di import che può essere utilizzata per importare membri statici di una classe nello stesso modo di come che la direttiva import è utilizzata per importare classi di un package. La direttiva è chiamata static import e ha la sintassi:

import static <package-name>.<class-name>.<static-member-name>;

per importare un membro statico da una classe, o

import static <package-name>.<class-name>.*;

per importare tutti i membri statici di una classe. Per esempio, se mettiamo

import static java.lang.System.out;

allora possiamo usare nel codice, il semplice nome out, invece del nome composito, System.out. Vuol dire che possiamo scrivere out.println invece di System.out.println. Se nel codice dobbiamo utilizzare molti metodi della classe Math, si può usare la sintassi:

import static java.lang.Math.*;

Questo permette di utilizzare direttamente sqrt invece di Math.sqrt, log invece di Math.log e PI invece di Math.PI.

Last updated