Java: Lösningar till tentamen 2008-01-14

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. En del av lösningarna är kanske inte fullständiga, utan hänvisar bara till var man kan läsa svaret.

Uppgift 1 (25 p)

a) (2p)

Klienten skickar Servern svarar Servern svarar vid fel
plus
tal
tal
tal Error!
minus
tal
tal
tal Error!
gånger
tal
tal
tal Error!
delat
tal
tal
tal Error!
kvadrat
tal
tal Error!
kvadratrot
tal
tal Error!

Kommunikationen sker som text. tal är ett flyttal utskrivet som text, i vanligt Java-format.

Man kan också ha en dialog vid uppkopplingen (till exmepel "hello", "ok"), och vid nerkopplingen ("done", "bye"), men det har jag struntat i här.

Programkoden blir enklare om man alltid skickar två tal, alltså även för kvadrat- och kvadratrotsoperationerna.

b) (10p)

Uppgiften blir mycket enklare om klienten kopplar upp sig på nytt mot servern för varje beräkning som ska utföras, och sen direkt kopplar ner förbindelsen, men här låter vi klienten behålla sin uppkoppling. Det betyder att servern måste vara flertrådad.

Klienten behöver i vilket fall som helst inte vara trådad.

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

class Knapp extends JButton {
    private String kommando;
    private int antal_argument;
    public Knapp(String text, String kommando, int antal_argument, ActionListener lyssnare) {
        super(text);
        this.kommando = kommando;
        this.antal_argument = antal_argument;
        addActionListener(lyssnare);
    }
    public String getKommando() { return kommando; }
    public int getAntalArgument() { return antal_argument; }
}

public class KalkKlient extends JFrame implements ActionListener {
    private Socket socket;
    private BufferedReader in;
    private PrintWriter out;
    BufferedReader kbd_reader;

    private JLabel label1 = new JLabel("Ena talet:");
    private JTextField text1 = new JTextField(10);
    private JLabel label2 = new JLabel("Andra talet: ");
    private JTextField text2 = new JTextField(10);
    private JLabel label3 = new JLabel("Resultat: ");
    private JTextField text3 = new JTextField(10);
    private Knapp plusknappen = new Knapp("+", "plus", 2, this);
    private Knapp minusknappen = new Knapp("-", "minus", 2, this);
    private Knapp gångerknappen = new Knapp("*", "gånger", 2, this);
    private Knapp delatknappen = new Knapp("/", "delat", 2, this);
    private Knapp rotknappen = new Knapp("Roten", "kvadratrot", 1, this);
    private Knapp kvadratknappen = new Knapp("Kvadraten", "kvadrat", 1, this);

    public KalkKlient() throws IOException, UnknownHostException {
        JFrame frame = new JFrame("KalkKlient");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 200);

        Container cp = frame.getContentPane();
        cp.setLayout(new FlowLayout());

        cp.add(label1);
        cp.add(text1);
        cp.add(label2);
        cp.add(text2);
        cp.add(label3);
        cp.add(text3);
        cp.add(plusknappen);
        cp.add(minusknappen);
        cp.add(gångerknappen);
        cp.add(delatknappen);
        cp.add(rotknappen);
        cp.add(kvadratknappen);

        frame.setVisible(true);
        InetAddress addr = InetAddress.getByName("mathserver.oru.se");
        Socket socket = new Socket(addr, 2000);
        System.out.println("Den nya socketen: " + socket);
        in = new BufferedReader(
            new InputStreamReader(
                socket.getInputStream()));
        out = new PrintWriter(
            new BufferedWriter(
                new OutputStreamWriter(
                    socket.getOutputStream())), true);
        // true: PrintWriter is line buffered

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

    public void actionPerformed(ActionEvent event) {
        Knapp knappen = (Knapp)event.getSource();
        String kommando = knappen.getKommando();
        out.println(kommando);
        int antal_argument = knappen.getAntalArgument();
        out.println(text1.getText());
        if (antal_argument == 2)
            out.println(text2.getText());
        try {
            String resultat = in.readLine();
            text3.setText(resultat);
        }
        catch (IOException e) {
            text3.setText("Nätverksfel!");
        }
    } // actionPerformed

    public static void main(String[] args) throws IOException, UnknownHostException {
        KalkKlient klienten = new KalkKlient();
    }
} // class KalkKlient

