Java: Lösningar till tentamen 2004-12-16

Observera att detta är förslag på lösningar. Det kan finnas andra lösningar som också är korrekta, och det kan hända att en del av lösningarna är mer omfattande än vad som krävs för full poäng på uppgiften.

Uppgift 1 (3p)

a) (1p)

Den virtuella maskinen är inte någon fysisk maskin, det vill säga en maskin som man kan ta på, utan den är ett program som läser Java-maskinkoden och sen gör det som den maskinkoden, enligt Java-specifikationen, ska göra. Därför kan samma kompilerade Java-program köras på olika processortyper och olika operativsystem.

Rättning: Ett svar som går att tolka som att den virtuella maskinen består av hårdvara, till exempel ett extra kretskort som man monterar i datorn, ger högst en halv poäng. (Det går i och för sig att bygga en JVM i hårdvara, men det är inte det vanliga sättet.)

b) (2p)

Fördelar:

Nackdelar:

Rättning: Det räcker med en (bra) fördel och en (bra) nackdel för att få full poäng.

Uppgift 2 (5p)

a) (2p)

class Mask {
    private final int längd;
    private static Mask längstaMasken = null;
    public Mask(int längd) {
        this.längd = längd;
        if (längstaMasken == null || längstaMasken.längd < this.längd)
            längstaMasken = this;
    }
} // Mask

b) (3p)

class ÄppletRedanTomtException extends Exception { }

class ÄppletRedanFulltException extends Exception { }

class Äpple { 
    private Mask mask = null;
    public Äpple() { }
    public Äpple(Mask masken) {
        this.mask = masken;
    }
    public void stoppaInEnMask(Mask masken)
        throws ÄppletRedanFulltException {
        if (this.mask != null)
            throw new ÄppletRedanFulltException();
        this.mask = masken;
    }
    public void taBortMasken()
        throws ÄppletRedanTomtException {
        if (this.mask == null)
            throw new ÄppletRedanTomtException();
        this.mask = null;
    }
} // Äpple

Uppgift 3 (4p)

Skapar en X(1).
Skapar en XYZ(1, 2, 3).
Skapar en X(0).
Skapar en X().
Skapar en X(4).
Skapar en XYZ(4, 5, 6).
Skapar en X(0).
Skapar en X().
Skapar en XYZ(17, 19).
Uppgift3.f
X.f
XYZ.f
Uppgift3.g
XYZ.f

Uppgift 4 (15p)

import javax.swing.JApplet;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;

import java.net.InetAddress;
import java.net.Socket;

import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.Iterator;

public final class TempApplet extends JApplet implements ActionListener {
    private static final int PORT = 3003;
    private JLabel etikett = new JLabel("De senaste temperaturerna:");
    private JTextArea tempfönster = new JTextArea(8, 20);
    private JButton knapp = new JButton("Rensa");

    public void init() {
        try {
            Container cp = getContentPane();
            cp.setLayout(new FlowLayout());
            cp.add(etikett);
            cp.add(tempfönster);
            cp.add(knapp);
            knapp.addActionListener(this);
            InetAddress addr = InetAddress.getByName("localhost");
            Socket socket = new Socket(addr, PORT);
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            ServerListener t = new ServerListener(in, tempfönster);
            t.start();
        }
        catch (IOException e) {
            tempfönster.append("Det har upppstått ett fel.");
        }
    }

    public void actionPerformed(ActionEvent e) {
        tempfönster.setText("");
    }
} // class TempApplet

final class ServerListener extends Thread {
    private final BufferedReader fromServer;
    private final JTextArea utfönster;
    private final HashMap tabellen;

    public ServerListener(BufferedReader fromServer, JTextArea utfönster) {
        this.fromServer = fromServer;
        this.utfönster = utfönster;
        this.tabellen = new HashMap();
    }

