STL - container associativi ordinati

Dal quarto capitolo di Designing Components with the C++ STL, di Ulrich Breymann. Parte dedicata ai container associativi ordinati.

Un container associativo permette un veloce accesso ai dati per mezzo di una chiave che può anche non coincidere con i dati. Nei set e multiset i dati stessi sono usati come chiave, mentre in map e multimap chiavi e dati sono distinti.

STL mette a disposizione quattro tipi di container associativi:
  • set: le chiavi coincidono con i dati, non ci sono elementi che abbiano la stessa chiave;
  • multiset: é un set in cui possono esistere elementi con la stessa chiave;
  • map: chiavi e dati sono distinti, le chiavi sono uniche;
  • multimap: é una map in cui possono esistere elementi con la stessa chiave.
In più, questi container STL memorizzano le chiavi in modo ordinato.

STL - priority_queue

Dal quarto capitolo di Designing Components with the C++ STL, di Ulrich Breymann.

Una coda con priorità ritorna l'elemento che ha la priorità più alta. Il criterio con cui si determina la priorità va specificato all'atto della creazione della coda.

Nell'esempio che segue, creo prima una semplice coda con priorità che contiene interi e usa come container sottostante il default (vector, in alternativa é possibile usare anche deque) e come ordinamento ancora il default (less: il che vuol dire che il più grande intero é seleziona come primo). A seguire creo un'altra coda a priorità di interi che però usa come criterio di ordinamento greater, dunque il più piccolo intero della coda é considerato in testa:

#include <iostream>
#include <queue>
#include <vector>
#include <functional>

using namespace std;

int main() {
priority_queue<int> pq;

pq.push(20);
pq.push(10);
pq.push(30);

while(pq.empty() == false) {
cout << pq.top() << ' ';
pq.pop();
}
cout << endl;

priority_queue<int, vector<int>, greater<int> > pq2;

pq2.push(20);
pq2.push(10);
pq2.push(30);

while(pq2.empty() == false) {
cout << pq2.top() << ' ';
pq2.pop();
}
cout << endl;
}

Deserializzare un oggetto

Da Head First Java O'Reilly, capitolo 14.

Dato un oggetto reso persistente é semplice riportarlo in attività. In pratica si tratta di fare il percorso inverso compiuto nel post precedente.

Useremo un oggetto di tipo FileInputStream per leggere i byte dal file, trasformeremo i byte in oggetto per mezzo di una istanza della classe ObjectInputStream e quindi lo "leggiamo" in un oggetto. Nota che, nel processo, l'oggetto é riportato al suo stato salvato, non viene quindi invocato il suo costruttore, dato che ciò lo porterebbe al suo stato iniziale. D'altra parte, se nella gerarchia della classe dell'oggetto serializzato c'é uno o più ascendenti non serializzabili, il costruttore é chiamato per il più esterno di essi e da quel punto in poi l'oggetto e inizializzato usando gli specifici costruttori. Le eventuali componenti transienti di un oggetto serializzato vengono inizializzate con il valore di default corrispondente (gli oggetti vengono messi a null).

A seguire un semplice deserializzatore per l'oggetto Box che abbiamo serializzato nel post precedente:

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class BoxGetter {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("box.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Box myBox = (Box) ois.readObject();
ois.close();

System.out.println("My box is: " + myBox.toString());

} catch(Exception ex) {
ex.printStackTrace();
}
}
}

Come serializzare gli oggetti

Da Head First Java O'Reilly, capitolo 14.

In Java un oggetto può essere reso persistente per mezzo del meccanismo della serializzazione che, in pratica, consiste nel salvare lo stato dell'oggetto in qualche modo su una qualche periferica.

Per default una classe non é serializzabile. Bisogna implementare l'interfaccia Serializable. Non esiste alcun metodo in questa interfaccia, é utilizzata semplicemente come etichetta per indicare che siamo consapevoli che un oggetto di questa classe possa essere serializzato.

