Java: Föreläsning 5

Innehåll i föreläsning 5

Flera trådar för I/O

I exemplet på nätverkskommunikation från föreläsning 4, med klasserna Client och Server, var både servern och klienten enkeltrådade: Lösningen är förstås att ha flera trådar: två trådar i klienten, och en tråd per klient i servern.

Trådad chat-server med trådad chat-klient

Kommentarerna om "förr" anger hur man skrev kod med ArrayList innan det fanns generiska datatyper, dvs när man använde "ArrayList" i stället för t ex "ArrayList<ClientThread>".

Filen MultiServer.java:

import java.net.ServerSocket;
import java.net.Socket;

import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.PrintWriter;
import java.io.IOException;

import java.util.ArrayList;
import java.util.Iterator;

class ClientThread extends Thread {
    private static int numberOfClients = 0;
    private static ArrayList<ClientThread> allClients = new ArrayList<ClientThread>();
    // Förr: private static ArrayList allClients = new ArrayList();

    private final int clientNumber = ++numberOfClients;
    private final Socket socket;
    private final BufferedReader in;
    private final PrintWriter out;

    public ClientThread(Socket s) throws IOException {
        socket = s;
        in = new BufferedReader(
                 new InputStreamReader(
                     socket.getInputStream()));
        out = new PrintWriter(
                  new BufferedWriter(
                      new OutputStreamWriter(
                          socket.getOutputStream())), true);
        // true: PrintWriter is line buffered

        System.out.println("Klienttråd " + clientNumber +
                           " skapad.");
        out.println("Välkommen. Du är klient nummer " +
                    clientNumber + ".");

        allClients.add(this);

        // If any of the above calls throw an 
        // exception, the caller is responsible for
        // closing the socket. Otherwise the thread
        // will close it.
        start(); // Starts the thread, and calls run()
    }

    public void run() {
        try {
            while (true) {
                String inline = in.readLine();
                System.out.println("Klienttråd " + clientNumber +
                                   " tog emot: " + inline);
                // Not: inline == "quit"
                if (inline == null || inline.equals("quit"))
                    break;
                out.println("Du sa '" + inline + "'");
                Iterator<ClientThread> i = allClients.iterator();
                // Förr: Iterator i = allClients.iterator();
                while (i.hasNext()) {
                    ClientThread t = i.next();
                    // Förr: ClientThread t = (ClientThread)i.next();
                    if (t != this)
                        t.out.println("Från klient " + clientNumber +
                                      ": " + inline);
                }
            }
            System.out.println("Klienttråd " + clientNumber +
                               ": Avslutar...");
        }
        catch(IOException e) {
            System.out.println("Klienttråd " + clientNumber +
                               ": I/O-fel");
        }
        finally {
            try {
                socket.close();
            }
            catch(IOException e) {
                System.out.println("Klienttråd " + clientNumber +
                                   ": Socketen ej stängd");
            }
            allClients.remove(allClients.indexOf(this));
        }
    } // run
} // class ClientThread

public class MultiServer {
    public static final int PORT = 2000;
    public static void main(String[] args) throws IOException {
        ServerSocket s = new ServerSocket(PORT);
        System.out.println("Server-socketen: " + s);
        System.out.println("Servern lyssnar...");

        try {
            while(true) {
                // Blocks until a connection occurs:
                Socket socket = s.accept();
                System.out.println("Uppkoppling accepterad.");
                System.out.println("Den nya socketen: " + socket);
                try {
                    ClientThread t = new ClientThread(socket);
                    System.out.println("Ny tråd skapad.");
                    System.out.println("Den nya tråden: " + t);
                }
                catch(IOException e) {
                    // If the constructor fails, close the socket,
                    // otherwise the thread will close it:
                    socket.close();
                }
            }
        }
        finally {
            s.close();
        }
    } // main
} // MultiServer
Filen Client2.java:
import java.net.Socket;
import java.net.InetAddress;

import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.PrintWriter;
import java.io.IOException;

final class ServerListener extends Thread {
    final private BufferedReader fromServer;

    public ServerListener(BufferedReader fromServer) {
        this.fromServer = fromServer;
    }

