Strutture di Controllo in Java: if, else e switch con Esempi

Strutture di Controllo in Java per Android: if, else, switch, switch expression e pattern matching

Questa guida tecnica avanzata analizza in profondità le strutture di controllo if, if/else if/else, l’operatore ternario ?:, lo switch classico e le switch expression (Java 14+) con cenni al pattern matching, con un focus specifico sullo sviluppo Android in Java. Gli esempi sono pensati per sviluppatori Android che lavorano con Activity, Fragment, ViewModel e componenti del framework.

Strutture di controllo Java: if, else e switch - diagramma di flusso

1. Concetti di base: flusso di controllo e condizioni

Le questi meccanismi determinano quali parti del codice vengono eseguite in base a condizioni booleane. In Java (e quindi in Android) le condizioni utilizzano espressioni che restituiscono true o false. È fondamentale mantenere il codice leggibile, evitare annidamento eccessivo e scegliere la struttura di controllo più espressiva per il problema.

2. I blocchi condizionali if, else if, else

2.1 Sintassi di base

// Esempio semplice in un'Activity Android
if (user == null) {
    // Blocco eseguito se la condizione è true
    Log.w("MainActivity", "Utente non autenticato");
} else {
    // Blocco eseguito se la condizione è false
    Log.d("MainActivity", "Utente: " + user.getDisplayName());
}

Le parentesi graffe non sono opzionali in Android production code: usarle sempre riduce errori in fase di manutenzione (es. quando si aggiungono nuove istruzioni).

2.2 Catena if / else if / else

// Selezione di layout in base allo stato di rete
void updateUiForNetworkState(@NonNull NetworkState state) {
    if (state == NetworkState.CONNECTED_WIFI) {
        showOnlineLayout("Wi-Fi");
    } else if (state == NetworkState.CONNECTED_MOBILE) {
        showOnlineLayout("Dati mobili");
    } else if (state == NetworkState.CONNECTING) {
        showLoadingLayout();
    } else { // INCLUDE sempre un else di fallback quando ha senso
        showOfflineLayout();
    }
}

Usare una catena if/else if/else è appropriato per gestire un numero limitato di casi mutuamente esclusivi, soprattutto quando le condizioni non sono semplici confronti con una singola variabile (altrimenti è spesso meglio uno switch).

2.3 Esempio pratico: validazione input in un Fragment

private boolean validateLoginForm(String email, String password) {
    if (email == null || email.trim().isEmpty()) {
        emailInputLayout.setError("Email obbligatoria");
        return false;
    } else if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
        emailInputLayout.setError("Email non valida");
        return false;
    } else {
        emailInputLayout.setError(null);
    }
    if (password == null || password.length() < 8) {
        passwordInputLayout.setError("Password troppo corta (min 8 caratteri)");
        return false;
    } else {
        passwordInputLayout.setError(null);
    }
    return true; // Tutte le condizioni superate
}

In questo esempio dividere la logica in più blocchi if consecutivi permette di comunicare l’errore specifico all’utente, evitando un unico grande if annidato poco leggibile.

3. Operatore ternario ?: per assegnazioni compatte

L’operatore ternario è una forma compatta di if/else per espressioni che restituiscono un valore. La sintassi è:

risultato = condizione ? valoreSeTrue : valoreSeFalse;

3.1 Esempio in Android: scelta risorsa stringa

// Imposta il testo di un pulsante in base allo stato di login
boolean loggedIn = (user != null);
String buttonText = loggedIn
        ? getString(R.string.action_logout)
        : getString(R.string.action_login);
loginButton.setText(buttonText);

3.2 Esempio: colore di un TextView in base al valore

int colorResId = batteryLevel < 20
        ? R.color.vivid_red
        : R.color.light_green_cyan;
batteryTextView.setTextColor(ContextCompat.getColor(this, colorResId));

Applicare correttamente le strutture di controllo in Java richiede attenzione a questi aspetti fondamentali:

