Verificare se una classe esiste in Java

1. Panoramica

Il controllo dell'esistenza di una classe potrebbe essere utile per determinare quale implementazione di un'interfaccia utilizzare. Questa tecnica è comunemente utilizzata durante le configurazioni JDBC meno recenti.

In questo tutorial, esploreremo le sfumature dell'utilizzo di Class.forName () per verificare l'esistenza di una classe nel classpath Java .

2. Utilizzo di Class.forName ()

Possiamo verificare l'esistenza di una classe utilizzando Java Reflection, in particolare Class.forName () . La documentazione mostra che verrà generata un'eccezione ClassNotFoundException se la classe non può essere individuata.

2.1. Quando aspettarsi ClassNotFoundException

Innanzitutto, scriviamo un test che genererà sicuramente un'eccezione ClassNotFoundException in modo che possiamo sapere che i nostri test positivi sono sicuri:

@Test(expected = ClassNotFoundException.class) public void givenNonExistingClass_whenUsingForName_thenClassNotFound() throws ClassNotFoundException { Class.forName("class.that.does.not.exist"); }

Quindi, abbiamo dimostrato che una classe che non esiste genererà un'eccezione ClassNotFoundException . Scriviamo un test per una classe che effettivamente esiste:

@Test public void givenExistingClass_whenUsingForName_thenNoException() throws ClassNotFoundException { Class.forName("java.lang.String"); }

Questi test dimostrano che l' esecuzione di Class.forName () e il mancato rilevamento di un'eccezione ClassNotFoundException è equivalente alla classe specificata esistente sul classpath . Tuttavia, questa non è una soluzione perfetta a causa degli effetti collaterali.

2.2. Effetto collaterale: inizializzazione della classe

È essenziale sottolineare che, senza specificare un caricatore di classi, Class.forName () deve eseguire l'inizializzatore statico sulla classe richiesta . Questo può portare a comportamenti imprevisti.

Per esemplificare questo comportamento, creiamo una classe che genera una RuntimeException quando viene eseguito il suo blocco di inizializzazione statico in modo che possiamo sapere immediatamente quando viene eseguito:

public static class InitializingClass { static { if (true) { //enable throwing of an exception in a static initialization block throw new RuntimeException(); } } }

Possiamo vedere dalla documentazione di forName () che lancia un ExceptionInInitializerError se l'inizializzazione provocata da questo metodo fallisce.

Scriviamo un test che si aspetta un ExceptionInInitializerError quando si cerca di trovare la nostra InitializingClass senza specificare un caricatore di classi:

@Test(expected = ExceptionInInitializerError.class) public void givenInitializingClass_whenUsingForName_thenInitializationError() throws ClassNotFoundException { Class.forName("path.to.InitializingClass"); }

Poiché l'esecuzione del blocco di inizializzazione statica di una classe è un effetto collaterale invisibile, ora possiamo vedere come potrebbe causare problemi di prestazioni o persino errori. Diamo un'occhiata a come saltare l'inizializzazione della classe.

3. Dire a Class.forName () di saltare l'inizializzazione

Fortunatamente per noi, esiste un metodo sovraccarico di forName (), che accetta un programma di caricamento classi e indica se deve essere eseguita l'inizializzazione della classe.

Secondo la documentazione, le seguenti chiamate sono equivalenti:

Class.forName("Foo") Class.forName("Foo", true, this.getClass().getClassLoader())

Cambiando true in false , ora possiamo scrivere un test che verifica l'esistenza della nostra InitializingClass senza attivare il suo blocco di inizializzazione statico :

@Test public void givenInitializingClass_whenUsingForNameWithoutInitialization_thenNoException() throws ClassNotFoundException { Class.forName("path.to.InitializingClass", false, getClass().getClassLoader()); }

4. Moduli Java 9

Per i progetti Java 9+, c'è un terzo overload di Class.forName () , che accetta un Module e un nome di classe String . Questo overload non esegue l'inizializzatore di classe per impostazione predefinita. Inoltre, in particolare, restituisce null quando la classe richiesta non esiste anziché generare un'eccezione ClassNotFoundException .

5. conclusione

In questo breve tutorial, abbiamo esposto l'effetto collaterale dell'inizializzazione della classe quando si utilizza Class.forName () e abbiamo scoperto che è possibile utilizzare gli overload di forName () per evitare che ciò accada.

Il codice sorgente con tutti gli esempi in questo tutorial può essere trovato su GitHub.