Il senso della cosa é che se per qualche motivo non vogliamo che oggetti di quella classe vengano resi persistenti abbiamo un mezzo per farlo (non implementando l'interfaccia).

Non é necessario implementare esplicitamente Serializable, é sufficiente che la nostra classe derivi da una classe che implementa questa interfaccia.

Cercare di serializzare un oggetto non serializzabile, anche solo in una sua componente, causa una eccezione di tipo java.io.NotSerializableException, ma é possibile esclude esplicitamente le componenti che non si vogliono serializzare.

Per compiere il processo ci si appoggia su due stream. Il primo si occupa della serializzazione vera e propria (traduce l'oggetto in una sequenza di byte), il secondo si occupa dei dettagli di basso livello per quanto riguarda la persistenza dello stream che gli viene passato.

Quello che facciamo nell'esempio che segue é creare un oggetto FileOutputStream, che si occupa di gestire il file, e un oggetto ObjectOutputStream che si occupa della serializzazione e usa il FileOutputStream per scrivere effettivamente su file.

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Box implements Serializable {
private int x;
private int y;
private transient InnerStuff stuff;

private class InnerStuff {
public InnerStuff(int internalCode) {
this.internalCode = internalCode;
}
private int internalCode;
}

public Box(int x, int y) {
this.x = x;
this.y = y;
this.stuff = new InnerStuff((int) (Math.random() * 1000));
}

public static void main(String[] args) {
Box box = new Box(12, 42);
try {
FileOutputStream fos = new FileOutputStream("box.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);

oos.writeObject(box);
oos.close();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}

La classe serializzabile Box ha due variabili di istanza di tipo primitivo (e quindi serializzabili) e un oggetto di tipo InnerStuff (classe che non implementando Serializable non é serializzabile). Se non specificassimo che l'oggetto InnerStuff sia transiente la serializzazione di Box fallirebbe con una eccezione.

Check box e lista swing

Da Head First Java O'Reilly, capitolo 13.

Un paio di utili widget sono il check box e la lista. Vediamo un piccolo esempio che li usa entrambi.

Lo scopo é quello di costruire una lista che permetta una selezione singola o multipla a seconda se il check box é spuntato o meno.

Vediamo subito il codice risultante:

import java.awt.BorderLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

public class MyList implements ItemListener, ListSelectionListener {

private JCheckBox cb;
private JList list;

public MyList() {
JFrame f = new JFrame();

addCheckBox(f);
addList(f);

f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(150, 220);
f.setVisible(true);
}

private void addCheckBox(JFrame f) {
cb = new JCheckBox("Multiple selection");
cb.setSelected(true);
cb.addItemListener(this);
f.add(BorderLayout.NORTH, cb);
}

private void addList(JFrame f) {
String[] items = {"alpha", "beta", "gamma", "delta", "epsilon"};
list = new JList(items);
list.addListSelectionListener(this);

JScrollPane sp = new JScrollPane(list);
sp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
sp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

f.add(BorderLayout.SOUTH, sp);
}

public void itemStateChanged(ItemEvent e) {
list.clearSelection();
if(cb.isSelected())
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
else
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

System.out.println("Multiple selection is " +
(cb.isSelected() ? "on" : "off"));
}

public void valueChanged(ListSelectionEvent e) {
if(e.getValueIsAdjusting() == false) {
if(list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
String item = (String) list.getSelectedValue();
if(item != null)
System.out.println("Selected item: " + item);
}
else {
Object[] items = list.getSelectedValues();
if(items.length > 0) {
System.out.print("Selected items:");
for(Object item : items) {
System.out.print(" " + item);
}
System.out.println();
}
}
}
}

public static void main(String[] args) {
new MyList();
}
}

Notiamo subito che la classe implementa i listener per eventi generati dalla check box (ItemListener) e dalla lista (ListSelectionListener). E dunque implementa i metodi itemStateChanged(), invocato quando lo stato della checkbox cambia, e valueChanged(), per un cambiamento nelle voci selezionate nella lista.

Ho scritto un paio di metodi privati alla classe che si occupano di istanziare le due widget. Con addCheckBox() creiamo la checkbox; la inizializziamo spuntata, con setSelected(true); registriamo la nostra classe per essere un listener della checkbox; e infine la aggiungiamo alla nostra frame.

Con addList() creiamo una lista di stringhe, che utilizziamo per creare la nostra JList su cui registriamo come listener la nostra classe e la aggiungiamo alla nostra frame.

La itemStateChanged() per prima cosa cancella tutte le eventuali selezioni (l'idea é che cambiando il modo di selezionare il lavoro precedente perde significato), poi modifica la modalità in cui lavora la lista a seconda del valore della checkbox.

La valueChanged() controlla lo stato dell'evento in ingresso per mezzo del suo metodo getValueIsAdjusting(), dato che se siamo nel mezzo di un cambiamento della selezione non dobbiamo fare alcun controllo aggiuntivo. Aspettiamo piuttosto che il cambiamento sia completato.

Controlliamo poi la modalità di selezione della lista. Se é in modalità di selezione singola, leggiamo la selezione con getSelectedValue(), se ritorna null vuol dire che non c'é alcuna selezione attiva, altrimenti scriviamo a consolle il valore della selezione.

In caso di modalità di selezione multipla reperiamo l'elenco di selezioni con getSelectedValues(), che torna un array di oggetti. Se c'é almeno una selezione (la length dell'array non é zero) le scriviamo a consolle.

Testo swing

Da Head First Java O'Reilly, capitolo 13.

JTextField permette all'utente di inserire una riga di testo. Nel caso si vogliano gestire più righe occorre passare a JTextArea, che richiede un po' più di configurazione.

Vediamo allora un piccolo esempio di uso di una text area.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class MyTextArea implements ActionListener {
private JTextArea text;

public MyTextArea() {
JFrame f = new JFrame();

text = new JTextArea(10, 20);
JScrollPane sp = new JScrollPane(text);
sp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
sp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

JPanel p = new JPanel();
p.add(sp);
f.getContentPane().add(BorderLayout.CENTER, p);

JButton b = new JButton("Click me");
b.addActionListener(this);
f.getContentPane().add(BorderLayout.SOUTH, b);

f.setSize(300, 300);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public void actionPerformed(ActionEvent e) {
text.append("Button clicked.\n");
}

public static void main(String[] args) {
new MyTextArea();
}
}

Mettiamo a sud un bottone che ogni volta che viene cliccato scrive del testo sulla textarea. Per far questo registriamo la nostra classe, che implementa l'interfaccia ActionListener, sul bottone, ed eseguiamo l'azione richiesta nel metodo actionPerformed().

La costruzione del widget per la text area, come vediamo, é un po' più laboriosa del solito, in quanto dopo aver creato la text area vera e propria, la piazziamo in un un pannello specializzato (JScrollPane) che permette la gestione delle barre di scorrimento associate alla text area.

Layout per swing

Da Head First Java O'Reilly, capitolo 13.

Una utile divisione empirica tra le componenti swing in Java é quella tra componenti interattive (JButton, JCheckBox, JTextField, ...) e componenti di background (JFrame e JPanel).

Tipicamente le seconde stanno sullo sfondo e le prime sono messe sopra a queste.

Il layout manager é il processo che decide come le componenti vengono piazzate dentro un background.

I tre layout manager più comuni:
  • BorderLayout: divide il background in cinque regioni, é possibile aggiungere una sola componente in ognuna di queste regioni; é il default per una frame.
  • FlowLayout: agisce come un word processor con il "word wrap" attivo. Ovvero le componenti vengono messe una a fianco all'altra finché ci stanno su una "riga". E poi si va "a capo"; é il default per un pannello.
  • BoxLayout: simile al FlowLayout, ma normalmente opera verticalmente ed é possibile forzare l'inizio di una "nuova riga".
Questa piccola applicazione mostra come funziona il BorderLayout. I bottoni sui bordi prendono lo spazio necessario per mostrare la propria etichetta, e tendono a coprire tutto il bordo di loro competenza. Il bottone al centro occupa tutto lo spazio che resta disponibile:

import java.awt.BorderLayout;
import javax.swing.*;

public class Border {
public Border() {
JFrame f = new JFrame();

f.getContentPane().add(BorderLayout.CENTER, new JButton("Center"));
f.getContentPane().add(BorderLayout.EAST, new JButton("East"));
f.getContentPane().add(BorderLayout.NORTH, new JButton("North"));
f.getContentPane().add(BorderLayout.WEST, new JButton("West"));
f.getContentPane().add(BorderLayout.SOUTH, new JButton("South"));

f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
f.setVisible(true);
}

public static void main(String[] args) {
new Border();
}
}

Una semplice animazione

Da Head First Java O'Reilly, capitolo 12.

Dovendo fare una animazione, e potendo scegliere il linguaggio di programmazione, io punterei su JavaFx, che semplifica molto la vita dello sviluppatore in questo contesto.

Questa piccola applicazione, disegnamo una palla che si muove di moto rettilineo uniforme, ha piuttosto l'intenzione di mostrare un altro utilizzo della classe interna (inner class) in Java.

L'idea é che abbiamo due oggetti di due classi diverse, uno fa da controller, l'altro cura la visualizzazione della palla. Il secondo oggetto deve accedere variabili private del primo, in modo da sapere dove disegnare la palla, deve quindi guadagnare in qualche modo un accesso privilegiato.

Il meccanismo della classe interna entra perfettamente in gioco in questa situazione. Vediamo il sorgente:

import java.awt.*;
import javax.swing.*;

public class SimpleAnimation {
private JFrame frame;
private int x = 70;
private int y = 70;

public SimpleAnimation() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new MyPanel());
frame.setSize(300, 300);
frame.setVisible(true);

go();
}

private void go() {
for(int i = 0; i < 130; ++i) {
++x;
++y;

frame.repaint();
try { Thread.sleep(50); } catch(InterruptedException ie) {}
}
}

private class MyPanel extends JPanel {
@Override
public void paintComponent(Graphics g) {
g.setColor(Color.GREEN);
g.fillOval(x, y, 40, 40);
}
}

public static void main(String[] args) {
new SimpleAnimation();
}
}

La inner class MyPanel usa x e y, variabili membro privati di SimpleAnimation, come base per disegnare la palla. Il metodo go() di SimpleAnimation modifica x e y, genera un repaint della finestra - e quindi chiama anche paintComponent() di MyPanel -, e poi dormicchia per mezzo decimo di secondo, per dare una sensazione di tranquillo spostamento della palla.

Due bottoni per una GUI

Da Head First Java O'Reilly, capitolo 12.

Riscriviamo la semplice Gui su cui stiamo lavorando da un po' per permetterle di gestire i comandi che arrivano da due bottoni diversi.

Questo richiede che la actionPerformed() discrimini la sorgente per decidere cosa fare o, meglio, che vi siamo due actionPerformed(), ognuna dedicata ad un compito.

Dato che non é possibile avere due metodi con lo stesso nome all'interno della stessa classe, e dato che sarebbe sgradevole delegare esternamente alla classe in cui sono definiti i bottoni la gestione dei comandi risultanti, conviene definire due classi interne (inner class) che agiscano come listener dei bottoni.

Data la MyDrawPanel definita nel precedente post, riscriviamo la classe che controlla la gui in questo modo:

import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;

public class TwoButtons {
private JFrame frame;
private JLabel label;

public TwoButtons() {
frame = new JFrame();
label = new JLabel("Hello");

JButton btnLabel = new JButton("Change label");
btnLabel.addActionListener(new LabelListener());

JButton btnColor = new JButton("Change color");
btnColor.addActionListener(new ColorListener());

MyDrawPanel panel = new MyDrawPanel();

frame.getContentPane().add(BorderLayout.WEST, label);
frame.getContentPane().add(BorderLayout.CENTER, panel);
frame.getContentPane().add(BorderLayout.EAST, btnLabel);
frame.getContentPane().add(BorderLayout.SOUTH, btnColor);

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setVisible(true);
}

class LabelListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
label.setText("Hi!");
}
}

class ColorListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
frame.repaint();
}

}

public static void main(String[] args) {
new TwoButtons();
}
}

LabelListener e ColorListener, essendo definite come inner class, hanno accesso alle variabili di instanza della classe esterna che possono accedere direttamente.

Si può dire che le classi interne permettono di implementare la stessa interfaccia più di una volta nella stessa classe.

Un widget personalizzato

Da Head First Java O'Reilly, capitolo 12.

Creiamo ora un'altra piccola applicazione GUI che usa un widget custom.

Estendiamo la classe JPanel per creare un pannello che contiene un ovale colorato con colori casuali, in modo da creare un effetto di passaggio tra due tinte.

import java.awt.*;
import javax.swing.JPanel;

public class MyDrawPanel extends JPanel {
@Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;

int red = (int) (Math.random() * 255);
int green = (int) (Math.random() * 255);
int blue = (int) (Math.random() * 255);
Color begin = new Color(red, green, blue);

red = (int) (Math.random() * 255);
green = (int) (Math.random() * 255);
blue = (int) (Math.random() * 255);
Color end = new Color(red, green, blue);

g2d.setPaint(new GradientPaint(70, 70, begin, 150, 150, end));
g2d.fillOval(70, 70, 100, 120);
}
}

L'unico metodo che ridefiniamo (nota la direttiva override) é paintConponent(), che viene invocato dalla repaint() quando occorre ridisegnare a video la componente.

Per prima cosa la reference a Graphics viene castata a Graphics2D, questo é sempre possibile e ci permette di avere accesso a metodi più avanzati.

Creiamo due colori distinti in modo causale, poi specifichiamo il nuovo pennello che vogliamo usare, con setPaint(), specificando un gradiente costruito con i colori generati, e quindi riempiamo un ovale dipinto con quel pennello.

Questa é la classe che usa il nostro pannello:

import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;

public class SimpleGuiC implements ActionListener {
private JFrame frame;

public SimpleGuiC() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JButton button = new JButton("Change colors");
button.addActionListener(this);
frame.getContentPane().add(BorderLayout.SOUTH, button);

MyDrawPanel panel = new MyDrawPanel();
frame.getContentPane().add(BorderLayout.CENTER, panel);

frame.setSize(300,300);
frame.setVisible(true);
}

public void actionPerformed(ActionEvent e) {
frame.repaint();
}

public static void main(String[] args) {
new SimpleGuiC();
}
}