    private class SetText implements Runnable {
        private String text;
        public SetText(String text) {
            this.text = text;
        }
        public void run() {
            utfönster.setText(text);
        }
    }

    private class AppendText implements Runnable {
        private String text;
        public AppendText(String text) {
            this.text = text;
        }
        public void run() {
            utfönster.append(text);
        }
    }

    public void run() {
        String lineFromServer;  
        try {
            while ((lineFromServer = fromServer.readLine()) != null) {
                // Warning: This fails for location names that contain spaces.
                StringTokenizer tokenizer = new StringTokenizer(lineFromServer, " ");
                String ort = tokenizer.nextToken();
                String temperatur = tokenizer.nextToken();
                tabellen.put(ort, temperatur);
                SwingUtilities.invokeLater(new SetText(""));
                for (Iterator i = tabellen.keySet().iterator(); i.hasNext(); ) {
                    String o = (String)i.next();
                    SwingUtilities.invokeLater(new AppendText(o + " " + tabellen.get(o) + "\n"));
                }
            }
            SwingUtilities.invokeLater(new AppendText("Servern har kopplat ner.\n"));
        }
        catch (IOException e) {
            SwingUtilities.invokeLater(new AppendText("Det har upppstått ett fel."));
        }
    }
} // class ServerListener

Rättning:

Uppgift 5 (5p)

a) (2p)

En applet är avsedd att köras inuti en webbläsare, som en del av en webbsida. En applikation är ett fristående program. (Dock kräver applikationen, precis som appleten, att det finns en Java-maskin installerad på datorn.)

När en applet ska startas (av webbläsaren) skapas det först en instans av applet-klassen, och sen anropas metoden init i den instansen. När en applikation ska startas skapas det inte automatiskt någon instans, utan den statiska metoden main anropas.

Eftersom appletar är tänkta att kunna användas för att till exempel göra saker som blinkar oc h tutar på webbsidor, och då laddas ner och startas automatiskt av webbläsaren utan att användaren fattat något beslut om att starta något program, måste de vara säkra. Därför finns det begränsningar på vad appletar får göra. Till exempel får en applikation läsa och skriva lokala filer på datorn, men det får inte appleten göra.

b) (2p)

Genom att lägga till en main-metod, mest praktiskt i själva applet-klassen, som skapar ett fönster att visa appleten i, som skapar en instans av appleten, och som anropar init. Så här kan en main-metod se ut som man kan lägga in i appleten i uppgift 1:

    public static void main(String[] args) {
        TempApplet applet = new TempApplet();
        JFrame frame = new JFrame("TempApplet");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(applet);
        frame.setSize(250, 200);
        applet.init();
        applet.start();
        frame.setVisible(true);
    }

c) (1p)

Man kan underlätta felsökningen genom att skriva ut spårutskrifter, på System.out eller på en loggfil som man skapar för det ändamålet, vilket inte går från en applet. Om appleten ska ansluta till en dator via Internet, behöver man inte ha appleten liggande på just den datorn. (Kom ihåg att en applet bara får ansluta till samma dator som appleten själv hämtades från.) Man slipper också skriva html-koden för att visa appleten. Man slipper använda en webbläsare eller speciell appletviewer.

Uppgift 6 (10p)

Man startar servern som en applikation, och en eller flera appletar kan sen koppla upp sig mot den. Serven läser inmatning från standardinmatningen, det vill säga för det mesta tangentbordet, och skickar vidare varje rad till alla uppkopplade appletar. Genom att skriva in olika kombinationer av temperaturdata i serven, kan man provköra appleten och testa dess funktion.

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 InputThread extends Thread {
    BufferedReader in;

    public InputThread() {
        in = new BufferedReader(new InputStreamReader(System.in));
        start();
    }

    public void run() {
        try {
            while (true) {
                String inline = in.readLine();
                if (inline == null || inline.equals("quit"))
                    break;
                System.out.println("Du sa '" + inline + "'");
                ClientConnection.sendToAllClients(inline);
            }
        }
        catch(IOException e) {
            System.out.println("InputThread: " + e);
        }
    } // run
} // class InputThread