Best practice: usare il ternario solo quando l’espressione rimane leggibile. Se ci sono più livelli di nidificazione o logiche complesse, tornare a un if/else esplicito.

4. Lo statement switch classico

Lo statement switch è utile quando vogliamo eseguire rami diversi in base al valore di una singola variabile (int, String, enum, ecc.). Nelle vecchie versioni di Java usate in Android esiste solo lo statement switch tradizionale.

4.1 Sintassi di base con int ed enum

enum SyncState {
    IDLE,
    RUNNING,
    SUCCESS,
    ERROR
}
void updateSyncIndicator(@NonNull SyncState state) {
    switch (state) {
        case IDLE:
            showIdleState();
            break; // IMPORTANTE: evita fall-through non voluto
        case RUNNING:
            showProgress();
            break;
        case SUCCESS:
            showSuccessIcon();
            break;
        case ERROR:
            showErrorSnackbar();
            break;
        default:
            // Fallback per stati futuri o inattesi
            Log.w("Sync", "Stato sconosciuto: " + state);
            showIdleState();
            break;
    }
}

L’istruzione break è obbligatoria per evitare che l’esecuzione “cada” nel caso successivo (fall-through) in modo non intenzionale.

4.2 Esempio: gestione di eventi in un listener

public boolean onNavigationItemSelected(@NonNull MenuItem item) {
    switch (item.getItemId()) {
        case R.id.nav_home:
            navController.navigate(R.id.homeFragment);
            return true; // Possiamo anche restituire direttamente
        case R.id.nav_profile:
            navController.navigate(R.id.profileFragment);
            return true;
        case R.id.nav_settings:
            navController.navigate(R.id.settingsFragment);
            return true;
        default:
            return false;
    }
}

Negli handler di eventi (come click listener e navigation) è comune usare return all’interno dei vari casi al posto di un break, rendendo superfluo il default in alcuni casi.

4.3 Fall-through intenzionale con commento esplicito

switch (priority) {
    case HIGH:
        sendPushNotification(message);
        // fall through (intenzionale)
    case MEDIUM:
        showInAppBanner(message);
        break;
    case LOW:
        logMessage(message);
        break;
    default:
        logMessage("Priorità sconosciuta");
}