Da notare, oltre all'uso del nostro widget custom, anche il fatto che abbiamo messo due componenti nel content del nostro frame, sfruttando le stringhe definite in BorderLayout che ci permettono di piazzare fino a 5 componenti che rispecchiano i punti cardinali, a cui si somma il CENTER.

In pratica piazziamo il bottone a sud (ovvero in basso) e il pannello in centro (che finisce per trasbordare in tutto il resto dello spazio a disposizione).

Cliccando sul bottone viene chiamata la actionPerformed(), che chiede un repaint dell'intero frame, e quindi anche della nostra componente custom, con conseguente nuova scelta di colori per il nostro oggetto ovoidale.

Una semplice GUI

Da Head First Java O'Reilly, capitolo 12.

Lasciamo la linea di comando per passare ad usare swing per costruirci una GUI.

Una prima semplice applicazione grafica:

import javax.swing.*;

public class SimpleGui {
public static void main(String[] args) {
JFrame f = new JFrame();
JButton b = new JButton("Hello!");

f.getContentPane().add(b);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
f.setVisible(true);
}
}

In pratica:
  • Creo un oggetto JFrame, ovvero una finestra sullo schermo.
  • Creo un oggetto JButton, un widget (oggetto grafico) che può essere aggiunto a una finestra.
  • Aggiuno il bottone al pannello dei contenuti della finestra (ottenuto con una chiamata a getContentPane()).
  • Specifico l'operazione da compiere alla chiusura della finestra.
  • Specifico la dimensione della finestra.
  • Rendo visibile la finestra.
