Java: Föreläsning 8

Av Thomas Padron-McCarthy (Thomas.Padron-McCarthy@tech.oru.se). Senaste ändring 23 november 2003.

Innehåll i föreläsning 8

Inre klasser

Ett program som öppnar ett fönster med fyra knappar.

BoringWindow-programmets fönster

Varje knapp har sin egen callback-metod: metoden actionPerformed, i en klass som implementerar gränssnittet ActionListener. Det finns flera olika sätt att definera dessa ActionListener-implementerande klasser:

Programmet BoringWindow.java:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class TrippButtonListener implements ActionListener {
    public void actionPerformed(ActionEvent event) {
        System.out.println("Tripp!");
    }
} // class TrippButtonListener

public class BoringWindow {
    private int bwx = 1;

    private class TrappButtonListener implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            System.out.println("Trapp!");
	    System.out.println("bwx = " + bwx);
        }
    } // class TrappButtonListener

    public BoringWindow() {
        int kx = 2;
        final int fkx = 3;

        class TrullButtonListener implements ActionListener {
            public void actionPerformed(ActionEvent event) {
                System.out.println("Trull!");
                System.out.println("bwx = " + bwx);
                // Ger kompileringsfel
                // local variable kx is accessed from within inner class; needs to be declared final
                // System.out.println("kx = " + kx);
                System.out.println("fkx = " + fkx);
            }
        } // class TrullButtonListener

        JFrame frame = new JFrame("Ett tråkigt fönster");
        frame.setSize(350, 50);

        Container cp = frame.getContentPane();
        cp.setLayout(new GridLayout(1, 4));

        JButton trippknapp = new JButton("Tripp!");
        JButton trappknapp = new JButton("Trapp!");
        JButton trullknapp = new JButton("Trull!");
        JButton hejsanknapp = new JButton("Hejsan!");

        cp.add(trippknapp);
        cp.add(trappknapp);
        cp.add(trullknapp);
        cp.add(hejsanknapp);

        trippknapp.addActionListener(new TrippButtonListener());
        trappknapp.addActionListener(new TrappButtonListener());
        trullknapp.addActionListener(new TrullButtonListener());
        hejsanknapp.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    System.out.println("Hejsan!");
                    System.out.println("bwx = " + bwx);
                    // Ger kompileringsfel
                    // local variable kx is accessed from within inner class; needs to be declared final
                    // System.out.println("kx = " + kx);
                    System.out.println("fkx = " + fkx);
                }
            });

        frame.setVisible(true);
    } // BoringWindow

    public static void main(String[] args) {
        final BoringWindow b = new BoringWindow();
        int mx = 4;
        final int fmx = 5;

        class UnusedButtonListener implements ActionListener {
            public void actionPerformed(ActionEvent event) {
                System.out.println("Impossible!");
                // Ger kompileringsfel
                // non-static variable bwx cannot be referenced from a static context
                // System.out.println("bwx = " + bwx);
                System.out.println("b.bwx = " + b.bwx);
                // Ger kompileringsfel
                // local variable mx is accessed from within inner class; needs to be declared final
                // System.out.println("mx = " + mx);
                System.out.println("fmx = " + fmx);
            }
        } // class TrullButtonListener

        // Ger kompileringsfel
        // non-static variable this cannot be referenced from a static context
        // new TrappButtonListener();

        new TrippButtonListener();

        // Ger kompileringsfel
        // cannot resolve symbol
        // new TrullButtonListener();

        new UnusedButtonListener();
    } // main
} // class BoringWindow
En klass kan vara: Ett objekt av en inre klass finns "inuti" ett objekt av den omgivande klassen, och har tillgång till det yttre objektets instansvariabler.

Om den inre klassen är definierad inuti en metod, kallas den för en lokal klass. Ett objekt av den lokala klasssen har tillgång till de lokala variablerna i metoden där den definierades, men bara om de är final-deklarerade. (De lokala variablerna försvinner ju när metoden avslutas, medan instansen av den lokala klassen kan finnas kvar, så det Java-maskinen gör är att kopiera värdena.)

Ett annat exempel på en inre klass

Programmet Client3.java från föreläsning 5 innehåller klassen ChatButtonWindow. Ett ChatButtonWindow är ett fönster med knappar, som kan skicka meddelanden till servern.

ChatButtonWindow-fönstret

För att kunna skicka dessa meddelanden, måste ChatButtonWindow-instansen på något sätt ha tillgång till den PrintWriter-ström som är kopplad till servern. Det görs genom att PrintWriter-strömmen skickas med till ChatButtonWindow-klassens konstruktor, och sen lagras i en instansvariabel i ChatButtonWindow-instansen.

Ett alternativ är att göra ChatButtonWindow som en inre klass, som då får direkt tillgång till "omgivande" variabler.

I programmet Client5.java (se nedan) är klasserna ServerListener och ChatButtonWindow inre klasser i den omgivande klassen Client5. Därför kan man använda BufferedReader-strömmen in för att läsa från servern i en ServerListener-instans, och man kan använda PrintWriter-strömmen out för att skriva till servern i en ChatButtonWindow-instans.