c) (12p)

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 Klienttråd extends Thread {
    private final Socket socket;
    private final BufferedReader in;
    private final PrintWriter out;

    public Klienttråd(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 skapad.");

        // 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 kommando = in.readLine();
                System.out.println("Klienttråd tog emot: " + kommando);
                if (kommando == null) {
                    break;
                }
                try {
                    if (kommando.equals("kvadrat")) {
                        String arg1 = in.readLine();
                        Double tal1 = Double.parseDouble(arg1);
                        Double resultat = tal1 * tal1;
                        out.println("" + resultat);
                    }
                    else if (kommando.equals("kvadratrot")) {
                        String arg1 = in.readLine();
                        Double tal1 = Double.parseDouble(arg1);
                        if (tal1 < 0) {
                            out.println("Error!");
                        }
                        else {
                            Double resultat = Math.sqrt(tal1);
                            out.println("" + resultat);
                        }
                    }
                    else if (kommando.equals("plus")) {
                        String arg1 = in.readLine();
                        String arg2 = in.readLine();
                        Double tal1 = Double.parseDouble(arg1);
                        Double tal2 = Double.parseDouble(arg2);
                        Double resultat = tal1 + tal2;
                        out.println("" + resultat);
                    }
                    else if (kommando.equals("minus")) {
                        String arg1 = in.readLine();
                        String arg2 = in.readLine();
                        Double tal1 = Double.parseDouble(arg1);
                        Double tal2 = Double.parseDouble(arg2);
                        Double resultat = tal1 - tal2;
                        out.println("" + resultat);
                    }
                    else if (kommando.equals("gånger")) {
                        String arg1 = in.readLine();
                        String arg2 = in.readLine();
                        Double tal1 = Double.parseDouble(arg1);
                        Double tal2 = Double.parseDouble(arg2);
                        Double resultat = tal1 * tal2;
                        out.println("" + resultat);
                    }
                    else if (kommando.equals("delat")) {
                        String arg1 = in.readLine();
                        String arg2 = in.readLine();
                        Double tal1 = Double.parseDouble(arg1);
                        Double tal2 = Double.parseDouble(arg2);
                        if (tal2 == 0) {
                            out.println("Error!");
                        }
                        else {
                            Double resultat = tal1 / tal2;
                            out.println("" + resultat);
                        }
                    }
                    else {
                        out.println("Huh?");
                    }
                }
                catch (NumberFormatException e) {
                    out.println("Error!");
                }
            }
            System.out.println("Klienttråd: Avslutar...");
        }
        catch(IOException e) {
            System.out.println("Klienttråd: I/O-fel");
        }
        finally {
            try {
                socket.close();
            }
            catch(IOException e) {
                System.out.println("Klienttråd: Socketen ej stängd");
            }
        }
    } // run
} // class Klienttråd

public class KalkServer {
    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 {
                    Klienttråd t = new Klienttråd(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
} // KalkServer

d) (1p)

Man kan lika gärna göra uträkningen direkt, i klientprogrammet, utan att blanda in någon server eller nätverkskommunikation. Det blir mycket enklare programkod, och går sådär en miljon gånger snabbare.

Uppgift 2 (10 p)

abstract class Figur {

} // class Figur

class Punkt extends Figur {
    private double x, y;
    public Punkt(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public boolean equals(Object o) {
        if (!(o instanceof Punkt))
            return false;
        Punkt rhs = (Punkt)o;
        return this.x == rhs.x && this.y == rhs.y;
    }
} // class Punkt

class Sträcka extends Figur {
    private Punkt start, slut;
    public Sträcka(double x1, double y1, double x2, double y2) {
        start = new Punkt(x1, y1);
        slut = new Punkt(x2, y2);
    }
    public Sträcka(Punkt start, Punkt slut) {
        this.start = start;
        this.slut = slut;
    }
    public boolean equals(Object o) {
        if (!(o instanceof Sträcka))
            return false;
        Sträcka rhs = (Sträcka)o;
        return this.start.equals(rhs.start) && this.slut.equals(rhs.slut);
    }
} // class Sträcka

class Triangel extends Figur {
    private Punkt p1, p2, p3;
    public Triangel(double x1, double y1, double x2, double y2, double x3, double y3) {
        p1 = new Punkt(x1, y1);
        p2 = new Punkt(x2, y2);
        p3 = new Punkt(x3, y3);
    }
} // class Triangel

Uppgift 3 (5 p)

a) (1p) Här är ett problem med multiplet arv:
class Läkare {
    int ålder;
    int examensår;
    public void operera() { /*...*/ }
}

class Ingenjör {
    int ålder;
    int examensår;
    public void konstruera() { /*...*/ }
}

class LäkareOchIngenjör extends Läkare, Ingenjör {
    
}
En LäkareOchIngenjör är både Läkare och Ingenjör, och klassen LäkareOchIngenjör ärver därför egenskaperna ålder och examensår två gånger, från Läkare respektive Ingenjör. Hur ska dessa kollisoner hanteras? Hur många åldrar och examensår ska en LäkareOchIngenjör ha?

Med ålder är det naturligt att slå ihop de två åldrarna till en enda variabel, för en person har bara en enda ålder, och den är samma oavsett vilket yrke han just nu utövar. Men en person som är både läkare och ingenjör har (förmodligen) två olika examensår, så examensåren ska inte slås ihop till en enda variabel. Det är svårt för kompilatorn att förstå skillnaden.

Det finns fler problem som man kan ta upp:

b) (3p)

Man använder gränssnitt, på engelska interface. I stället för två klasser Läkare och Ingenjör, som LäkareOchIngenjör ärver, skapar vi de två gränssnitten Läkare och Ingenjör, som bara talar om vilka metoder en läkare respektive ingenjör måste implementera, och sen låter vi klassen LäkareOchIngenjör implementera dessa båda gränssnitt:

interface Läkare {
    public void operera();
}

interface Ingenjör {
    public void konstruera();
}

class LäkareOchIngenjör implements Läkare, Ingenjör {
    int ålder;
    int årtal_för_läkarexamen;
    int årtal_för_ingenjörsexamen;
    public void operera() { /*...*/ }
    public void konstruera() { /*...*/ }
}

public class Gränssnittstest {
    public static void main(String[] args) {
        Ingenjör ing = new LäkareOchIngenjör();
        Läkare läk = new LäkareOchIngenjör();
    } // main
} // class Gränssnittstest
c) (1p)

Eftersom gränssnitten inte innehåller några (vanliga) variabler, kan det inte bli några krockar mellan variabler med samma namn.

(Man kan förstås argumentera för att vi inte alls löst problemet, utan bara förhindrat symtomen genom att man inte alls kan göra det som vi ville göra i deluppgift a. Men problemet uppstår i alla fall inte längre.)


Thomas Padron-McCarthy (Thomas.Padron-McCarthy@tech.oru.se) 3 februari 2008