Visualizzazione post con etichetta Java. Mostra tutti i post
Visualizzazione post con etichetta Java. Mostra tutti i post

Le vocali in una stringa

Scrivere un metodo statico in Java che ritorni il numero di vocali della stringa che gli viene passata come parametro.

Assumiamo che per vocali si intendano solo i caratteri a, e, i, o, u.

A voler fare i precisini bisognerebbe considerare anche i caratteri accentati, per non parlare dei caratteri che rappresentano suoni vocalici specifici di alcune lingue (tedesco, svedese, per dirne un paio). Ma limitiamoci alla soluzione di base.

In una classe creiamo un paio di metodi statici. Il primo, isVowel(), accetta in ingresso un carattere e ritorna true solo se questo é una vocale, secondo la nostra definizione:

public static final boolean isVowel(char c) {
switch(c) {
case 'A':
case 'a':
case 'E':
case 'e':
case 'I':
case 'i':
case 'O':
case 'o':
case 'U':
case 'u':
return true;
default:
return false;
}
}

Il secondo metodo é quello richiesto, accetta in input una stringa e fa un loop su di essa al fine di contare tutte le vocali presenti:

public static int vowels(String input) {
int value = 0;
if(input == null)
return 0;

for(int i = 0; i < input.length(); ++i)
if(isVowel(input.charAt(i)))
++value;
return value;
}

Per testare la funzionalità ho usato questo main:

public static void main(String[] argv) {
int value = vowels("ambaradan");
System.out.println(value);

value = vowels(null);
System.out.println(value);

value = vowels("");
System.out.println(value);

value = vowels("aeiou");
System.out.println(value);

value = vowels("sfrt");
System.out.println(value);
}

Set ordinati e no

Da Head First Java O'Reilly, capitolo 16 che tratta collezioni e programmazione generica.

Abbiamo detto che gli elementi di un Set sono unici. Ma dobbiamo specificare cosa si intende essere unico per un elemento.

Due oggetti sono considerati uguali in Java se vale one.equals(two) e one e two hanno il medesimo codice hash, come ritornato dal metodo hashcode(). Perché Set consideri un oggetto come duplicato dobbiamo ridefinire hashCode() e equals() in modo che i due oggetti siano visti come identici.

Per vedere se due reference puntino allo stesso oggetto possiamo usare l'operatore ==.

Dunque, se vogliamo poter usare la classe Song che abbiamo visto in questo post in un Set dobbiamo ridefinire il modo in cui due oggetti di tipo Song sono riconosciuti come identici. Aggiungeremo perciò la ridefinizione per il metodo equals() e hashCode() in questo modo:

public class Song implements Comparable<Song> {

private String title;
private String artist;
private int rating;

// ...

@Override
public boolean equals(Object o) {
if(o instanceof Song) {
Song s = (Song) o;
return s.title.equals(title) && s.artist.equals(artist);
}
return false;
}

@Override
public int hashCode() {
return title.hashCode() + artist.hashCode();
}

// ...
}

Data questa classe Song, possiamo riscrivere il nostro JukeBox per fare in modo che accetti in input una lista di brani non unici ma la collezione risultante contenga solo elementi unici.

Dobbiamo di nuovo prendere la decisione se vogliamo che la nostra collezione sia ordinata o meno. Se non siamo interessati all'ordinamento ci conviene usare HashSet, altrimenti TreeSet ci permette di mantenere la nostra collezione ordinata, al costo di un leggero aggravio dei tempi d'uso.
Nell'esempio che segue usiamo entrambi gli approcci:

package Chap16;

import java.util.*;

