Tips&Tricks I trucchi del mestiere




Usare cut & paste in Java

Le applicazioni Java possono utilizzare la clipboard creando un'istanza dell'oggetto Clipboard e richiedendone o settandone il contenuto:

Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
String str = textarea.getText();
StringSelection contents = new StringSelection(str);
cb.setContents(contents, null);

Dall'altra parte si può invece recuperare il contenuto della clipboard con:

Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable content = cb.getContents(this);
try {
   String str = (String)content.getTransferData(DataFlavor.stringFlavor);
   textarea.setText(str);
} catch (Throwable e) 
{ System.err.println(e); }

Nella documentazione API di Java potete trovare tutte le informazioni necessarie a recuperare anche altri tipi di dati dalla clipboard per aggiungere ai vostri programmi tutta la comodità e la potenza della funzionalità di cut & paste.

Concatenazione di stringhe e performance

Se avete del codice in cui la concatenazione di stringhe Φ un'operazione molto frequente, tenete a mente che l'utilizzo dell'operazione + sulle stringhe:

String string1 = "Ciao ";
String string2 = "Federico";
string1 = string1 + string2;

è molto meno performante del metodo append dello string buffer

StringBuffer buffer1 = "Ciao ";
String string2 = "Federico";
buffer1.append(string2);

Questo Φ dovuto al fatto che le stringhe in Java sono immutabili e quindi l'operatore + (unico operatore overloaded in Java) per unire due stringe deve compiere le seguenti operazioni:

Se sostituite, quindi, lo StringBuffer alla String nei punti dove la concatenazione avviene spesso, potreste rendere molto pi∙ efficienti i vostri algoritmià

Pass-by-Value e Pass-By-Reference

Considerate il seguente codice di esempio di una classe qualunque:

void passByValue(int i) { f = 15; }
void passByReference(StringBuffer sb) { sb.append(" vedo?"); }
void provaMetodi() 
{
 	int i = 1;
	StringBuffer sb = new StringBuffer("Cosa");
	passByValue(i);
	passByReference(sb);
	System.out.println("Value of i: " + i);
	System.out.println("Value of sb: " + sb);
}

Il risultato stampato sarα

Value of i: 1
Value of sb: Cosa vedo?

In altre parole il valore intero originale non ha subito le modifiche del metodo passByValue, mentre l'oggetto StringBuffer si porta dietro, dal metodo passByReference, gli effetti della manipolazione subita. Il fatto Φ che tutti i tipi primitivi in java sono passati per valore, cioΦ ne viene fatta una copia prima del passaggio ad un metodo chiamato. Gli oggetti e gli array invece sono passati per riferimento, ovverosia ai metodi invocati viene offerto un "puntatore" - per dirla alla C++ - alla locazione di memoria dove si trova l'oggetto originale, e quindi se l'oggetto viene manipolato nel metodo viene modificata l'unica copia dell'oggetto disponibile. Tenete sempre a mente gli effetti collaterali del passaggio di riferimento quando scrivete dei metodi che ricevono degli oggetti o degli array come parametri, in modo da non modificarli inavvertitamente e trovare delle sorprese inaspettate al ritorno dalla chiamata del metodo.

La corto-circuitazione degli operatori booleani

Considerate il seguente codice:

if ( object != null && object.equalsTo(otherObject) ) 
{
      // fai qualcosa con object 
}

Se gli operatori booleani non fossero ottimizzati, il problema sarebbe che quando object non Φ inizializzato, la seconda parte della condizione genera una fastidiosa NullPointerException e il programma si ferma. Invece sia && che || sono corto-circuitati, nel senso che se il primo dei due operandi di questi operatori restituisce un valore per cui il valore del secondo operando non va ad influire sul risultato dell'espressione, il secondo operando non viene valutato, risparmiando cos∞ tempo di esecuzione e sulla nidificazione degli if. In breve, quando object != null Φ false, il valore di object.equalsTo(otherObject) non pu≥ cambiare il fatto che l'intero if sarα falso, e quindi non viene valutato, evitando cos∞ la dolorosa eccezione che dicevamo. La stessa cosa vale per ||, ma in questo caso l'ottimizzazione scatta per valori true. Questo Φ una conseguenza delle tabelle di veritα di AND e di OR, che fanno riferimento alla base dell'aritmetica booleana.