    public void run() {
        String lineFromServer;
        try {
            while ((lineFromServer = fromServer.readLine()) != null &&
                   !lineFromServer.equals("quit")) {
                System.out.println("Från servern: " + lineFromServer);
            }
        }
        catch (IOException e) {
            System.out.println("Undantag fångat: " + e);
        }
    }
} // class ServerListener

public class Client2 {
    public static final int PORT = 2000;
    public static void main(String[] args) throws IOException {
        InetAddress addr;
        if (args.length >= 1)
            addr = InetAddress.getByName(args[0]);
        else
            addr = InetAddress.getByName(null);

        Socket socket = new Socket(addr, PORT);
        System.out.println("Den nya socketen: " + socket);

        BufferedReader in = new BufferedReader(
            new InputStreamReader(socket.getInputStream()));
        PrintWriter out = new PrintWriter(
            new BufferedWriter(
                new OutputStreamWriter(
                    socket.getOutputStream())), true);
        // true: PrintWriter is line buffered

        BufferedReader kbd_reader = new BufferedReader(
            new InputStreamReader(System.in));

        ServerListener t = new ServerListener(in);
        t.start();

        String buf;
        while (true) {
            buf = kbd_reader.readLine();
            System.out.println("Från tangentbordet: " + buf);
            System.out.println("Till servern: " + buf);
            out.println(buf);
        }
    } // main
} // class Client2

Kort utvikning: Chatklient med fönster

Client3 är också en chatklient, likadan som Client2 ovan, men den har dessutom ett litet extra fönster med några knappar.

Som övning kan du flytta även utmatningen och inmatningen till det fönstret, så har du en grafisk chatklient!

Filen Client3.java:

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

final class ServerListener extends Thread {
    private final BufferedReader fromServer;

    public ServerListener(BufferedReader fromServer) {
        this.fromServer = fromServer;
    }

    public void run() {
        String lineFromServer;
        try {
            while ((lineFromServer = fromServer.readLine()) != null &&
                   !lineFromServer.equals("quit")) {
                System.out.println("Från servern: " + lineFromServer);
            }
        }
        catch (IOException e) {
            System.out.println("Undantag fångat: " + e);
        }
    }
} // class ServerListener

class ChatButtonWindow extends JFrame {
    private final PrintWriter toServer;

    public ChatButtonWindow(PrintWriter to) {
        super("Extra chat buttons");
        this.toServer = to;
        Container cp = getContentPane();
        cp.setLayout(new FlowLayout());
        JButton button1 = new JButton("Skicka förolämpning");
        cp.add(button1);
        button1.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    toServer.println("Ni, min herre, är en apa.");
                    System.out.println("Skickade en förolämpning.");
                }
            });

        JButton button2 = new JButton("Skicka beröm");
        cp.add(button2);
        button2.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    toServer.println("Du är bäst!");
                    System.out.println("Skickade beröm.");
                }
            });

        JButton button3 = new JButton("Avsluta");
        cp.add(button3);
        button3.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    System.out.println("Avslutar.");
                    System.exit(0);
                }
            });

        setSize(200, 200);
        setVisible(true);
    }
} // class ChatButtonWindow

public class Client3 {
    public static final int PORT = 2000;
    public static void main(String[] args) throws IOException {
        InetAddress addr;
        if (args.length >= 1)
            addr = InetAddress.getByName(args[0]);
        else
            addr = InetAddress.getByName(null);

        Socket socket = new Socket(addr, PORT);
        System.out.println("Den nya socketen: " + socket);

        BufferedReader in = new BufferedReader(
            new InputStreamReader(socket.getInputStream()));
        PrintWriter out = new PrintWriter(
            new BufferedWriter(
                new OutputStreamWriter(
                    socket.getOutputStream())), true);
        // true: PrintWriter is line buffered

        BufferedReader kbd_reader = new BufferedReader(
            new InputStreamReader(System.in));

        ServerListener t = new ServerListener(in);
        t.start();

        ChatButtonWindow w = new ChatButtonWindow(out);

        String buf;
        while (true) {
            buf = kbd_reader.readLine();
            System.out.println("Från tangentbordet: " + buf);
            System.out.println("Till servern: " + buf);
            out.println(buf);
        }
    } // main
} // class Client3

Kommunikationsprotokoll

