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.

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
switchdiventano difficili da leggere, riscrivi usandoif/elsepiù 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
enumper stati di rete, tipo di utente, priorità, ecc. Questo migliora la sicurezza e la leggibilità neiswitch. - Gestisci sempre i casi di default: nei
switchclassici includi undefaultche logga o gestisce input inattesi, soprattutto quando il valore proviene dall’esterno (Intent, API, database). - Evita side effect nascosti: nelle condizioni
ifnon inserire logica troppo complessa con side effect; mantieni la condizione focalizzata sulla valutazione booleana. - Non confrontare mai stringhe con ==: usa sempre
"text".equals(variable)ovariable.equals("text")(gestendo i null) invece di==negliif. - 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.
onCreatevsonResume, 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")quandotextpuò 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/elseo metodi separati. - Mancata sincronizzazione con il thread UI: usare
ifper decidere se aggiornare la UI da un thread in background senza passare al main thread (es. conrunOnUiThreado 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 gliifeswitchrelativi; iswitchexpression 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.
Lascia un commento