Quando il fall-through è intenzionale è fondamentale documentarlo con un commento (es. // fall through) per evitare fraintendimenti.

5. Switch expression (Java 14+) per codice più espressivo

Con Java 14+ sono state introdotte le switch expression, che consentono di usare switch come espressione che restituisce un valore, con una sintassi più compatta. Queste caratteristiche sono disponibili solo se usi un toolchain Java recente (ad esempio con Android Gradle Plugin che supporta Java 17+ e abilitando la compatibilità nel tuo progetto).

5.1 Sintassi base con freccia ->

String mapSyncStateToMessage(SyncState state) {
    // Switch expression: restituisce direttamente un valore
    return switch (state) {
        case IDLE -> "Sincronizzazione in attesa";
        case RUNNING -> "Sincronizzazione in corso";
        case SUCCESS -> "Sincronizzazione completata";
        case ERROR -> "Errore di sincronizzazione";
    };
}

Non servono break e ogni caso restituisce un valore. Se non viene gestito un caso, il compilatore segnalerà un errore (soprattutto con enum sealed in Java moderno).

5.2 Raggruppare più casi

String getNetworkLabel(NetworkState state) {
    return switch (state) {
        case CONNECTED_WIFI -> "Online via Wi-Fi";
        case CONNECTED_MOBILE -> "Online via rete mobile";
        case CONNECTING, DISCONNECTING -> "Connessione in corso";
        case OFFLINE -> "Offline";
    };
}

5.3 Switch expression con blocchi e parola chiave yield

Drawable getStatusIcon(Context context, SyncState state) {
    return switch (state) {
        case IDLE -> ContextCompat.getDrawable(context, R.drawable.ic_idle);
        case RUNNING -> {
            logAnalyticsEvent("sync_running");
            // Con blocchi multipli usiamo 'yield' per restituire il valore
            yield ContextCompat.getDrawable(context, R.drawable.ic_sync);
        }
        case SUCCESS -> ContextCompat.getDrawable(context, R.drawable.ic_success);
        case ERROR -> ContextCompat.getDrawable(context, R.drawable.ic_error);
    };
}

Usa yield quando hai bisogno di più istruzioni per un singolo caso mantenendo la natura espressiva dello switch.

6. Pattern matching e switch avanzati (Java 17+)

Le versioni più recenti di Java introducono il pattern matching per instanceof e (in anteprima) il pattern matching per switch. Queste funzionalità sono utili quando si lavora con gerarchie di classi, ad esempio per rappresentare eventi UI o stati di una schermata in un’architettura MVI/MVVM. Su Android sono utilizzabili solo se la toolchain supporta queste feature del linguaggio.

6.1 Pattern matching per instanceof

// Senza pattern matching
if (event instanceof UiEvent.ShowToast) {
    UiEvent.ShowToast showToast = (UiEvent.ShowToast) event;
    Toast.makeText(context, showToast.getMessage(), Toast.LENGTH_SHORT).show();
}
// Con pattern matching (Java 16+)
if (event instanceof UiEvent.ShowToast showToast) {
    Toast.makeText(context, showToast.getMessage(), Toast.LENGTH_SHORT).show();
}

Questo rende il codice più conciso e meno soggetto a errori di casting manuale.

6.2 Pattern matching per switch (feature preview)

In Java 17+ (preview) è possibile usare pattern direttamente nei switch. Esempio concettuale (il supporto in Android può variare):

sealed interface UiEvent permits UiEvent.ShowToast, UiEvent.Navigate, UiEvent.ShowDialog {
    record ShowToast(String message) implements UiEvent {}
    record Navigate(@IdRes int destinationId) implements UiEvent {}
    record ShowDialog(String title, String message) implements UiEvent {}
}
void handleEvent(@NonNull UiEvent event) {
    switch (event) {
        case UiEvent.ShowToast(var message) ->
                Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
        case UiEvent.Navigate(var destinationId) ->
                navController.navigate(destinationId);
        case UiEvent.ShowDialog(var title, var message) -> {
            new AlertDialog.Builder(context)
                    .setTitle(title)
                    .setMessage(message)
                    .setPositiveButton(android.R.string.ok, null)
                    .show();
        }
    }
}

Questo approccio è molto espressivo per modellare stati ed eventi in app complesse, riducendo la necessità di lunghi if/else if basati su instanceof.

7. Casi d’uso reali in Android

7.1 Gestione permessi runtime

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_LOCATION_PERMISSION) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            startLocationUpdates();
        } else {
            boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(
                    this, Manifest.permission.ACCESS_FINE_LOCATION);
            if (showRationale) {
                showLocationPermissionRationaleDialog();
            } else {
                showPermissionPermanentlyDeniedSnackbar();
            }
        }
    }
}

Qui gli if annidati riflettono il flusso decisionale del framework: richiesta, esito, razionale o blocco definitivo.

7.2 Binding di stato in ViewModel

public void render(@NonNull ViewState state) {
    progressBar.setVisibility(state.isLoading() ? View.VISIBLE : View.GONE);
    if (state.getErrorMessage() != null) {
        errorTextView.setVisibility(View.VISIBLE);
        errorTextView.setText(state.getErrorMessage());
    } else {
        errorTextView.setVisibility(View.GONE);
    }
    if (!state.getItems().isEmpty()) {
        recyclerView.setVisibility(View.VISIBLE);
        adapter.submitList(state.getItems());
    } else {
        recyclerView.setVisibility(View.GONE);
    }
}

L’uso combinato di ternario e if mantiene il codice conciso ma chiaro.

7.3 Gestione di Intent extras con switch