Il risultato sarà quello di creare una finestra che contiene il bottone, che riempe tutto lo spazio a disposizione. Nel chiudere la finestra il programma viene terminato.

Per fare in modo che un click sul bottone possa essere interpretato dobbiamo fare in modo che qualcuno sia in ascolto sul bottone, e che possa quindi reagire agli eventi che il bottone genera.

Il bottone, in questo contesto viene visto come sorgente di eventi che possono essere intercettati e gestiti da un listener.

Riscriviamo l'esempio precedente facendo in modo che pigiare il bottone causi il cambiamento del testo associato.

import java.awt.event.*;
import javax.swing.*;

public class SimpleGuiB implements ActionListener {
JButton button;

public SimpleGuiB() {
JFrame f = new JFrame();
button = new JButton("Hello!");
button.addActionListener(this);

f.getContentPane().add(button);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
f.setVisible(true);
}

public void actionPerformed(ActionEvent e) {
button.setText("I've been clicked.");
}

public static void main(String[] args) {
SimpleGuiB gui = new SimpleGuiB();
}
}

La classe implementa ora l'interfaccia ActionListener, e quindi definisce il metodo actionPerformed(). Il bottone, a sua volta, registra la classe come listener per mezzo del metodo addActionListener().

Ogni evento generato dal bottone viene ora gestito dalla actionPerformed().