Regler för hur de meddelanden som skickas mellan programmen ska se ut, och hur de ska tolkas.

Exempel 1:

Exempel 2:

Behållare

En behållare (engelska container) är ett objekt som kan innehålla andra objekt, till exempel en array eller en mängd.

I Java 1.2 ("Java 2") kom flera nya typer av behållare, bl a ArrayList, och i Java 1.5 infördes generics (generiska datatyper), som inte bara är behållare som kan innehålla vad som helst, utan som är behållare som innehåller en viss typ av objekt.

(En del svenska böcker kallar arrayer för "fält" eller "vektorer".)

Klassdiagram med med de viktigaste typerna av behållare (ur Bruce Eckels Thinking in Java, 3d Ed):

Behållardiagram för Java

Behållarna finns i paketet java.util:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.HashSet;
import java.util.TreeSet;
import java.util.HashMap;
import java.util.TreeMap;

Förr innehöll alla behållare (utom de inbyggda arrayerna) enbart Object, och det fungerar fortfarande att använda dem så, men numera kan man använda generics! Exempel: ArrayList<String>

Behållarna kan inte innehålla primitiva typer (int, float m fl) men mha auto-boxing och auto-unboxing funkar det ändå.

Ett testprogram med en massa behållare av olika slag: Datatest.java

Behållare: De inbyggda arrayerna

De inbyggda []-arrayerna är enklast, och ibland måste man använda dem, när de används av bibliotek och inbyggda mekanismer. Ett exempel är main-funktionens argument:
public static void main(String[] args) {
    for (int i = 0; i < args.length; ++i)
        System.out.println("Argument " + i + ": '" + args[i] + "'");
    }
}
Man måste skapa själva arrayen med new:
int[] intarray1 = new int[10];
int intarray2[] = new int[10];
String[] fruits = { "Äpple", "Päron", "Apelsin" };
int intarray[10]; // Ger kompileringsfel: ']' expected
fruits = new String[] { "Ananas", "Banan" };
Paketet java.util.Arrays innehåller några nyttiga funktioner, till exempel sort som sorterar en array:
java.util.Arrays.sort(fruits);
java.util.Arrays.fill(fruits, "Citron");
Man kan förstås också ha instanser av sina egna klasser i arrayerna. Antag att vi har en klass som heter Hamster:
Hamster[] ha1 =
    { new Hamster("Adam"), new Hamster("Bertil"), new Hamster("Cecar") };
for (int i = 0; i < ha1.length; ++i)
    System.out.println("ha1[" + i + "]: '" + ha1[i] + "'");
Utmatning, om vi har definierat en lämplig toString-metod i klassen Hamster:
ha1[0]: 'Hamster-Adam'
ha1[1]: 'Hamster-Bertil'
ha1[2]: 'Hamster-Cecar'
Man kan också allokera arrayen först, och sätta elementen efteråt:
Hamster[] ha2 = new Hamster[4];
ha2[0] = new Hamster("Anna");
ha2[1] = new Hamster("Beata");
ha2[2] = new Hamster("Cecilia");
ha2[3] = new Hamster("Dora");
Om man försöker adressera utanför arrayen, kastas ett ArrayIndexOutOfBoundsException:
try {
    ha2[4] = new Hamster("Eva");
}
catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("Undantag som fångades: '" + e + "'");
}
Arrayer är typsäkra. Det går inte att stoppa in fel sorts objekt:
ha2[2] = new Båt("Titanic"); // Ger kompileringsfel: "incompatible types"
Arrayerna har fix storlek, men man kan ändå "ändra storlek" på en array genom att skapa en ny (och kopiera elementen från den gamla arrayen):
System.out.println("ha2.length = " + ha2.length);
ha2 = new Hamster[7];
System.out.println("ha2.length = " + ha2.length);

En annan sorts "array": ArrayList

ArrayList är ett bra alternativ om man behöver en dynamisk array, dvs en array som kan växa och krympa. Från början är listan tom, och metoden add lägger till ett nytt element sist i listan. Metoden get hämtar elementet som finns på en viss position.
// ArrayList är ett bra alternativ om man behöver en dynamisk array
ArrayList<Båt> båtar = new ArrayList<Båt>();
Båt b1 = new Båt("Titanic");
Båt b2 = new Båt("Exxon Valdez");
Båt b3 = new Båt("Torrey Canyon");
båtar.add(b1);
båtar.add(b2);
båtar.add(b3);
båtar.add(b1);
båtar.add(b1);
Jämför med C++, och dess templates: vector<Boat>

