Classi interne e anonymous class in Android Java: guida completa con esempi pratici
In Android Java le classi interne (inner class, static nested class, local class e anonymous class) sono uno strumento fondamentale per organizzare meglio il codice, incapsulare logica e lavorare con callback ed eventi dell'interfaccia grafica. In questa guida tecnica ma accessibile vediamo come usarle in modo efficace nello sviluppo di app Android moderne.
1. Introduzione ai tipi di classi interne in Java
Prima di entrare negli esempi Android, è utile ricordare come sono organizzate le classi interne in Java. Se hai bisogno di un ripasso dei concetti di base della programmazione ad oggetti (OOP) in Android, ti consiglio di leggere l'articolo dedicato.
In Java esistono quattro principali categorie di classi interne:
- Inner Class non statica: è legata a un'istanza della classe esterna. Può accedere direttamente a metodi e campi dell'istanza esterna.
- Static Nested Class: è una classe interna dichiarata con
static. Non è legata a un'istanza della classe esterna e si comporta in modo simile a una classe di primo livello, ma con visibilità limitata. - Anonymous Class: è una classe senza nome, creata “al volo” per implementare un'interfaccia o estendere una classe. È molto usata come callback (ad esempio per
OnClickListener). - Local Class: è una classe dichiarata all'interno di un metodo, di un costruttore o di un blocco. Ha visibilità limitata a quel contesto.
Nel contesto Android queste classi interne vengono utilizzate quotidianamente per:
- Gestire eventi dell'interfaccia (click, long click, swipe, ecc.).
- Incapsulare la logica di un
ViewHolderin unRecyclerView.Adapter. - Creare callback per thread secondari (
Runnable), timer,DialogFragmente molto altro.
2. Inner Class non statica con esempio in un'Activity Android
Una inner class non statica mantiene un riferimento implicito all'istanza esterna. In un'Activity Android questo significa che la inner class può accedere direttamente ai widget (come TextView, Button) e ai metodi dell'Activity.
Esempio: inner class che implementa View.OnClickListener dentro una MainActivity.
Java – Inner class non statica in Activity
public class MainActivity extends AppCompatActivity {
private TextView textResult;
private Button buttonHello;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textResult = findViewById(R.id.textResult);
buttonHello = findViewById(R.id.buttonHello);
// Uso della inner class non statica come listener
buttonHello.setOnClickListener(new HelloClickListener());
}
/**
* Inner class non statica.
* Può accedere direttamente ai campi e ai metodi di MainActivity.
*/
private class HelloClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
// Accedo direttamente a textResult perché la inner class
// mantiene un riferimento implicito all'istanza di MainActivity
textResult.setText("Ciao da Inner Class!");
mostraToast();
}
}
private void mostraToast() {
Toast.makeText(this, "Bottone premuto", Toast.LENGTH_SHORT).show();
}
}
Quando usare una inner class non statica:
- Quando la classe interna ha bisogno di accedere spesso allo stato dell'istanza esterna (widget, variabili, metodi).
- Quando la logica della inner class è fortemente legata all'istanza (ad esempio listener specifici di un'Activity).
3. Static Nested Class con esempio ViewHolder in RecyclerView
Una static nested class è una classe interna dichiarata con static. Non ha un riferimento implicito all'istanza esterna e in genere accede solo a membri static della classe che la contiene.
Nel mondo Android, l'esempio più comune è il ViewHolder all'interno di un RecyclerView.Adapter. Se non lo hai ancora fatto, ti consiglio di leggere l'articolo su RecyclerView in Android per avere il quadro completo.
Java – Adapter con static nested ViewHolder
public class ContattoAdapter extends RecyclerView.Adapter<ContattoAdapter.ContattoViewHolder> {
private final List<Contatto> contatti;
public ContattoAdapter(List<Contatto> contatti) {
this.contatti = contatti;
}
@NonNull
@Override
public ContattoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_contatto, parent, false);
return new ContattoViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ContattoViewHolder holder, int position) {
Contatto contatto = contatti.get(position);
holder.textNome.setText(contatto.getNome());
holder.textTelefono.setText(contatto.getTelefono());
}
@Override
public int getItemCount() {
return contatti.size();
}
/**
* Static nested class: non ha riferimento implicito a ContattoAdapter.
* Questo aiuta a evitare memory leak e rende il codice più chiaro.
*/
public static class ContattoViewHolder extends RecyclerView.ViewHolder {
TextView textNome;
TextView textTelefono;
public ContattoViewHolder(@NonNull View itemView) {
super(itemView);
textNome = itemView.findViewById(R.id.textNome);
textTelefono = itemView.findViewById(R.id.textTelefono);
}
}
}
Perché usare una static nested class per il ViewHolder?
- Performance e memoria: il
ViewHoldernon ha bisogno di accedere all'istanza dell'adapter, quindi evitare il riferimento implicito riduce il rischio di memory leak. - Chiarezza: rende evidente che la classe non dipende dallo stato dell'adapter.
- Struttura: la classe rimane logicamente collegata all'adapter ma è isolata come implementazione.
4. Anonymous Class: OnClickListener, Runnable, Comparator con esempi Android
Una anonymous class è una classe senza nome definita e istanziata in un unico punto. In Java tradizionale, prima delle lambda, era il modo principale per creare callback veloci. In Android la troverai spesso con listener, runnable e comparator.
4.1 Anonymous class per View.OnClickListener
buttonHello.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,
"Click gestito da anonymous class",
Toast.LENGTH_SHORT).show();
}
});
Qui non creiamo una classe separata per il listener: implementiamo OnClickListener direttamente nel punto in cui ne abbiamo bisogno.
4.2 Anonymous class per Runnable (thread secondario)
new Thread(new Runnable() {
@Override
public void run() {
// Esegui operazioni in background
// ...
// Torna sul thread principale (UI thread)
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,
"Operazione completata",
Toast.LENGTH_SHORT).show();
}
});
}
}).start();
In questo esempio abbiamo due anonymous class: una per il Runnable del thread secondario e una per il Runnable passato a runOnUiThread().
4.3 Anonymous class per Comparator (ordinamento lista)
Collections.sort(contatti, new Comparator<Contatto>() {
@Override
public int compare(Contatto c1, Contatto c2) {
// Ordina per nome in modo crescente
return c1.getNome().compareToIgnoreCase(c2.getNome());
}
});
In assenza delle lambda, questo è il modo più comune per definire velocemente un criterio di ordinamento personalizzato.
5. Local Class con esempio pratico
Una local class è una classe dichiarata all'interno di un metodo. È utile quando hai bisogno di una classe di supporto che abbia senso solo in un determinato metodo, e non vuoi “inquinare” il livello superiore della classe.
Esempio: una local class per validare un form di login all'interno di un metodo validaForm() in un'Activity.
private void validaForm() {
final String email = editEmail.getText().toString();
final String password = editPassword.getText().toString();
// Local class: visibile solo dentro validaForm()
class Validator {
boolean isEmailValida() {
return email.contains("@") && email.contains(".");
}
boolean isPasswordValida() {
return password.length() >= 6;
}
}
Validator validator = new Validator();
if (!validator.isEmailValida()) {
editEmail.setError("Email non valida");
return;
}
if (!validator.isPasswordValida()) {
editPassword.setError("Password troppo corta");
return;
}
// Se arrivo qui i dati sono validi
Toast.makeText(this, "Login ok", Toast.LENGTH_SHORT).show();
}
La local class Validator è visibile solo dentro validaForm() e può accedere alle variabili email e password dichiarate come final (o effettivamente final).
6. Mini App Lista Contatti: esempio completo
Mettiamo insieme quanto visto finora costruendo una piccola schermata che mostra una lista di contatti con RecyclerView. Utilizzeremo:
- Static nested class per il
ViewHolder. - Anonymous class per il
OnClickListenersugli item. - Anonymous class per il
Comparatordi ordinamento.
6.1 Modello dati Contatto
public class Contatto {
private final String nome;
private final String telefono;
public Contatto(String nome, String telefono) {
this.nome = nome;
this.telefono = telefono;
}
public String getNome() {
return nome;
}
public String getTelefono() {
return telefono;
}
}
6.2 Layout XML Activity principale
res/layout/activity_lista_contatti.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ListaContattiActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerContatti"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/
>
</androidx.constraintlayout.widget.ConstraintLayout>
6.3 Layout XML per il singolo item
res/layout/item_contatto.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:id="@+id/textNome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textStyle="bold"
android:text="Nome"/>
<TextView
android:id="@+id/textTelefono"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#666666"
android:text="Telefono"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
6.4 Adapter con ViewHolder static nested e OnClickListener anonimo
public class ContattoAdapter extends RecyclerView.Adapter<ContattoAdapter.ContattoViewHolder> {
public interface OnContattoClickListener {
void onContattoClick(Contatto contatto);
}
private final List<Contatto> contatti;
private final OnContattoClickListener listener;
public ContattoAdapter(List<Contatto> contatti, OnContattoClickListener listener) {
this.contatti = contatti;
this.listener = listener;
}
@NonNull
@Override
public ContattoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_contatto, parent, false);
return new ContattoViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ContattoViewHolder holder, int position) {
final Contatto contatto = contatti.get(position);
holder.textNome.setText(contatto.getNome());
holder.textTelefono.setText(contatto.getTelefono());
// Anonymous class per OnClickListener sul singolo item
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onContattoClick(contatto);
}
}
});
}
@Override
public int getItemCount() {
return contatti.size();
}
// Static nested class per il ViewHolder
public static class ContattoViewHolder extends RecyclerView.ViewHolder {
TextView textNome;
TextView textTelefono;
public ContattoViewHolder(@NonNull View itemView) {
super(itemView);
textNome = itemView.findViewById(R.id.textNome);
textTelefono = itemView.findViewById(R.id.textTelefono);
}
}
}
6.5 Activity che utilizza RecyclerView e Comparator anonimo
ListaContattiActivity.java
public class ListaContattiActivity extends AppCompatActivity {
private RecyclerView recyclerContatti;
private ContattoAdapter adapter;
private List<Contatto> contatti;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lista_contatti);
recyclerContatti = findViewById(R.id.recyclerContatti);
recyclerContatti.setLayoutManager(new LinearLayoutManager(this));
contatti = creaContattiDiEsempio();
// Comparator definito con anonymous class per ordinare i contatti per nome
Collections.sort(contatti, new Comparator<Contatto>() {
@Override
public int compare(Contatto c1, Contatto c2) {
return c1.getNome().compareToIgnoreCase(c2.getNome());
}
});
adapter = new ContattoAdapter(contatti, new ContattoAdapter.OnContattoClickListener() {
@Override
public void onContattoClick(Contatto contatto) {
// Gestisci il click sul contatto
Toast.makeText(ListaContattiActivity.this,
"Hai selezionato: " + contatto.getNome(),
Toast.LENGTH_SHORT).show();
// Esempio: potresti aprire un Fragment o un DialogFragment
// vedi gli articoli dedicati a
// https://apptutorial.link/cambiare-fragment-android/
// https://apptutorial.link/dialogfragment-android/
}
});
recyclerContatti.setAdapter(adapter);
}
private List<Contatto> creaContattiDiEsempio() {
List<Contatto> lista = new ArrayList<>();
lista.add(new Contatto("Andrea", "3331111111"));
lista.add(new Contatto("Luca", "3332222222"));
lista.add(new Contatto("Maria", "3333333333"));
lista.add(new Contatto("Giulia", "3334444444"));
return lista;
}
}
Questa mini app dimostra in un contesto reale come combinare static nested class, anonymous class e comparator per costruire una schermata Android completa.
7. Tabella comparativa: Inner vs Static Nested vs Anonymous vs Lambda
Per riassumere, ecco una tabella che confronta le principali caratteristiche delle diverse tipologie di classi interne in Java (con riferimento all'uso in Android). Le lambda non sono una classe interna, ma offrono una sintassi alternativa alle anonymous class per interfacce funzionali.
| Tipo | Ha nome? | Riferimento all'istanza esterna | Uso tipico in Android | Vantaggi | Svantaggi |
|---|---|---|---|---|---|
| Inner Class (non statica) | Sì | Sì | Listener legati a un'Activity o Fragment, helper che usano spesso lo stato esterno | Accesso diretto ai membri dell'istanza esterna, codice leggibile | Rischio di memory leak se usata in modo scorretto (thread lunghi, handler, ecc.) |
| Static Nested Class | Sì | No | ViewHolder di RecyclerView, helper che non dipendono dall'istanza | Più sicura lato memoria, dipendenze chiare, più facile da testare | Non può usare direttamente membri non statici della classe esterna |
| Anonymous Class | No | Di solito sì (permette accesso a membri esterni e variabili effectively final) | OnClickListener, Runnable, Comparator, callback varie | Definizione e utilizzo in un unico punto, ottima per callback semplici | Codice verboso se i metodi sono lunghi, difficile da riutilizzare |
| Local Class | Sì, ma visibile solo nel metodo | Sì (può accedere a membri esterni e variabili final/effectively final) | Logica di supporto confinata in un singolo metodo complesso | Incapsula bene i dettagli implementativi, migliora la leggibilità | Può complicare il metodo se troppo grande, difficile da testare separatamente |
| Lambda (Java 8+) | No (è una espressione) | Dipende dal tipo di cattura; usata solo con interfacce funzionali | Alternative compatte a anonymous class per listener e callback semplici | Sintassi molto concisa, codice più pulito per callback brevi | Meno adatta quando servono molti metodi o stato interno complesso |
8. Conclusione
Le classi interne in Java sono uno strumento potente e flessibile, particolarmente utile nello sviluppo Android dove la gestione di eventi, callback e componenti UI è all'ordine del giorno. Saper scegliere tra inner class, static nested class, anonymous class, local class e lambda ti permette di scrivere codice più pulito, sicuro e manutenibile.
Come linea guida pratica:
- Usa static nested class quando non ti serve l'istanza esterna (es.
ViewHolder). - Usa inner class non statica quando la classe interna deve accedere spesso a membri non statici dell'istanza esterna.
- Usa anonymous class o lambda per callback brevi e locali (listener, runnable, comparator).
- Usa local class per strutturare meglio metodi complessi senza esporre dettagli all'intera classe.
Per approfondire la progettazione delle tue Activity e Fragment in un'ottica orientata agli oggetti, puoi dare un'occhiata anche agli articoli su Activity, Fragment e DialogFragment. Combinando questi concetti con un buon uso delle classi interne potrai strutturare app Android robuste e facilmente estendibili.
Lascia un commento