La sorgente di eventi, e l'oggetto evento, sono in genere gestiti da una classe preesistente (nel nostro caso JButton). Il lavoro normale dello sviluppatore consiste nello scrivere il listener. Si definisce una classe che implementa ActionListener, si scrive l'implementazione del metodo actionPerformed() per definire il comportamento in risposta all'azione osservata, e si registra il listener sulla sorgente di eventi.

Musica ed eccezioni

Da Head First Java O'Reilly, capitolo 11: eccezioni

Se utilizziamo un metodo che può generare un'eccezione, dobbiamo scrivere il codice che ne permetta una appropriata gestione. Ovvero dobbiamo usare il costrutto try/catch (o lasciare che l'eccezione si propaghi al chiamante).

Vediamo un esempio con la chiamata al metodo statico getSequencer() di MidiSystem:

package Chap11;

import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequencer;

public class MusicTest1 {
public void play() {
try {
Sequencer s = MidiSystem.getSequencer();
System.out.println("Done!");
}
catch (MidiUnavailableException ex) {
System.out.println("Something went wrong: " + ex.getMessage());
}
}

public static void main(String[] args) {
MusicTest1 mt = new MusicTest1();
mt.play();
}
}

Per generare una eccezione si usa throw e si marca il metodo dichiarando quale tipo di eccezione può lanciare. Tipo:

public void risky() throws MyException {
// ...
if (somethingBad) {
throw new MyException();
}
}

Il compilatore controlla tutte le eccezioni, tranne quelle che sono di tipo RuntimeException o derivati, che si considera possano avvenire ovunque nel codice.
Per tutte le altre deve valere lo schema indicato sopra (direttiva throws a marcare il metodo, codice cliente che tratta l'eccezione o delega il chiamante a farlo).

lo schema try/catch può essere completato dalla sezione finally: il blocco finally verrà sempre eseguito sia se c'é una eccezione nel blocco try (e quindi il blocco catch relativo all'eccezione generata é eseguito) sia se non si incontrano eccezioni.