Java: Lösningar till tentamen 2004-08-17

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 (5p)

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

public class PlusMinusApplet extends JApplet implements ActionListener {
    private JTextField text = new JTextField(10);
    private JButton plusButton = new JButton("Plus");
    private JButton minusButton = new JButton("Minus");

    public void actionPerformed(ActionEvent event) {
	String s = text.getText();
	try {
	    int num = Integer.parseInt(s);
	    JButton button = (JButton)event.getSource();
	    if (button == plusButton)
		++num;
	    else if (button == minusButton)
		--num;
	    text.setText(num + "");
	}
	catch (NumberFormatException exc) {
	    text.setText("ERROR");
	}
    } // actionPerformed

    public void init() {
        plusButton.addActionListener(this);
        minusButton.addActionListener(this);
        Container cp = getContentPane();
        cp.setLayout(new FlowLayout());
        cp.add(text);
        cp.add(plusButton);
        cp.add(minusButton);
    }
} // class PlusMinusApplet

Uppgift 2 (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 och 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) {
        PlusMinusApplet applet = new PlusMinusApplet();
        JFrame frame = new JFrame("PlusMinusApplet");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(applet);
        frame.setSize(160, 85);
        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 3 (4p)

Skapar ett fordon...
Fordonet kör en bit.
Skapar ett fordon...
Skapar en bil...
Fordonet kör en bit.
Skapar ett fordon...
Skapar en bil...
Bilen kör 14.0 meter.
Skapar ett fordon...
Skapar en bil...
Fordonet kör 14.0 meter.

Uppgift 4 (2p)

    public static int length(ListElement p) {
	int n = 0;
	while (p != null) {
	    p = p.next;
	    ++n;
	}
	return n;
    }

Uppgift 5 (4p)

a) (2p)

Det finns flera olika sorters "arrayer" i Java, men om vi antar att vi menar de inbyggda, enkla arrayerna, som anges med hakklamrar, så kan metoden skrivas så här:

    public static int sum(int[] a) {
	int result = 0;
	for (int i = 0; i < a.length; ++i)
	    result += a[i];
	return result;
    }
Om vi i stället använder ArrayList, kommer metoden att se ut så här. Det blir lite krångligare, för vi kan inte lagra vanliga heltal (int) i en ArrayList, för den kan bara innehålla instanser av Object. int är inte ett Object. Därför måste vi "packa in" ("wrappa") heltalen i objekt av klassen Integer:
    public static int sum(ArrayList a) {
	int result = 0;
	for (int i = 0; i < a.size(); ++i)
	    result += ((Integer)a.get(i)).intValue();
	return result;
    }

b) (1p)

En static-deklarerad metod körs utan att det finns något this-objekt som den arbetar med.

En vanlig, icke static-deklarerad Java-metod arbetar med ett visst objekt, och den har tillgång till attributen (dvs variablerna) i det objektet. Titta till exempel på klassen Rektangel:

class Rektangel {
    private double höjd, bredd;
    public Rektangel(int höjd, int bredd) {
	this.höjd = höjd;
	this.bredd = bredd;
    }
    public double area() {
	return bredd * höjd;
    }
} // class Rektangel
En rektangel har en höjd och en bredd. En instans av klassen Rektangel representerar en rektangel, och en höjd och en bredd lagras i instansen, som värden på medlemsvariablerna höjd och bredd. Metoden area räknar ut arean genom att hämta "det objektets", alltså this-objektets, variabelvärden.

Notera att metoden area också kan skrivas så här, vilket säger precis samma sak: b

    public double area() {
	return this.bredd * this.höjd;
    }

Här är ett exempel som använder Rektangel-klassen. Först skapar vi en rektangel r med höjden 5 och bredden 8, och anropet r.area() kommer då att ge arean på den rektangeln, nämligen 40.

Rektangel r = new Rektangel(5, 8);
System.out.println("area = " + r.area());

I en static-deklarerad metod finns det inget this-objektet när metoden körs. Följande area-metod ger kompileringsfel, för det finns ju inga variabler höjd och bredd att hämta värden på:

    public static double area() {
	return bredd * höjd;
    }

Om man vill göra area-metoden static-deklarerad, måste man skicka med de värden som behövs:

    public static double area(double bredd, double höjd) {
	return bredd * höjd;
    }
En vanlig Java-metod gör alltså något med ett visst objekt, medan en static-deklarerad metod gör något med data som man skickar med som parametrar.

En vanlig Java-metod kräver att det finns en instans av den klassen för att man ska kunna anropa metoden (för det måste finnas ett this-objekt), men det behövs inte för en static-deklarerad metod.

c) (1p)

Jag tycker att det kan vara bra att static-deklarera sum, för metoden är ju inte en medlemsmetod i en arrayklass och arbetar på den arrayen, utan den får arrayen den ska jobba med skickad till sig som parameter.

Uppgift 6 (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.HashMap;

class ClientThread extends Thread {
    private final Socket socket;
    private final HashMap table;
    private final BufferedReader in;
    private final PrintWriter out;

    public ClientThread(Socket s, HashMap t) throws IOException {
	socket = s;
	table = t;
	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()
    }

    private synchronized void store(int key, int value) {
	table.put(new Integer(key), new Integer(value));
    }

    class NoSuchKey extends Exception { }

    private synchronized int retrieve(int key) throws NoSuchKey {
	Integer valueObject = (Integer)table.get(new Integer(key));
	if (valueObject == null)
	    throw new NoSuchKey();
	return valueObject.intValue();
    }

    public void run() {
	try {
	    while (true) {
		String inline = in.readLine();
		System.out.println("Klienttråd tog emot: " + inline);
		if (inline == null) {
		    break;
		}
		else if (inline.equals("LAGRA")) {
		    String keyLine = in.readLine();
		    String valueLine = in.readLine();
		    try {
			int key = Integer.parseInt(keyLine);
			int value = Integer.parseInt(valueLine);
			store(key, value);
			out.println("OK");
		    }
		    catch (NumberFormatException exc) {
			out.println("NOK");
		    }   
		}
		else if (inline.equals("HÄMTA")) {
		    String keyLine = in.readLine();
		    try {
			int key = Integer.parseInt(keyLine);
			int value = retrieve(key);
			out.println(value);
		    }
		    catch (NumberFormatException exc) {
			out.println("NOK");
		    }   
		    catch (NoSuchKey exc) {
			out.println("NOK");
		    }   
		}
		else {
		    out.println("NOK");
		}
	    }
	    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 ClientThread

public class TableServer {
    public static final int PORT = 2000;

    public static void main(String[] args) throws IOException {
	ServerSocket s = new ServerSocket(PORT);
	HashMap table = new HashMap();

	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, table);
		    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
} // TableServer


Thomas Padron-McCarthy (Thomas.Padron-McCarthy@tech.oru.se) 18 augusti 2004