En vanlig while-loop, till båtar.size():

System.out.println("Båtarna (1):");
for (int i = 0; i < båtar.size(); ++i)
    System.out.println("  båtar[" + i + "]: " + båtar.get(i));
Utmatning, om vi har definierat en lämplig toString-metod i klassen Båt:
Båtarna (1):
  båtar[0]: Båten Titanic
  båtar[1]: Båten Exxon Valdez
  båtar[2]: Båten Torrey Canyon
  båtar[3]: Båten Titanic
  båtar[4]: Båten Titanic
Vi la ju till båten Titanic flera gånger, så den förekommer förstås flera gånger i listan!

I stället för att stega fram ett heltal som anger vilken position vi vill titta på, och sen hämta elementet på den positionen med get, kan vi använda en Iterator. Metoden iterator returnerar en ny iterator, och i den finns metoderna hasNext och next.

System.out.println("Båtarna (2):");
Iterator<Båt> i1 = båtar.iterator();
while (i1.hasNext())
    System.out.println("  En båt: " + i1.next());
Oftast är det mer praktiskt att byta while-loopen mot en for-loop. Då kan man definiera iterator-variabeln så att den bara finns inuti själva loopen.
System.out.println("Båtarna (3):");
for (Iterator<Båt> i = båtar.iterator(); i.hasNext(); )
    System.out.println("  En båt: " + i.next());
Men "for-each-loopar" är ännu mer praktiskt:
System.out.println("Båtarna (4):");
for (Båt i : båtar) {
    System.out.println("  En båt: " + i);
}

Förr i världen (före 2004):

Före Java 1.5 fanns inte generics, utan bara "enkla" behållare, som innehåller Object vilka som helst:

ArrayList båtar = new ArrayList();
Såvitt Java-kompilatorn känner till, innehåller en sådan "otypad" ArrayList ju bara objekt av typen Object. Dels kan man stoppa in objekt av helt fel sorts typ. Dels kommer get och next, såvitt kompilatorn vet, bara att returnera Object, och inte något mer specifikt:
System.out.println("Båtarna:");
for (Iterator i = båtar.iterator(); i.hasNext(); ) {
    Båt b;
    // Ger kompileringsfel: "incompatible types"
    b = i.next();
    System.out.println("  En båt: " + b);
}
Man måste göra en explicit typkonvertering:
System.out.println("Båtarna:");
for (Iterator i = båtar.iterator(); i.hasNext(); ) {
    Båt b;
    b = (Båt)i.next();
    System.out.println("  En båt: " + b);
}
Observera att det inte är något objekt som konverteras. Vi bara talar om för Java-kompilatorn att "hördudu, det där Object som returneras av metoden next, det är faktiskt en Båt, så du kan lugnt lägga det i variabeln b".

När man försöker konvertera till den agivna typen, kan det kastas ett ClassCastException. Ett Object som verkligen är av typen Båt kan konverteras till Båt (jag talar ju bara om att den där saken där, det är faktiskt en båt), men en Hamster kan omöjligt konverteras till en Båt. (På vetenskapens nuvarande stadium.)

try {
    System.out.println("Båtarna:");
    for (Iterator i = båtar.iterator(); i.hasNext(); ) {
        Båt b;
        // Kommer att ge ClassCastException för hamstern Hasse
        b = (Båt)i.next();
        System.out.println("  En båt: " + b);
    }
}
catch (ClassCastException e) {
    System.out.println("Undantag som fångades: '" + e + "'");
}

Metoden indexOf returnerar positionen för (den första förekomsten av) ett objekt i listan, eller -1 om det inte fanns i listan.

Tänk på att den gör en likhetsjämförelse. Är en ny hamster som heter Hasse lika med den gamla hamstern som heter Hasse? Eller räknas två hamstrar bara som lika om de verkligen är samma hamster, dvs om de två variablerna refererar till samma objekt? Det beror på hur vi definierat klassen Hamster, och dess equals-metod. Om vi inte skrivit någon egen equals-metod, räknas två objekt som lika bara om de är samma objekt.

