Filen MultiServer.java:
Filen Client2.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
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
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
Exempel 1:
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.
Klassdiagram med med de viktigaste typerna av behållare (ur Bruce Eckels Thinking in Java, 3d Ed):
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
Man måste skapa själva arrayen med new:public static void main(String[] args) { for (int i = 0; i < args.length; ++i) System.out.println("Argument " + i + ": '" + args[i] + "'"); } }
Paketet java.util.Arrays innehåller några nyttiga funktioner, till exempel sort som sorterar en array: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" };
Man kan förstås också ha instanser av sina egna klasser i arrayerna. Antag att vi har en klass som heter Hamster:java.util.Arrays.sort(fruits); java.util.Arrays.fill(fruits, "Citron");
Utmatning, om vi har definierat en lämplig toString-metod i klassen 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] + "'");
Man kan också allokera arrayen först, och sätta elementen efteråt:ha1[0]: 'Hamster-Adam' ha1[1]: 'Hamster-Bertil' ha1[2]: 'Hamster-Cecar'
Om man försöker adressera utanför arrayen, kastas ett ArrayIndexOutOfBoundsException: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");
Arrayer är typsäkra. Det går inte att stoppa in fel sorts objekt:try { ha2[4] = new Hamster("Eva"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Undantag som fångades: '" + e + "'"); }
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):ha2[2] = new Båt("Titanic"); // Ger kompileringsfel: "incompatible types"
System.out.println("ha2.length = " + ha2.length); ha2 = new Hamster[7]; System.out.println("ha2.length = " + ha2.length);
Jämför med C++, och dess templates: vector<Boat>// 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);
En vanlig while-loop, till båtar.size():
Utmatning, om vi har definierat en lämplig toString-metod i klassen Båt: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));
Vi la ju till båten Titanic flera gånger, så den förekommer förstås flera gånger i listan!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
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.
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 (2):"); Iterator<Båt> i1 = båtar.iterator(); while (i1.hasNext()) System.out.println(" En båt: " + i1.next());
Men "for-each-loopar" är ännu mer praktiskt:System.out.println("Båtarna (3):"); for (Iterator<Båt> i = båtar.iterator(); i.hasNext(); ) System.out.println(" En båt: " + i.next());
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: 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:ArrayList båtar = new ArrayList(); Man måste göra en explicit typkonvertering: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); } 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".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); } 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.
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:System.out.println("Hasses position (1): " + saker.indexOf(hasse)); System.out.println("Hasses position (2): " + saker.indexOf(new Hamster("Hasse")));
// 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 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());
Både HashSet och TreeSet implementerar gränssnittet Set, så de kan göra precis samma saker, men de har olika prestanda.
Utmatning:// 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());
Eftersom elementen lagras i en hashtabell, kan de komma i vilken ordning som helst.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
Och glöm inte den här varianten:
for (Telefonnummer i : telefonnummermängd1) System.out.println(" Ett telefonnummer: " + i);
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.
Utmatning: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());
Eftersom det är ett träd, kommer nycklarna i ordning.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
En HashMap och TreeMap implementerar gränssnittet Map, så de kan göra precis samma saker.
Utmatning:// 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)); }
Eftersom det är en hashtabell, kan nycklarna komma i vilken ordning som helst.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
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.
Eftersom det är ett träd, kommer nycklarna i ordning när man går igenom en TreeMap.// 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));
Men det finns boxade ("lådade"?) varianter av de typerna, t ex Integer för int:// Ger kompileringsfel: "unexpected type" ArrayList<int> talen = new ArrayList<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);