Egentligen används PrintWriter-strömmen out inte direkt i ChatButtonWindow. I stället finns det två anonyma ActionListener-klasser, som definieras i ChatButtonWindow-klassens konstruktor. Eftersom de är inre klasser i ChatButtonWindow-klassen, som i sin tur är en inre klass i Client3, kommer de åt PrintWriter-strömmen out.

Programmet Client5.java:

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

public class Client5 {
    public static final int PORT = 2000;
    private Socket socket;
    private BufferedReader in;
    private PrintWriter out;
    BufferedReader kbd_reader;

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

    private class ChatButtonWindow extends JFrame {
        public ChatButtonWindow() {
            super("Extra chat buttons");
            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) {
                        out.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) {
                        out.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 Client5(String serverName) throws IOException {
        InetAddress addr = InetAddress.getByName(serverName);
        Socket socket = new Socket(addr, PORT);
        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));

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

        ChatButtonWindow w = new ChatButtonWindow();
    } // Client5

    void listen() throws IOException {
        String buf;
        while (true) {
            buf = kbd_reader.readLine();
            System.out.println("Från tangentbordet: " + buf);
            System.out.println("Till servern: " + buf);
            out.println(buf);
        }
    }

    public static void main(String[] args) throws IOException {
        Client5 c;
        if (args.length >= 1)
            c = new Client5(args[0]);
        else
            c = new Client5(null);
        c.listen();
    } // main
} // class Client5
Men, varning: Bara för att det går att göra så här, behöver det inte vara bra! I exemplet ovan använder koden i klassen ChatButtonWindow variabeln out, som finns i den omgivande klassen Client5.

Det gör att man kan få leta en del i programkoden för att hitta var den variabeln definieras, sätts och används. Det är förmodligen bättre att i stället skicka med out till konstruktorn, och låta ChatButtonWindow själv hålla reda på den i en egen variabel.

Inlämningsuppgift 2: Klientens uppbyggnad

Behöver den vara trådad? Nej. Varför?

Den gamla "DOS-klienten" (som egentligen borde heta "textklienten"):

Följder detta namnkonventionerna för Java? För projektet?

Vad behövs mer i den nya grafiska klienten?
Åtminstone ett fönster!
Kan göras som en klass. Jämför ChatButtonWindow ovan.

Inlämningsuppgift 2: Serverns uppbyggnad

Behöver den vara trådad? Ja. Varför?

Den gamla "DOS-servern" (som egentligen borde heta "textservern"):

Inlämningsuppgift 2: Datahanteringen i botten

Databas-stubben: (Ladda ner alltihop som en ZIP-fil från beskrivningen av inlämningsuppgiften.)

Inlämningsuppgift 2: JDBC mot bilbasen

Från föreläsning 6. Exempel:
String sql = "insert into person values (" + number + ", '" + name + "', '" + phone + "')";
System.out.println("SQL-kommandot: " + sql);
int rowCount = stmt.executeUpdate(sql);

Telnet-programmet

I ett kommandofönster:
  1. Starta programmet: Skriv telnet
  2. Slå på lokalt eko, så du ser vad du skriver: Skriv set local_echo
  3. Anslut till en viss port på en viss maskin: Skriv open localhost 2747 för att ansluta till samma maskin, eller open 130.243.105.246 2747 för att ansluta till maskinen med IP-numret 130.243.105.246

Mer om layout

Man kan bygga upp en layout i flera steg. Exempel:
    class DummyClientWindow {
	public DummyClientWindow() {
	    JFrame frame = new JFrame("Bil-klienten");
	    frame.setSize(500, 100);

	    Container cp = frame.getContentPane();
	    cp.setLayout(new GridLayout(2, 1));

	    Container cp2 = new Container();
	    cp2.setLayout(new GridLayout(1, 4));
	    cp.add(cp2);

	    Container cp3 = new Container();
	    cp3.setLayout(new GridLayout(1, 1));
	    cp.add(cp3);

	    JButton showButton= new JButton("Visa");
	    JButton averageButton = new JButton("Medel");
	    JButton newButton = new JButton("Ny");
	    JButton removeButton = new JButton("Ta bort");
	    JButton exitButton = new JButton("Avsluta");

	    cp2.add(showButton);
	    cp2.add(averageButton);
	    cp2.add(newButton);
	    cp2.add(removeButton);
	    cp3.add(exitButton);

	    frame.setVisible(true);
	} // DummyClientWindow
    } // class DummyClientWindow
Vi har skapat två ytor (av typen Container), med varsin GridLayout, att placera knappar på. Sen placerar vi båda ytorna på "grundytan" i fönstret.

Klientfönster med knappar

Som vanligt går det att ändra storleken på fönstret, och allt innehåll justeras automatiskt så gott det går:

Samma klientfönster, med ny storlek