System.out.println("Hasses position (1): " + saker.indexOf(hasse));
System.out.println("Hasses position (2): " + saker.indexOf(new Hamster("Hasse")));
Vi kan ta bort element ur listan med hjälp av metoden remove, både genom att ange en position och genom att ange vilket objekt vi vill ta bort:
// Ta bort elementet på position fyra (dvs med index 3)!
båtar.remove(4);
// Ta bort (den första av förekomsterna av) Titanic
båtar.remove(b1);

En annan sorts "array": LinkedList

Både ArrayList och LinkedList implementerar gränssnittet List, så de kan göra precis samma saker. Men LinkedList är internt representerad som en länkad lista, och har därför annorlunda prestanda.
// En LinkedList är som en ArrayList, men med annorlunda prestanda
LinkedList<Båt> båtlista = new LinkedList<Båt>();
for (Iterator<Båt> i = båtar.iterator(); i.hasNext(); )
    båtlista.add(i.next());
båtlista.addAll(båtar); // Alla på en gång!

System.out.println("Båtarna i båtlistan:");
for (Iterator<Båt> i = båtlista.iterator(); i.hasNext(); )
    System.out.println("  En båt: " + i.next());

Mängder: HashSet

En mängd tillåter inga dubletter, och elementen har ingen bestämd ordning. (Jo, i en behållare ligger elementen i någon ordning, men den ordningen beror på implementationsdetaljer i mängdens interna datastrukturer, inte på i vilken ordning man lagt dit dem.)

Både HashSet och TreeSet implementerar gränssnittet Set, så de kan göra precis samma saker, men de har olika prestanda.

// En HashSet är en mängd, så den har inga dubletter
HashSet<Båt> båtmängd = new HashSet<Båt>();
for (Iterator<Båt> i = båtar.iterator(); i.hasNext(); )
    båtmängd.add(i.next());
båtmängd.addAll(båtar); // Alla på en gång!

System.out.println("Båtarna i båtmängden:");
for (Iterator<Båt> i = båtmängd.iterator(); i.hasNext(); )
    System.out.println("  En båt: " + i.next());

båtmängd.remove(b1);

System.out.println("Båtarna i båtmängden, nu utan Titanic:");
for (Iterator<Båt> i = båtmängd.iterator(); i.hasNext(); )
    System.out.println("  En båt: " + i.next());
Utmatning:
Båtarna i båtmängden:
  En båt: Båten Titanic
  En båt: Båten Exxon Valdez
  En båt: Båten Torrey Canyon
Båtarna i båtmängden, nu utan Titanic:
  En båt: Båten Exxon Valdez
  En båt: Båten Torrey Canyon
Eftersom elementen lagras i en hashtabell, kan de komma i vilken ordning som helst.

Och glöm inte den här varianten:

for (Telefonnummer i : telefonnummermängd1)
    System.out.println("  Ett telefonnummer: " + i);

En annan sorts mängd: TreeSet

Både HashSet och TreeSet implementerar gränssnittet Set, så de kan göra precis samma saker, men de har olika prestanda.

TreeSet lagrar internt sina data i form av ett träd, och den kräver därför att objekten implementerar gränssnittet Comparable, som säger att det ska finnas en compareTo-metod.

HashSet<Telefonnummer> telefonnummermängd1 = new HashSet<Telefonnummer>();
telefonnummermängd1.add(new Telefonnummer(116090));
telefonnummermängd1.add(new Telefonnummer(271010));
telefonnummermängd1.add(new Telefonnummer(271011));
telefonnummermängd1.add(new Telefonnummer(271012));
telefonnummermängd1.add(new Telefonnummer(111111));
telefonnummermängd1.add(new Telefonnummer(111111));
telefonnummermängd1.add(new Telefonnummer(111111));

System.out.println("Telefonnumren i telefonnummermängd 1:");
for (Iterator<Telefonnummer> i = telefonnummermängd1.iterator(); i.hasNext(); )
    System.out.println("  Ett telefonnummer: " + i.next());