class ClientConnection {
    private static ArrayList allClients = new ArrayList();
    private final Socket socket;
    private final PrintWriter out;

    public ClientConnection(Socket s) throws IOException {
        socket = s;
        out = new PrintWriter(
                  new BufferedWriter(
                      new OutputStreamWriter(
                          socket.getOutputStream())), true);
        System.out.println("En klienttråd har skapats.");
        allClients.add(this);
    }
    public static void sendToAllClients(String line) {
        Iterator i = allClients.iterator();
        while (i.hasNext()) {
            ClientConnection t = (ClientConnection)i.next();
            t.out.println(line);
        }
    }
} // class ClientConnection

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

        InputThread it = new InputThread();

        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 {
                    ClientConnection cc = new ClientConnection(socket);
                    System.out.println("Ny klientkoppling skapad.");
                    System.out.println("Den nya klientkopplingen: " + cc);
                }
                catch(IOException e) {
                    // If the constructor fails, close the socket,
                    // otherwise the thread will close it:
                    socket.close();
                }
            }
        }
        finally {
            s.close();
        }
    } // main
} // Temperaturserver

Notera att det inte behövs någon tråd för varje klient. Klienten skickar ju inte några förfrågningar till servern, så det finns inget att ligga och vänta på. Däremot krävs två trådar (dvs en extra, förutom main-tråden): en som väntar på uppkopplingar från nya klienter med accept, och en som väntar på inmatning från tangentbordet.

Här är en alternativ lösning på uppgift 6, som använder sig av ett fönster i stället för att läsa från tangentbordet. En grafisk lösning blir oftast mindre flexibel än en textbaserad, och det här är inget undantag, men många trivs trots det bättre med fönster än med text. Med Java är det inte krångligare att skriva den grafiska lösningen än den textbaserade.

GrafiskServer

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

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

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

import java.awt.Container;
import java.awt.FlowLayout;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.JButton;

class ClientConnection {
    private static ArrayList allClients = new ArrayList();
    private final Socket socket;
    private final PrintWriter out;

    public ClientConnection(Socket s) throws IOException {
        socket = s;
        out = new PrintWriter(
                  new BufferedWriter(
                      new OutputStreamWriter(
                          socket.getOutputStream())), true);
        System.out.println("En klienttråd har skapats.");
        allClients.add(this);
    }
    public static void sendToAllClients(String line) {
        Iterator i = allClients.iterator();
        while (i.hasNext()) {
            ClientConnection t = (ClientConnection)i.next();
            t.out.println(line);
        }
    }
} // class ClientConnection

public class GrafiskServer {
    public static final int PORT = 3003;

    public static void main(String[] args) throws IOException {
        JFrame frame = new JFrame("GrafiskServer");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container cp = frame.getContentPane();
        cp.setLayout(new FlowLayout());
        final JTextField textfield = new JTextField("Kilpisjärvi -41");
        JButton button = new JButton("Skicka");
        button.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    String inline = textfield.getText();
                    System.out.println("Du sa '" + inline + "'");
                    ClientConnection.sendToAllClients(inline);
                }
            });
        cp.add(textfield);
        cp.add(button);
        frame.pack();
        frame.setVisible(true);

        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 {
                    ClientConnection cc = new ClientConnection(socket);
                    System.out.println("Ny klientkoppling skapad.");
                    System.out.println("Den nya klientkopplingen: " + cc);
                }
                catch(IOException e) {
                    // If the constructor fails, close the socket,
                    // otherwise the thread will close it:
                    socket.close();
                }
            }
        }
        finally {
            s.close();
        }
    } // main
} // GrafiskServer

Thomas Padron-McCarthy (Thomas.Padron-McCarthy@tech.oru.se) 26 januari 2005