private void handleActionFromIntent(@NonNull Intent intent) {
    String action = intent.getAction();
    if (action == null) return;
    switch (action) {
        case ACTION_OPEN_DETAILS:
            long itemId = intent.getLongExtra(EXTRA_ITEM_ID, -1L);
            if (itemId != -1L) {
                openDetails(itemId);
            }
            break;
        case ACTION_SHOW_SETTINGS:
            openSettings();
            break;
        case ACTION_LOGOUT:
            confirmLogout();
            break;
        default:
            Log.w("MainActivity", "Azione Intent sconosciuta: " + action);
            break;
    }
}

Usare switch sulle action degli Intent evita lunghi if/else if su stringhe.

8. Best practices per le Strutture di Controllo Java

  • Preferisci chiarezza a concisione: se un ternario o uno switch diventano difficili da leggere, riscrivi usando if/else più espliciti.
  • Riduci l’annidamento: estrai metodi privati per gestire blocchi di logica complessi; considera l’uso di early-return per uscire prima da un metodo invece di annidare molti if.
  • Usa enum per stati discreti: invece di interi o stringhe “magiche”, definisci enum per stati di rete, tipo di utente, priorità, ecc. Questo migliora la sicurezza e la leggibilità nei switch.
  • Gestisci sempre i casi di default: nei switch classici includi un default che logga o gestisce input inattesi, soprattutto quando il valore proviene dall’esterno (Intent, API, database).
  • Evita side effect nascosti: nelle condizioni if non inserire logica troppo complessa con side effect; mantieni la condizione focalizzata sulla valutazione booleana.
  • Non confrontare mai stringhe con ==: usa sempre "text".equals(variable) o variable.equals("text") (gestendo i null) invece di == negli if.
  • Commenta il fall-through intenzionale: se usi il fall-through in uno switch, aggiungi un commento chiaro; meglio ancora, valuta se è possibile riscrivere usando switch expression.
  • Allinea la logica al ciclo di vita Android: usa le strutture di controllo per riflettere chiaramente le transizioni di stato (es. onCreate vs onResume, stato ViewModel, LiveData, ecc.).

9. Errori comuni nelle Strutture di Controllo Java

  • Dimenticare il break nello switch classico: causa fall-through non intenzionale e bug difficili da individuare. Verifica sempre ogni caso.
  • Condizioni troppo complesse in un singolo if: espressioni lunghe con molti && e || riducono la leggibilità; spezzale in variabili booleane intermedie con nomi significativi.
  • NullPointerException nelle condizioni: chiamare metodi su variabili potenzialmente null (es. text.equals("ok") quando text può essere null). Usa "ok".equals(text) o verifica esplicitamente il null prima.
  • Uso eccessivo dell’operatore ternario: più di un ternario annidato è un segnale che serve un refactoring verso if/else o metodi separati.
  • Mancata sincronizzazione con il thread UI: usare if per decidere se aggiornare la UI da un thread in background senza passare al main thread (es. con runOnUiThread o LiveData) può causare crash; ricordati sempre del contesto di esecuzione.
  • Non gestire tutti i casi di un enum: quando aggiungi un nuovo valore a un enum, aggiorna gli if e switch relativi; i switch expression aiutano perché il compilatore può segnalare i casi mancanti.

10. Conclusioni

La padronanza delle strutture di controllo Java è fondamentale per scrivere codice Android chiaro, mantenibile e privo di bug. Scegliere tra if, if/else if/else, operatore ternario, switch classico e switch expression dipende dal contesto, dalla necessità di esprimere logica come valore e dall’obiettivo di mantenere il codice leggibile. Integrare queste tecniche con un buon design dei tipi (enum, sealed class, gerarchie di eventi) consente di modellare con precisione il comportamento dell’app e di ridurre significativamente i casi limite non gestiti.

Commenti

5 risposte a “Strutture di Controllo in Java: if, else e switch con Esempi”

  1. […] array multidimensionali in Java sono in realtà array di array. Questo permette strutture irregolari (jagged […]

  2. […] Strutture di Controllo in Java […]

  3. […] Strutture di controllo in Java: if, else, switch e cicli […]

Lascia un commento