Linee guida per l’ottimizzazione del codice Java

In questo articolo vedremo alcuni suggerimenti per ottenere del codice Java performante, anche se alcune di queste best-practice potrebbero avere come drawback quello di inficiare la leggibilità del codice. Nei prossimi paragrafi ne elenchiamo alcune.

Tutorial Apache POI: scriviamo e leggiamo un documento Word
I modificatori di accesso
Tutorial GWT: creiamo un servizio GWT RPC

In questo articolo vedremo alcuni suggerimenti per ottenere del codice Java performante, anche se alcune di queste best-practice potrebbero avere come drawback quello di inficiare la leggibilità del codice. Nei prossimi paragrafi ne elenchiamo alcune.

String Concatenation

Per la concatenazione di più stringhe è da preferire la classe StringBuilder (o la versione thread-safe StringBuffer), piuttosto che usare l’operatore += con le singole stringhe. Le stringhe sono immutabili in Java, il che significa che una volta creato un oggetto String, non è più possibile modificarlo. L’uso di += sulle stringhe in un ciclo comporterà la creazione di numerose istanze String separate, che potrebbero creare problemi di prestazioni. StringBuilder può concatenare le stringhe senza dover creare nuove istanze, il che può far risparmiare tempo ed aumentare le performance, a seconda dello scenario.

D’altro canto, risulta consigliabile di utilizzare il solo operatore +, ad esempio per motivi di leggibilità del codice (quando la concatenazione avviene in un unico statement), e di non utilizzare la classe StringBuilder, in quando il compilatore Java provvederà ad ottimizzare il bytecode prevedendo un’unica Stringa finale.

Utilizzare i tipi primitivi quando possibile

Un altro modo semplice e veloce per evitare sovraccarichi e migliorare le prestazioni dell’applicazione è utilizzare tipi primitivi invece delle loro classi wrapper. Quindi, è meglio usare un int invece di un Integer, o un double invece di un Double. Ciò consente alla JVM di memorizzare il valore nello stack anziché nell’heap per ridurre il consumo di memoria e in generale gestirlo in modo più efficiente.

That allows your JVM to store the value in the stack instead of the heap to reduce memory consumption and overall handle it more efficiently.
Stack vs Heap

Controllare prima il livello corrente del log

 Prima di creare un messaggio di debug (o di un altro livello), è necessario innanzitutto controllare il livello del log corrente. Altrimenti, potrebbe essere creata una stringa con il messaggio di log che verrà ignorata in seguito.

Ecco 2 esempi di come NON dovresti farlo.

// don’t do this

log.debug(“User [” + userName + “] called method X with [” + i + “]”);

// or this

log.debug(String.format(“User [%s] called method X with [%d]”, userName, i));

In entrambi i casi, verranno eseguiti tutti i passaggi richiesti per creare il messaggio di log senza sapere se il framework di log utilizzerà e stamperà il messaggio. È meglio controllare il livello di log corrente prima di creare il messaggio di debug.

// do this

if (log.isDebugEnabled()) {
 log.debug(“User [” + userName + “] called method X with [” + i + “]”); 
}

Utilizzare Apache Commons StringUtils.replace anziché String.replace

In generale, il metodo String.replace funziona bene ed è piuttosto efficiente, specialmente se si utilizza Java 9. Tuttavia, se l’applicazione richiede molte operazioni di sostituzione e non si è aggiornato alla versione di Java più recente, è comunque utile verificare alternative più veloci e più efficienti.

Un candidato è il metodo StringUtils.replace di Apache Commons Lang. Come Lukas Eder ha descritto in uno dei suoi ultimi post sul blog [1], questo metodo supera di molto in performance il metodo String.replace di Java 8 e richiede solo un minimo cambiamento. È necessario aggiungere una dipendenza Maven per il progetto Apache Commons Lang alla propria applicazione nel pom.xml e sostituire tutte le chiamate del metodo String.replace con il metodo StringUtils.replace.

// sostituisci questo

test.replace(“test”, “simple test”);

// con questo

StringUtils.replace(test, “test”, “simple test”);

Lo stesso discorso vale per le regular expression.

Loop

Utilizzare la streamAPI messa a disposizione da Java 8 (i loop sono parallelizzabili invocando il metodo parallelStream).

//sostituire questo

for (int i: myList) {
 if (i % 2 == 0) 
 result += i; 
}

//con questo

myList.parallelStream().filter(value -> value % 2 == 0).mapToInt(Integer::intValue).sum()

CollectionsCapacityInitialization

La capacità iniziale predefinita di un ArrayList è piuttosto piccola (10 da Java 1.4 - 1.8). Ma poiché l'implementazione sottostante è un vettore, l'array deve essere ridimensionato se si aggiungono molti elementi. Per evitare l'alto costo del ridimensionamento, quando già si prevede di aggiungere molti elementi, è preferibile inizializzare l’ArrayList con una capacità iniziale più alta.

Di seguito la capacità iniziale delle Collections Java più usate:

  1. Vector, ArrayList = 10
  2. LinkedList, TreeSet = non ha una capacità iniziale
  3. HashMapLinkedHashMap, ConcurrentHashMap, HashSet, LinkedHashSet = 16 (ma con un default load factor di 0.75, solo i primi 12 elementi possono essere inseriti prima che avvenga un resizing)

[1] https://blog.jooq.org/2017/10/11/benchmarking-jdk-string-replace-vs-apache-commons-stringutils-replace/ come suite di performance test è utilizzato JMH.

COMMENTS

WORDPRESS: 0