Java – Il limit dello split per le stringhe

A partire dalla release 1.4 di Java la classe String ha due metodi split che permettono di spezzare una stringa sfruttando il meccanismo delle regular expression (espressioni regolari), introdotte anch’esse in Java 1.4. L’uso di questi split mi ha talvolta causato dubbi/disagi ma non per il concetto stesso di “split” o per l’uso delle espressioni regolari. No, per un altro motivo: il concetto “extra” del limit che è stato volutamente aggiunto nello split. E ogni volta devo andare a rileggere la documentazione javadoc per ricordarmi la logica di questo limit ...

Nella classe String i due metodi split sono i seguenti:

  • public String[] split(String regex)
  • public String[] split(String regex, int limit)

Invocare il primo è sostanzialmente l’equivalente di invocare il secondo con limit=0 . Questi due metodi, tra l’altro, non sono quelli che usano direttamente le espressioni regolari. Tutto il lavoro viene comunque svolto dai metodi split della classe java.util.regex.Pattern che sono i seguenti:

  • public String[] split(CharSequence input)
  • public String[] split(CharSequence input, int limit)

Pertanto tutto quello che descriverò di seguito vale sia che usiate gli split di String, sia che usiate direttamente gli split di Pattern.

Il concetto del limit

Il concetto del limit nei metodi split è stato introdotto per dare la possibilità di specificare in un qualche modo il numero massimo di volte che il pattern deve poter essere applicato alla stringa da spezzare. E questo chiaramente influisce anche sul numero di elementi che si otterranno in uscita nell’array String[] restituito da split.

Il parametro limit può ricevere qualunque valore lecito per un int. Ma si devono distinguere 3 casistiche differenti: a) valore negativo, b) valore zero, c) valore positivo. Sono 3 situazioni differenti che portano a risultati diversi. Ed è appunto questa la parte che generalmente è meno facile da capire e soprattutto da “ricordare”.

Per le prove si può utilizzare il seguente codice che ho scritto appositamente. Nota: richiede almeno Java 5 per via della formattazione con il format che ho sfruttato solamente per pura comodità.

public class ProvaLimitSplit {
    public static void main(String[] args) {
        String str = "--aaa-bbb-ccc-ddd-eee--";
        String regex = "-";

        for (int limit = -10; limit <= 10; limit++) {
            String[] parti = str.split(regex, limit);

            System.out.format("limit = %-3d ---> { ", limit);

            for (int i = 0; i < parti.length; i++) {
                if (i > 0) {
                    System.out.print(", ");
                }

                System.out.print("\"" + parti[i] + "\"");
            }

            System.out.println(" }");
        }
    }
}

L’output generato dal codice è il seguente:

limit = -10 ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }
limit = -9  ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }
limit = -8  ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }
limit = -7  ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }
limit = -6  ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }
limit = -5  ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }
limit = -4  ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }
limit = -3  ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }
limit = -2  ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }
limit = -1  ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }
limit = 0   ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee" }
limit = 1   ---> { "--aaa-bbb-ccc-ddd-eee--" }
limit = 2   ---> { "", "-aaa-bbb-ccc-ddd-eee--" }
limit = 3   ---> { "", "", "aaa-bbb-ccc-ddd-eee--" }
limit = 4   ---> { "", "", "aaa", "bbb-ccc-ddd-eee--" }
limit = 5   ---> { "", "", "aaa", "bbb", "ccc-ddd-eee--" }
limit = 6   ---> { "", "", "aaa", "bbb", "ccc", "ddd-eee--" }
limit = 7   ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee--" }
limit = 8   ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "-" }
limit = 9   ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }
limit = 10  ---> { "", "", "aaa", "bbb", "ccc", "ddd", "eee", "", "" }

Con questi risultati ben visibili possiamo analizzare le 3 casistiche.

Limit negativo

Quando il valore del limit è negativo, quale sia esattamente il valore negativo non importa per split. Potete passare -1, -10, -123, -1000 ecc... il risultato è sempre lo stesso (tipicamente si passa -1).

In questo caso split applica il pattern quante più volte possibile, ovvero in tutti i punti della stringa in cui c’è un match della epressione regolare.

Nell’esempio sopra la stringa contiene 8 volte il pattern "-" e quindi viene spezzata 8 volte ottenendo 9 elementi. È bene notare che c’è un elemento vuoto "" all’inizio della stringa (prima del primo "-") e c’è un elemento vuoto alla fine della stringa (dopo l’ultimo "-"). E c’è un elemento vuoto anche ovviamente tra due "-".

Limit zero

Quando il limit è zero (0) il comportamento di split è praticamente quasi uguale al limit negativo, ovvero applica ancora il pattern quante più volte possibile ma, attenzione, prima di restituire l’array elimina gli elementi vuoti "" finali. Quest’ultima caratteristica è l’unica differenza tra il limit negativo e zero.

Limit positivo

Quando il valore del limit è positivo, la questione cambia perché il valore positivo è rilevante e influisce sul risultato finale. In questo caso, con un limit=N (N>0) split applica il pattern al massimo N-1 volte. Nell’array restituito ci saranno quindi N-1 elementi ottenuti spezzando la stringa e la restante parte della stringa viene restituita intatta come ultimo elemento.

Vedendo gli esempi sopra:

Con limit = 1 si ottiene un array:

{ "--aaa-bbb-ccc-ddd-eee--" }

Il pattern viene applicato 0 volte. La parte rimanente della stringa (tutta in questo caso), appare quindi intatta come unico elemento in uscita.

Con limit = 5 si ottiene un array:

{ "", "", "aaa", "bbb", "ccc-ddd-eee--" }

Il pattern viene applicato esattamente 4 volte ottenendo gli elementi "", "", "aaa", "bbb" e la parte rimanente della stringa "ccc-ddd-eee--" risulta tale e quale come quinto e ultimo elemento.

Quando il limit è superiore al numero massimo di applicazioni possibili del pattern, non fa più alcuna differenza. Come si vede bene nella prova sopra, con 9 e 10 si ottiene lo stesso risultato (e sarebbe altrettanto uguale con 11, 12, ecc...).

Conclusioni

Con questo articolo spero di aver chiarito (anche per me!) l’uso del limit di split per cui altrimenti bisognerebbe sempre andare a rileggersi la documentazione javadoc del framework. 😉