Utmatning:
Telefonnumren i telefonnummermängd 2:
  Ett telefonnummer: +46-(0)19-111111
  Ett telefonnummer: +46-(0)19-116090
  Ett telefonnummer: +46-(0)19-271010
  Ett telefonnummer: +46-(0)19-271011
  Ett telefonnummer: +46-(0)19-271012
Eftersom det är ett träd, kommer nycklarna i ordning.

Mappning: HashMap

En "map" eller "mapping" kallas ibland "dictionary" eller "table". Man lägger in par av objekt: en nyckel och ett värde. Om man vet nyckeln går det sen snabbt att slå upp värdet.

En HashMap och TreeMap implementerar gränssnittet Map, så de kan göra precis samma saker.

// En "map" eller "mapping" kallas ibland "dictionary" eller "table".
HashMap<String, Telefonnummer> telefonbok1 = new HashMap<String, Telefonnummer>();
telefonbok1.put("Anna", new Telefonnummer(116090));
telefonbok1.put("Bengt", new Telefonnummer(224000));
telefonbok1.put("Conny", new Telefonnummer(125566));
telefonbok1.put("Doris", new Telefonnummer(171045));
telefonbok1.put("Eberhart", new Telefonnummer(111111));
telefonbok1.put("Eberhart", new Telefonnummer(222222));
telefonbok1.put("Eberhart", new Telefonnummer(333333));

System.out.println("Conny har nummer " + telefonbok1.get("Conny"));

System.out.println("Telefonbok 1:");
for (Iterator<String> i = telefonbok1.keySet().iterator(); i.hasNext(); ) {
    String namn = i.next();
    System.out.println("  " + namn + " har nummer " + telefonbok1.get(namn));
}

System.out.println("Telefonbok 1, igen:");
for (String i : telefonbok1.keySet()) {
    System.out.println("  " + i + " har nummer " + telefonbok1.get(i));
}
Utmatning:
Conny har nummer +46-(0)19-125566
Telefonbok 1:
  Doris har nummer +46-(0)19-171045
  Eberhart har nummer +46-(0)19-333333
  Conny har nummer +46-(0)19-125566
  Bengt har nummer +46-(0)19-224000
  Anna har nummer +46-(0)19-116090
Telefonbok 1, igen:
  Doris har nummer +46-(0)19-171045
  Eberhart har nummer +46-(0)19-333333
  Conny har nummer +46-(0)19-125566
  Bengt har nummer +46-(0)19-224000
  Anna har nummer +46-(0)19-116090
Eftersom det är en hashtabell, kan nycklarna komma i vilken ordning som helst.

En annan sorts mappning: TreeMap

En HashMap och TreeMap implementerar gränssnittet Map, så de kan göra precis samma saker, men de har olika prestanda.

TreeMap lagrar internt sina nycklar i form av ett träd, och den kräver därför att nyckelobjekten implementerar gränssnittet Comparable, som säger att det ska finnas en compareTo-metod.

// TreeMap kräver att nycklarna implementerar Comparable (och det gör String)
TreeMap<String, Telefonnummer> telefonbok2 = new TreeMap<String, Telefonnummer>();
telefonbok2.put("Anna", new Telefonnummer(116090));
Eftersom det är ett träd, kommer nycklarna i ordning när man går igenom en TreeMap.

Auto-boxing och auto-unboxing

Man kan inte ha behållare (förutom inbyggda arrayer) av primitiva datatyper:
// Ger kompileringsfel: "unexpected type"
ArrayList<int> talen = new ArrayList<int>();
Men det finns boxade ("lådade"?) varianter av de typerna, t ex Integer för int:
ArrayList<Integer> talen = new ArrayList<Integer>();

int tal1 = new Integer(1);
talen.add(tal1);
int tal2 = new Integer(1);
talen.add(tal2);
talen.add (new Integer(3));

// Auto-boxing
int tal4 = 4;
talen.add (tal4);
talen.add(5);

System.out.println("Talen:");
for (Integer i : talen)
    System.out.println("  Ett tal " + i);

// Auto-unboxing
System.out.println("Talen, igen:");
for (int i : talen)
    System.out.println("  Ett tal " + i);


Thomas Padron-McCarthy (thomas.padron-mccarthy@tech.oru.se), 23 november 2007