Thread ed eccezioni

Quando in un thread vengono sollevate delle eccezioni che non sono gestite, la macchina virtuale termina il thread in questione ed invoca il metodo uncaughtException del ThreadGroup cui il thread appartiene. ╚ possibile derivare una propria implementazione di ThreadGroup e riscrivere il metodo uncaughtException per avere pieno controllo sulla chiusura problematica dei thread che la vostra applicazione utilizza e per poter gestire, in un solo punto, tutte le eccezioni che non volete catturare singolarmente in ogni metodo dei vostri thread. La signature del metodo da riscrivere Φ:

public void uncaughtException(Thread t, Throwable e) 

mentre per la creazione di un thread associato ad un gruppo che avete derivato voi, usate uno dei seguenti costruttori

Thread(ThreadGroup tg, Runnable target, String name)
Thread(ThreadGroup tg, Runnable target)
Thread(ThreadGroup tg, String name)

Internazionalizzare le applicazioni

Quando nelle applicazioni si devono visualizzare o stampare numeri e date o confrontare stringhe, spesso si deve far caso alle differenze che queste operazioni comportano rispetto a culture diverse. In Java Φ possibile utilizzare delle classi apposite che permettono di gestire in maniera semplice questa varietα. Nel package java.text esistono delle classi per lo pi∙ astratte per la formattazione di numeri e date, che utilizzano dei metodi getInstance per restituire degli oggetti di formattazione dei valori:

NumberFormat nf2 = NumberFormat.getInstance(Locale.ITALIAN);
System.out.println(nf2.format(1234.56));

visualizzerα il seguente output

1.234,56

secondo la nostra convenzione di utilizzare la virgola come punto decimale ed il punto come separatore delle migliaia. Siccome le funzionalitα offerte sono molte e riguardano la rappresentazione di svariati formati numerici (percentuali, valuta, decimali, interi, etc.), di date, ed il confronto nazionalizzato di stringhe. Si consiglia di consultare l'API di Java per approfondire l'argomento se pensate che vi possa tornare utile.

Classi interne e classi anonime

Nel seguente codice troviamo un esempio sintattico dell'utilizzo di classi interne e di classi anonime. Le prime sono classi che vengono utilizzate solo all'interno di altre classi, in quanto sono definite proprio internamente al blocco stesso di definizione della classe che le include. La loro utilitα Φ quella di non essere visibili se non nell'oggetto che le definisce e quindi sono comode in quelle situazioni in cui servono degli oggetti solo per l'implementazione di una classe. Le definizioni anonime invece vengono impiegate nella ridefinizione al volo di qualche tipo di dati, ridefinizione che per≥ non ha altri impieghi se non nel contesto in cui Φ dichiarata.

class UnaClasseNormale 
{
  void unMetodo() {}
  void unAltroMetodo() {}
}  
class UnAltraClasseNormale 
{
  // Queste due classi saranno visibili solo in UnAltraClasseNormale
  static class UnaClasseInterna {} // classe interna statica
  class AltraClasseInterna {} // classe interna
  void unMioMetodo() 
 {
     class ClasseInternaLocale {} // classe interna locale al metodo  
  }
  void mioAltroMetodo() 
 {
    // classe anonima: ridefinisce un metodo della classe originale
    UnaClasseNormale bref = new UnaClasseNormale () 
   { 
       void unMetodo() {} // Qui ridefinisco il metodo originale 
    }; 
  } 
}

Come dichiarare array anonimi

Quando in un metodo Φ richiesto un array come parametro o comunque in qualunque circostanza vi sia necessitα di un array, ricordate che esiste una sintassi abbreviata che vi consente di creare array all'istante (un po' come per le classi anonime) e senza associarli ad una variabile. Questo pu≥ evitarvi la creazione di variabili inutili e semplifica il vostro codice. Ecco un esempio:

myMethod(new int[] {10, 23, 45, 9, 12, 59});
// dichiarazione di myMethod: void myMethod(int[] values)

Ovviamente questo ha senso solo se l'array del caso poi non vi serve pi∙, perchΘ non avete nessun riferimento all'oggetto anonimo creato come mostrato qui sopra.

Gestire le versioni dei package

Pu≥ essere utile creare dei package java in diverse versioni, in modo da poter scrivere del codice che faccia riferimento alle caratteristiche di una o dell'altra release delle classi che fanno parte del package stesso. Esiste una specifica java (Java Product Versioning Specification) che consente di aggiungere informazioni relative al livello di sviluppo di un package attraverso il manifest file del JAR in cui le varie classi vengono raccolte. Ecco un esempio di manifest.mf con informazioni di versioning:

Manifest-Version: 1.0
Name: it/fedmest/myclasses
Specification-Title: Java Package con Versioni 
Specification-Vendor: Federico Mestrone
Specification-Version: 1.0
Implementation-Title: it.fedmest.myclasses
Implementation-Vendor: FedericoMestrone.Com
Implementation-Version: Build 1.0.3-b32

Queste informazioni possono poi essere utilizzate nel vostro codice con i metodi della classe java.lang.Package:

Package pkg = Package.getPackage("it.fedmest.myclasses");
System.out.println("Package name:\t" + pkg.getName());
System.out.println("Spec title:\t" + pkg.getSpecificationTitle());
System.out.println("Spec vendor:\t" + pkg.getSpecificationVendor());
System.out.println("Spec version:\t" + pkg.getSpecificationVersion());
System.out.println("Impl title:\t" + pkg.getImplementationTitle());
System.out.println("Impl vendor:\t" + pkg.getImplementationVendor());
System.out.println("Impl version:\t" + pkg.getImplementationVersion());

Realizzare un file Zip in Java

Java viene fornito completo di tutte le classi necessarie a creare dei file ZIP compressi. Con il codice seguente viene creato un file zippato che include un file .doc compresso. ╚ interessante notare che Φ anche possibile zippare dei dati in memoria, senza necessariamente passare per un file, utilizzando uno stream di input diverso da FileInputStream.

BufferedInputStream origin = null;
FileOutputStream dest = new FileOutputStream("D:\\filezippato.zip");
ZipOutputStream out = new ZipOutputStream(new 
BufferedOutputStream(dest));
out.setMethod(ZipOutputStream.DEFLATED); // Attiva la compressione
byte data[] = new byte[2048];
FileInputStream fi = new FileInputStream("D:\\filenormale.doc");
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry("D:\\filenormale.doc");
out.putNextEntry(entry);
int count;
while((count = origin.read(data, 0, 2048)) != -1) 
{ out.write(data, 0, count); 
}
origin.close();
out.close();

ZipAnywhere: WinZip secondo Java

Esiste un'applicazione grafica Java - gratuita per uso non commerciale - con la quale potete compiere tutte le operazioni classiche di WinZip, oltre a crearvi dei JAR auto-estraibili, molto comodi per distribuire le vostre applicazioni. Il programma si chiama ZipAnywhere ed Φ necessario solo un ambiente run-time Java per poterlo utilizzare. Lo potete scaricare (si tratta di soli 87Kb) all'indirizzo http://www.geocities.com/zipanywhere - si tratta di un file jar eseguibile quindi basta un doppio click per avviare la finestra principale.

Memoria a disposizione

Se volete conoscere il valore della memoria a disposizione della vostra applicazione, basta utilizzare il codice che segue:

Runtime rt = Runtime.getRuntime();
System.out.println("Memoria totale massima a disposizione della VM: " + 
  rt.totalMemory());
System.out.println("Memoria ancora non utilizzata di quella a 
disposizione: " +   rt.freeMemory());

Il metodo freeMemory() dell'oggetto Runtime restituisce il valore in bite della memoria di sistema ancora disponibile per la macchina virtuale, mentre con totalMemory potete conoscere il numero di byte che il sistema operativo ha allocato per la JVM.