public class JukeBoxC {
private HashSet<Song> hs = new HashSet<Song>();
private TreeSet<Song> ts = new TreeSet<Song>();

public JukeBoxC() {
getSongs();
System.out.println(hs);
System.out.println(ts);
}

private void getSongs() {
// fake implementation
String[] fakeList = {"Pink Moon/Nick Drake/5", "Somersault/Zero 7/4",
"Shiva Moon/Prem Joshua/5", "Circles/BT (Brian Wayne Transeau)/3",
"Deep Channel/Afro Celts/3", "Passenger/Headmix/4",
"Pink Moon/Nick Drake/5", "Listen/Tahiti 80/3", "Listen/Tahiti 80/3"};
for(String s : fakeList) {
System.out.println(s);
String[] tokens = s.split("/");

int rating = Integer.parseInt(tokens[2]);
Song aSong = new Song(tokens[0], tokens[1], rating);
if(hs.add(aSong) == false) {
System.out.println("Duplicated song discarded");
}
ts.add(aSong);
}
}

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

Collezioni in Java

Da Head First Java O'Reilly, capitolo 16 che tratta collezioni e programmazione generica.

I principali tipi di collezioni resi disponibili in Java sono:
  • List: quando l'accento é sulla posizioni nella collezione. É possibile avere elementi ripetuti.
  • Set: quando vogliamo che gli elementi inclusi siano unici.
  • Map: quando si una chiave di accesso (univoca) agli elementi (che possono essere duplicati) nella collezione.
Se guardiamo la struttura delle classi e interfacce, scopriamo una ambiguità. Gran parte delle collezioni implementano, direttamente o indirettamente l'interfaccia Collection. Alcune però implementano l'interfaccia Map che é indipendente dalla gerarchia basata su Collection.

Le principali interfacce e classi definite in questo contesto sono:

Collection
Set [implementata da HashSet e LinkedHashSet], List [implementata da ArrayList, LinkedList e Array]
SortedSet [implementata da TreeSet]

Map [implementata da HashMap, LinkedHashMap e Hashtable]
SortedMap [implementata da TreeMap]

Una ArrayList ordinata /2

Da Head First Java O'Reilly, capitolo 16 che tratta collezioni e programmazione generica.

L'uso del metodo statico sort() della classe Collections, che abbiamo visto nel precendente post, diventa un poco più complicato in un caso più realistico, in cui la collezione non usi un tipo standard (String, nel caso visto) ma un tipo custom.

Infatti la dichiarazione del metodo sort() é la seguente:

public static <T extends Comparable<? super T>> void sort(List<T> list)

Ne consegue che, se intendiamo usare questo metodo sulla nostra collezione, questa deve essere basata su un tipo che implementi l'interfaccia Comparable.

Creiamo quindi la classe Song che descrive le canzoni come vogliamo vengano gestite dalla nostra applicazione:

package Chap16;

public class Song implements Comparable<Song> {
private String title;
private String artist;
private int rating;

public void setArtist(String artist) {
this.artist = artist;
}

public String getArtist() {
return artist;
}

public Song(String title, String artist, int rating) {
this.title = title;
this.artist = artist;
this.rating = rating;
}

public int compareTo(Song s) {
int result = title.compareTo(s.title);
if(result == 0)
return artist.compareTo(s.artist);

return result;
}

@Override
public String toString() {
return title + " by " + artist;
}
}

L'interfaccia Comparable richiede che si implementi il medoto compareTo(), nel nostro caso si compara il titolo e, in caso che questo risulti identico, l'artista.

L'altro punto che resta scoperto é la possibilità di ordinare la collezione usando un criterio diverso da quello standard per la classe sottostante. Diciamo che nel nostro caso si voglia realizzare un ordinamento anche in base all'autore e non solo al titolo del brano.

Il metodo sort() di Collections prevede la possibilità di specificare un oggetto comparatore che definisce come comparare gli oggetti durante l'ordinamento usando questa versione di sort:

public static <T> void sort(List<T> list, Comparator<? super T> c)


Definiamo come classe interna ArtistCompare che ha proprio questo scopo:

package Chap16;

import java.util.*;

public class JukeBoxB {
private ArrayList<Song> songs = new ArrayList<Song>();

public JukeBoxB() {
getSongs();
System.out.println(songs);
Collections.sort(songs);
System.out.println(songs);
Collections.sort(songs, new ArtistCompare());
System.out.println(songs);
}

private void getSongs() {
// fake implementation
String[] fakeList = {"Pink Moon/Nick Drake/5", "Somersault/Zero 7/4",
"Shiva Moon/Prem Joshua/5", "Circles/BT (Brian Wayne Transeau)/3",
"Deep Channel/Afro Celts/3", "Passenger/Headmix/4",
"Listen/Tahiti 80/3"};
for(String s : fakeList) {
System.out.println(s);
String[] tokens = s.split("/");

int rating = Integer.parseInt(tokens[2]);
songs.add(new Song(tokens[0], tokens[1], rating));
}
}

private class ArtistCompare implements Comparator {

public int compare(Song s1, Song s2) {
return s1.getArtist().compareTo(s2.getArtist());
}
}

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

Una ArrayList ordinata

Da Head First Java O'Reilly, capitolo 16 che tratta collezioni e programmazione generica.

Una operazione tipica che viene richiesta sulle collezioni é l'ordinamento. La collezione che abbiamo utilizzato prevalentemente in Head First Java é ArrayList, un array esteso di elementi non ordinati. Consideriamo un caso in cui siamo richiesti di leggere una serie di stringhe (titoli di brani musicali) e di presentarli in ordine alfabetico.

Abbiamo fondamentalmente un paio di alternative: o utilizziamo una collezione diversa, che mantenga i propri elementi in ordine, o applichiamo un ordinamento agli elementi della collezione.

La seconda alternativa é preferibile se la nostra collezione é tutto sommato statica, con pochi inserimenti. Se questo é il caso, possiamo utilizzare il metodo statico sort() della classe Collections per riorganizzare gli elementi come richiesto.

Vediamo un esempio:

package Chap16;

import java.util.*;

public class JukeBox {
private ArrayList<String> songs = new ArrayList<String>();

public JukeBox() {
getSongs();
System.out.println(songs);
Collections.sort(songs);
System.out.println(songs);
}

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

private void getSongs() {
// fake implementation
String[] fakeList = {"Pink Moon/Nick Drake", "Somersault/Zero 7",
"Shiva Moon/Prem Joshua", "Circles/BT (Brian Wayne Transeau)",
"Deep Channel/Afro Celts", "Passenger/Headmix", "Listen/Tahiti 80"};
for(String s : fakeList) {
System.out.println(s);
String[] tokens = s.split("/");
songs.add(tokens[0]);
}
}
}

Il metodo getSongs() simula la gestione dell'input da una fonte esterna di una lista di titoli e autori divisi dal carattere /. Qui ci interessano solo i titoli, facciamo dunque uno split di ogni stringa e prendiamo solo la prima parte per metterla nella collezione di canzoni.

Dopo aver caricato i brani eseguiamo una sort e vediamo come effettivamente l'operazione funzioni.

Chat client /2

Da Head First Java O'Reilly, capitolo 15. Networking e threads.

Dopo aver visto un semplice chat server multithread e un client minimale che gli si connette, vediamo ora un client che implementa anche la ricezione di messaggi dal server.

A livello di GUI il cambiamento consiste nell'introduzione di un oggetto JTextArea destinato a raccogliere i messaggi mandati dal chat server. Il metodo addIncoming() si fa carico di inizializzare la JTextArea mettendola in un pannello scrollabile e quindi nel pannello principale.

Il metodo connect() che stabilisce la connessione con il chat server estrae dal socket l'input stream, che verrà utilizzato dal chat server per mandare i messaggi, crea a partire da esso un reader su input stream, e quindi un reader bufferizzato che mette a disposizione della classe nella variabile d'istanza reader.

Ultimo cambiamento é il nuovo thread che viene creato e fatto partire al termine del costruttore del chat client. La classe che viene passata al costruttore del thread (che deve implementare l'interfaccia Runnable) é definita come inner class del chat client, MessagesReader.

Il metodo run() del MessageReader resta appeso sullo stream di input del socket, in attesa di messaggi. Come un messaggio viene ricevuto lo si aggiunge alla text area della GUI.

package Chap15;

import java.awt.BorderLayout;
import java.awt.event.*;
import java.io.*;
import java.net.Socket;
import javax.swing.*;

public class ChatClient2 {

JTextField outgoing = new JTextField(20);
JTextArea incoming = new JTextArea(15, 25);
PrintWriter writer;
BufferedReader reader;

public ChatClient2() {
JFrame frame = new JFrame("Chat Client 2");
JPanel panel = new JPanel();
JButton btnSend = new JButton("Send");
btnSend.addActionListener(new ButtonListener());

addIncoming(panel);
panel.add(outgoing);
panel.add(btnSend);
frame.getContentPane().add(BorderLayout.CENTER, panel);

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

connect();

new Thread(new MessagesReader()).start();
}

private void connect() {
try {
Socket socket = new Socket("localhost", 4242);

InputStreamReader isr = new InputStreamReader(socket.getInputStream());
reader = new BufferedReader(isr);
writer = new PrintWriter(socket.getOutputStream());
System.out.println("Connected to chat server");
}
catch (Exception ex) {
ex.printStackTrace();
}
}

private void addIncoming(JPanel panel) {
incoming.setLineWrap(true);
incoming.setWrapStyleWord(true);
incoming.setEditable(false);

JScrollPane scroller = new JScrollPane(incoming);
scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);

panel.add(scroller);
}

private class ButtonListener implements ActionListener {

public void actionPerformed(ActionEvent e) {
String txt = outgoing.getText();
System.out.println("Sending message: " + txt);
try {
writer.println(txt);
writer.flush();
}
catch (Exception ex) {
ex.printStackTrace();
}

outgoing.setText("");
outgoing.requestFocus();
}
}

private class MessagesReader implements Runnable {

public void run() {
String message;
try {
while ((message = reader.readLine()) != null) {
System.out.println("Message read: " + message);
incoming.append(message + "\n");
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}

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

Anche questo client non é molto robusto. In particolare lo si deve lanciare solo quando il server é in esecuzione.

Nel server ho preferito non implementare la classe di appoggio come interna alla classe principale del server, in quanto la interazione tra le due classi é debole. Nel caso del client, c'é una interazione più forte, il MessageReader usa due variabili di istanza del client, reader e incoming, e inoltre il suo unico metodo, run(), é compatto e non mi pare renda troppo complesso il codice della classe esterna. Mi pare perciò più semplice usare una inner class per implementare questa relazione.

Chat client /1

Da Head First Java O'Reilly, capitolo 15. Networking e threads

Nel post precedente abbiamo visto un semplice chat server multithread. Ora vediamo il lato client.

La prima versione é molto semplice, serve più che altro a testare il server. Ci permette infatti solo di creare un messaggio e mandarlo al server.

La classe ChatClient costruisce una applicazione GUI minimale, i punti salienti del codice che vediamo a seguire sono nel metodo connect(), che stabilisce la connessione con il server (che deve essere in esecuzione!), e nella classe interna ButtonListener che mette a disposizione il metodo actionPerformed() che reagisce alla pressione del bottone:

package Chap15;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintWriter;
import java.net.Socket;
import javax.swing.*;

public class ChatClient {
JTextField message = new JTextField(20);
PrintWriter writer;

public ChatClient() {
JFrame frame = new JFrame("Chat Client");
JPanel panel = new JPanel();
JButton btnSend = new JButton("Send");
btnSend.addActionListener(new ButtonListener());

panel.add(message);
panel.add(btnSend);
frame.getContentPane().add(BorderLayout.CENTER, panel);

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

connect();
}

private void connect() {
try {
Socket socket = new Socket("localhost", 4242);
writer = new PrintWriter(socket.getOutputStream());
System.out.println("Connected to chat server");
} catch(Exception ex) {
ex.printStackTrace();
}
}

private class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
String txt = message.getText();
System.out.println("Sending message: " + txt);
try {
writer.println(txt);
writer.flush();
} catch(Exception ex) {
ex.printStackTrace();
}

message.setText("");
message.requestFocus();
}
}

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

Nel metodo connect() creiamo un socket che cerca di connettersi alla macchina locale (localhost) sulla porta 4242. Se la connessione riesce creiamo un oggetto PrintWriter a partire dallo stream di output del socket nella variabile di istanza writer.
Il metodo actionPerformed() della classe interna ButtonListener, reagisce alla pressione del bottone leggendo il testo dalla input line e scrivendolo nel writer.

Un semplice chat server

Da Head First Java O'Reilly, capitolo 15. Networking e threads

Estendiamo il nostro esempio di una applicazione client/server creando un semplice sistema di chat. Il passo in avanti é che usiamo il multithreading (wow).

In questo post vediamo la parte server del sistema. Si tratta di un server semplificato al massimo, quello che fa é creare un server socket sulla porta 4242 e restare appeso in attesa di connessione dal lato client. Ogni volta che un client si connette creiamo un oggetto ChatClientHandler e facciamo partire un nuovo thread che gestisce la connessione.

Nel libro il client handler é una inner class del server. A me le classi interne mi innervosiscono, mi pare rendano il codice meno leggibile. In questo caso, poi, la connessione tra le due classi non é particolarmente forte, ho preferito quindi utilizzare un meccanismo di call back: il server passa se stesso alla classe interna in modo che questa possa richiamare il suo metodo tellEveryone().

Il client handler implementa l'interfaccia Runnable, in quanto vogliamo eseguire i suoi oggetti in differenti thread. Il costruttore ha come parametri il ChatServer, in modo da poter fare la callback, e il socket che usiamo per la connessione al client. Dal socket estraiamo l'input stream e con esso, via InputStreamReader, costruiamo il BufferedReader che, variabile di istanza, utilizziamo per leggere le comunicazioni che ci arrivano dal client.

Il metodo run() resta appeso in lettura sul BufferedReader. Come arriva una stringa facciamo la callback al chat server invocando il metodo tellEveryone() che manda il messaggio a tutti i sottoscrittori attivi della chat.

Questo il codice:

package Chap15;

import java.io.*;
import java.net.Socket;

public class ChatClientHandler implements Runnable {

private ChatServer server;
private BufferedReader reader;

public ChatClientHandler(ChatServer server, Socket socket) {
this.server = server;
try {
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
reader = new BufferedReader(isr);
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}

public void run() {
String message;
try {
while ((message = reader.readLine()) != null) {
System.out.println("Got message: " + message);
server.tellEveryone(message);
}
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}

Il chat server implementa il main(), che semplicemente crea un oggetto ChatServer. Nel costruttore il loop infinito crea un nuovo thread per ogni nuovo cliente che si connette alla chat e aggiunge un oggetto PrintWriter, derivato dal output stream del socket di connessione al client, alla lista mantenuta come variabile di istanza nell'oggetto.

Il metodo tellEveryone() viene chiamato dal gestore dei client e fa un loop su tutti gli oggetti PrintWriter nella lista, su ognuno dei quali chiama la println() per notificare il messaggio arrivato.

package Chap15;

import java.io.*;
import java.net.*;
import java.util.*;

public class ChatServer {
private ArrayList<PrintWriter> streams = new ArrayList<PrintWriter>();

public ChatServer() {
try {
ServerSocket ss = new ServerSocket(4242);
System.out.println("Chat server started on port 4242");

while(true) {
Socket client = ss.accept();
streams.add(new PrintWriter(client.getOutputStream()));

Thread t = new Thread(new ChatClientHandler(this, client));
t.start();
}
}
catch(Exception ex) {
ex.printStackTrace();
}
}

/**
* package visibility, to be used by the client handler
*/
void tellEveryone(String message) {
Iterator<PrintWriter> it = streams.iterator();
while(it.hasNext()) {
try {
PrintWriter pw = it.next();
pw.println(message);
pw.flush();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}

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

Da notare che il codice é veramente minimale e richiede una cooperazione assidua da parte dell'utilizzatore. Occorre lanciare prima il server, poi i client; un client che non trova il server ad attenderlo sulla porta specificata, infatti, non sa che fare. Allo stesso modo, per terminare l'esecuzione conviene prima chiudere il server e poi i client, altrimenti al server verrà recapitata un'eccezione ogni volta che un client chiude, dato che la readLine() al reader nel client handler verrà notificata una eccezione SocketException dovuta al reset della connesione sottostante.

Multithreading e concorrenza in Java /2

Da Head First Java O'Reilly, capitolo 15. Networking e threads

Nel post precendente abbiamo visto una applicazione multithread con un problema di concorrenza su di una risorsa condivisa. Ora vediamo come fare in modo che la nostra gestione del conto corrente funzioni come atteso.

Dobbiamo regolamentare l'accesso alla risorsa, ovvero vogliamo creare una sorta di coda di processi in esecuzione sul metodo withdrawl(). Per far questo in Java basta marcare il metodo come synchronized. In pratica questo é l'unico cambiamento che ci serve per evitare lo spiacevole comportamento che abbiamo osservato:

private synchronized boolean withdrawl(int amount) {
if(account.getBalance() >= amount) {
// ...
return true;
}

// can't withdraw
System.out.println(Thread.currentThread().getName() + ": out of money");
return false;
}


A livello implementativo quello che Java fa é mettere a disposizione un semaforo su ogni oggetto. Prima di eseguire un metodo synchronized il thread corrente controlla il semaforo. Se un altro thread é in esecuzione nella zona protetta, il thread corrente si mette in attesa del suo turno sul semaforo. Ma, dato che il semaforo é sull'oggetto e non sul metodo, se i metodi sincronizzati di una classe sono più di uno, non é possibile eseguire due di questi metodi contemporaneamente in due diversi thread.

Multithreading e concorrenza in Java

Da Head First Java O'Reilly, capitolo 15. Networking e threads

Abbiamo visto quanto sia facile creare applicazioni multithreading in Java, ma questo non ci libera dai problemi intrinseci a questo modello di sviluppo.

Un problema tipico si ha quando due diversi attori competono su di un unica risorsa. Nell'esempio che vediamo ci sono due utenti che cercano di accedere allo stesso conto corrente per fare un prelievo. Una progettazione poco attenta dello scenario può portare al codice sottostante che, come vedremo, causa grossi problemi.

La classe RMAccount definisce il conto corrente che viene acceduto da Ryan e Monica. Parte con un valore di 100 e su di esso possono essere operati dei prelievi. Il nostro scopo sarebbe quello di evitare che il conto vada in rosso.

package Chap15;

public class RMAccount {
private int balance = 100;

public int getBalance() {
return balance;
}

public void withdraw(int amount) {
balance -= amount;
}
}


La classe RMJob definisce la classe runnable. Il main crea una istanza di RMJob e due thread (Ryan e Monica) che operano sullo stesso oggetto. L'oggetto RMJob ha un solo account, definito come variabile di istanza, il metodo run() itera per dieci volte un prelievo chiamando il metodo withdrawl() che dovrebbe operare il prelievo solo se il bilancio del conto lo permette.

Si controlla dunque che il bilancio sia maggiore della somma che si vuole prelevare e, solo se é il caso, si effettua il prelievo. Per rendere evidente la debolezza di questo approccio mettiamo in pausa il thread per mezzo secondo tra il test sul bilancio e l'effettivo prelievo.

package Chap15;

public class RMJob implements Runnable {

private RMAccount account = new RMAccount();

private boolean withdrawl(int amount) {
if(account.getBalance() >= amount) {
System.out.println(Thread.currentThread().getName() + ": withdrawing");
try {
System.out.println(Thread.currentThread().getName() + ": sleeping a bit");
Thread.sleep(500);
}
catch (InterruptedException ie) {
ie.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": up again");
account.withdraw(amount);
System.out.println(Thread.currentThread().getName() + ": withdrawl done");
return true;
}

// can't withdraw
System.out.println(Thread.currentThread().getName() + ": out of money");
return false;
}

public void run() {
for (int i = 1; i < 10; ++i) {
if(withdrawl(10)) {
if(account.getBalance() < 0) {
System.out.println("Negative balance! ");
}
}
}
}

public static void main(String[] args) {
RMJob job = new RMJob();

new Thread(null, job, "Ryan").start();
new Thread(null, job, "Monica").start();
}
}

L'aver aggiunto lo sleep() rende il problema evidente. Un thread esegue il controllo, che riesce, ma prima che possa eseguire il prelievo, l'altro thread esegue nuovamente il controllo con successo. A questo punto solo uno dei due potrà prelevare correttamente i soldi dal conto, l'altro lo manderà inopinatamente in rosso.

Il nostro codice va ridisegnato: la risorsa condivisa deve essere protetta.

Multithreading in Java

Da Head First Java O'Reilly, capitolo 15. Networking e threads

Creare più thread di esecuzione in Java é davvero molto semplice. In pratica si tratta di creare una classe che implementi l'interfaccia Runnable, mettendo nel suo metodo run() la logica che deve essere eseguita dal nostro thread.
Si crea poi un nuovo oggetto di tipo Thread passando al costruttore un oggetto della classe che abbiamo appena definito, e chiamiamo su di esso il metodo start().

Nell'esempio a seguire creiamo due thread, in ognuno dei quali corre una istanza della classe MyRunnable. Nota che i due thread sono creati specificando un nome (alpha e beta, rispettivamente) per l'oggetto.


package Chap15;

public class MyRunnable implements Runnable {

public void run() {
try {
Thread.sleep(1000);
}
catch (InterruptedException ie) {
ie.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is running");
}

public static void main(String[] args) {
Runnable r1 = new MyRunnable();
Runnable r2 = new MyRunnable();

new Thread(null, r1, "alpha").start();
new Thread(null, r2, "beta").start();

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

Socket - un primo esempio

Da Head First Java O'Reilly, capitolo 15. Networking e threads

Il primo esempio sulle connessioni di rete é basato sulla costruzione di una semplice applicazione in cui il server attende un client, gli manda un messaggio - il consiglio del giorno - e poi termina l'esecuzione. Il client, dal canto suo, cerca di connettersi al server, legge il messaggio che questo gli manda, lo stampa e termina l'esecuzione.

Iniziamo a vedere il server.

Il cuore della classe é una istanza della classe ServerSocket, che viene creata per la porta 4242.

Sul socket server così creato chiamiamo il metodo accept(), che "appende" il server in attesa di una richiesta del client. Come il client si connette, il metodo accept() ritorna un socket, da cui estraiamo l'output stream che utilizziamo per creare un oggetto PrintWriter, su cui scriviamo il nostro messaggio.

Completato il nostro flusso d'esecuzione, chiudiamo il PrintWriter, operazione che implica la chiusura di tutti gli stream aperti.

Ecco il codice:

package Chap15;

import java.io.*;
import java.net.*;

public class DailyAdviceServer {

private String[] advices = {"Have fun", "Eat less", "No alcohol today"};

public DailyAdviceServer() {
try {
ServerSocket ss = new ServerSocket(4242);

System.out.println("Waiting for a client ...");
Socket socket = ss.accept();
PrintWriter pw = new PrintWriter(socket.getOutputStream());

String advice = advices[(int) (Math.random() * advices.length)];
pw.println(advice);
pw.close();
System.out.println("The advice sent is: " + advice);
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}

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


Il client funziona in modo speculare. Si crea un socket passandogli l'indirizzo IP della macchina su cui corre il server - nel nostro caso localhost - si crea un lettore sullo stream, e lo si bufferizza. Leggiamo una riga dal buffer, che sarà il testo che ci manda il server, chiudiamo il lettore e terminiamo l'esecuzione.

Questo il codice risultante:

package Chap15;

import java.io.*;
import java.net.Socket;

public class DailyAdviceClient {
public DailyAdviceClient() {
try {
Socket s = new Socket("localhost", 4242);
InputStreamReader isr = new InputStreamReader(s.getInputStream());
BufferedReader reader = new BufferedReader(isr);

System.out.println("Your today's advice: " + reader.readLine());
reader.close();
}
catch(IOException ioe) {
ioe.printStackTrace();
}
}

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

Leggere file di testo

Da Head First Java O'Reilly, capitolo 14.

Come é lecito attendersi, la lettura di un file di testo avviene in modo speculare rispetto alla sua scrittura.

Se per scrivere usiamo la classe FileWriter, per leggere usiamo la simmetrica FileReader.

Nell'esempio che segue vediamo anche l'uso delle classi che permettono di bufferizzare le operazioni di lettura/scrittura, mirando all'ottimizzazione dell'accesso alla memoria di massa.

Già che ci sono riscrivo anche la parte, vista nel post citato, in cui si scrive per mezzo di FileWriter, anche qui usando la bufferizzazione.

package Chap14;

import java.io.*;

public class StringManager {
private File f;

public StringManager(String fileName) {
f = new File(fileName);
}

public void write() {
try {
BufferedWriter bw = new BufferedWriter(new FileWriter(f));

for(int i = 0; i < 3; ++i)
bw.write("Hello!\n");
bw.close();
} catch(Exception ex) {
ex.printStackTrace();
}
}

public void read() {
try {
BufferedReader br = new BufferedReader(new FileReader(f));

String line;
while((line = br.readLine()) != null) {
System.out.println("read: " + line);
}
br.close();
} catch(Exception ex) {
ex.printStackTrace();
}
}

public static void main(String[] args) {
StringManager sm = new StringManager("hi.txt");

sm.write();
sm.read();
}
}

La classe java.io.File

Da Head First Java O'Reilly, capitolo 14.

La classe File ha lo scopo di permettere una facile gestione delle metainformazioni di un file e di mettere a disposizione alcune funzionalità di base, come la creazione di file e directory. Per accedere alle informazioni contenute nel file stesso occorre usare uno stream.

Alcune operazioni messe a disposizione da File:
  • mkdir(): creazione di una directory;
  • list(): ritorna il contenuto di una directory come array di stringhe;
  • getAbsolutePath(): il path assoluto di un file;
  • delete(): rimuove il file, ritorna true in caso di successo.

Scrivere testo semplice su file

Da Head First Java O'Reilly, capitolo 14.

Rendere persistente un oggetto in Java, usando il meccanismo della serializzazione é relativamente semplice. Scrivere stringhe di testo é ancora più semplice.

Tutto quello che occorre é un oggetto FileWriter e invocare il metodo write() su di esso.

A seguire, un piccolo esempio:

package Chap14;

import java.io.FileWriter;

public class StringWriter {
public static void main(String[] args) {
try {
FileWriter fw = new FileWriter("hi.txt");
fw.write("Hello!");
fw.close();
} catch(Exception ex) {
ex.printStackTrace();
}

}
}

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();
}
}