Java: Föreläsning 9

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

Innehåll i föreläsning 9

Skräpsamling revisited

Fråga:
Det här med skräpinsamling, hur var det med det egentligen? Om jag t ex skapar objekt med "new" hur tar jag bort dem? Det där automatiska var ju inte att lita på?
Om skräpsamling, finalize och diverse close-metoder: Programmet FileCounter1.java:
import java.io.*;
import java.util.*;

public class FileCounter1 {
    public static void main(String[] args) {    
        try {

            int nrFiles = 0;
            ArrayList files = new ArrayList();
            while (true) {
                BufferedReader in =
                    new BufferedReader(new FileReader("dummy.txt"));
                files.add(in);
                ++nrFiles;
                System.out.println("nrFiles = " + nrFiles);
            }

        }
        catch (FileNotFoundException e) {
            System.out.println("Exception caught: " +
                               e.getMessage());
        }
    } // main
} // class FileCounter1
Programmet är förstås lite förenklat. Ett användbart program skulle läsa från filen, och det skulle inte öppna samma fil gång på gång.

FileCounter1 kraschar:

nrFiles = 1
nrFiles = 2
nrFiles = 3
nrFiles = 4
nrFiles = 5
nrFiles = 6
nrFiles = 7

...

nrFiles = 1017
nrFiles = 1018
nrFiles = 1019
Exception caught: dummy.txt (Too many open files)
Programmet FileCounter2.java:
import java.io.*;
import java.util.*;

public class FileCounter2 {
    public static void main(String[] args) {    
        try {

            int nrFiles = 0;
            while (true) {
                BufferedReader in =
                    new BufferedReader(new FileReader("dummy.txt"));
                ++nrFiles;
                System.out.println("nrFiles = " + nrFiles);
            }

        }
        catch (FileNotFoundException e) {
            System.out.println("Exception caught: " +
                               e.getMessage());
        }
    } // main
} // class FileCounter2
FileCounter2 kraschade inte, i alla fall inte innan jag tröttnade på att vänta:
nrFiles = 1
nrFiles = 2
nrFiles = 3
nrFiles = 4
nrFiles = 5
nrFiles = 6
nrFiles = 7

...

nrFiles = 999998
nrFiles = 999999
nrFiles = 1000000
nrFiles = 1000001
nrFiles = 1000002
nrFiles = 1000003

...
Programmet FileCounter3.java:
import java.io.*;
import java.util.*;

class MyFileObject {
    private final BufferedReader reader;
    static int nrInstances = 0;
    public MyFileObject(BufferedReader reader) {
        this.reader = reader;
        ++nrInstances;
        System.out.println("nrInstances up, now " + nrInstances);
    }

    public void finalize() {
        --nrInstances;
        System.out.println("nrInstances down, now " + nrInstances);
    }
} // class MyFileObject

public class FileCounter3 {
    public static void main(String[] args) {    
        try {

            int nrFiles = 0;
            while (true) {
                BufferedReader in =
                    new BufferedReader(new FileReader("dummy.txt"));
                MyFileObject mfo = new MyFileObject(in);
                ++nrFiles;
                System.out.println("nrFiles = " + nrFiles);
            }

        }
        catch (FileNotFoundException e) {
            System.out.println("Exception caught: " +
                               e.getMessage());
        }
    } // main
} // class FileCounter3
FileCounter3 provkördes flera gånger. Ibland körde programmet i timmar utan problem. Ibland kraschade det:
nrInstances up, now 1
nrFiles = 1
nrInstances up, now 2
nrFiles = 2
nrInstances up, now 3
nrFiles = 3

...

nrInstances up, now 14
nrFiles = 14
nrInstances up, now 15
nrFiles = 15
nrInstances down, now 14
nrInstances down, now 13
nrInstances up, now 14
nrFiles = 16
nrInstances up, now 15
nrFiles = 17

...

nrInstances up, now 998
nrFiles = 7586
nrInstances up, now 999
nrFiles = 7587
nrInstances up, now 1000
nrFiles = 7588
nrInstances up, now 1001
nrFiles = 7589
nrInstances up, now 1002
nrFiles = 7590
Exception caught: dummy.txt (Too many open files)
nrInstances down, now 1001
nrInstances down, now 1000
nrInstances down, now 999
nrInstances down, now 998
nrInstances down, now 997

...

nrInstances down, now 38
nrInstances down, now 37
nrInstances down, now 36
nrInstances down, now 35
Övning: Den stannar på 35. Varför räknar den inte ner till 0? [Svar]

Man måste frigöra resursern (den öppna filen) själv, med en metod som till exempel heter cleanup eller close. Programmet FileCounter4.java:

import java.io.*;
import java.util.*;

class MyFileObject {
    private final BufferedReader reader;
    static int nrInstances = 0;
    public MyFileObject(BufferedReader reader) {
        this.reader = reader;
        ++nrInstances;
        System.out.println("nrInstances up, now " + nrInstances);
    }

    public void finalize() {
        --nrInstances;
        System.out.println("nrInstances down, now " + nrInstances);
    }

    public void close() throws IOException {
        reader.close();
    }
} // class MyFileObject

public class FileCounter4 {
    public static void main(String[] args) {    
        try {

            int nrFiles = 0;
            while (true) {
                BufferedReader in =
                    new BufferedReader(new FileReader("dummy.txt"));
                MyFileObject mfo = new MyFileObject(in);
                ++nrFiles;
                System.out.println("nrFiles = " + nrFiles);
                mfo.close();

            }

        }
        catch (FileNotFoundException e) {
            System.out.println("Exception caught: " +
                               e.getMessage());
        }
        catch (IOException e) {
            System.out.println("Exception caught: " +
                               e.getMessage());
        }
    } // main
} // class FileCounter4

Inre klasser igen

Kodningsstil. Tips för att programmen ska bli lättare att skriva, felsöka och förstå: Undvik att låta en inre klass använda den omgivande klassens variabler, annat än i mycket enkla fall. Skicka med de data som den inre klassen behöver använda sig av till konstruktorn, och lagra i egna variabler!

Exempel: Klassen ChatButtonWindow i programmet Client5.java från föreläsning 8.

Ur programmet Client6.java:
    private class ChatButtonWindow extends JFrame {
        private PrintWriter out;
        public ChatButtonWindow(PrintWriter outParameter) {
            super("Extra chat buttons");
            out = outParameter;
            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
En tumregel om namn: Ju större scope ett namn har, desto mer informativt ska namnet vara!

Look and feel i Swing

Man kan sätta "look and feel", alltså utseende och (i viss mån) funktion på knappar mm. Här är default- respektive Windows-look-and-feel i Windows:

windows-look-and-feel.gif

(Jag använder skrivbordsschemat Öken i Windows. Java följer skrivbordsschemat när man använder Windows-look-and-feelen.)

Programmet Sqrt1.java (som påminner mycket om appleten Sqrt.java från föreläsning 3):

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

public class Sqrt1 extends JFrame {
    private JLabel label = new JLabel("Här visas roten");
    private JTextField text = new JTextField("Skriv talet här");
    private JButton button = new JButton("Visa rot!");
    private ButtonListener bl = new ButtonListener();

    public Sqrt1() {
        super("Sqrt1");
        button.addActionListener(bl);
        Container cp = getContentPane();
        cp.setLayout(new FlowLayout());
        cp.add(text);
        cp.add(button);
        cp.add(label);
    }

    // Inre klass för att lyssna på knapp
    class ButtonListener implements ActionListener {

        // Hantera klick på roten-ur-knapp
        public void actionPerformed(ActionEvent event) {
            String numbuf = text.getText();
            try {
                float num = Float.parseFloat(numbuf);
                float res = (float)Math.sqrt(num);
                if (Float.isNaN(res))
                    label.setText("Det måste vara ett positivt tal.");
                else
                    label.setText("Roten ur " + num + " är " + res);
            }
            catch (NumberFormatException exc) {
                label.setText("'" + numbuf + "' är inte ett tal.");
            }
        } // actionPerformed
    } // class ButtonListener

    public static void main(String[] args) {
        Sqrt1 frame = new Sqrt1();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 100);
        frame.setVisible(true);
    }
} // class Sqrt1
I programmet Sqrt2.java, som använder Windows-look-and-feelen, har vi lagt till följande rader först i main, innan vi skapar det första fönstret:
        String laf = UIManager.getSystemLookAndFeelClassName();
        try {
            UIManager.setLookAndFeel(laf);
            // If you want the Cross Platform L&F instead, replace
            // UIManager.getSystemLookAndFeelClassName()
            // with
            // UIManager.getCrossPlatformLookAndFeelClassName()
        } catch (UnsupportedLookAndFeelException exc) {
            System.err.println("Warning: UnsupportedLookAndFeel: " + laf);
        } catch (Exception exc) {
            System.err.println("Error loading " + laf + ": " + exc);
        }

Mer om Swing

Det finns en demo av hur man kan använda Swing-paketets fönsterfunktioner till diverse avancerade och vackra saker, med källkod så man ser hur man gör. Gå till katalogen /usr/local/java/j2sdk1.4.2/demo/jfc/SwingSet2, C:\j2sdk1.4.2_02\demo\jfc\SwingSet2, eller var du nu har Java installerat på just ditt system, och ge kommandot java -jar SwingSet2.jar

(Klicka på bilderna nedan för att se dem i större format.)

Swing-demons första exempel, körd på Linux:

linux-swing-demo-1-small

Swing-demons första exempel, körd på Windows. Eftersom vi använder default-look-and-feelen ser det precis likadant ut inuti fönstret:

swing-demo-01-small

Swing-demons första exempel, körd på Windows, men nu med Windows-look-and-feel påslagen:

swing-demo-02-small

Tydlig skillnad på look and feel kan man se i filväljardialogen. Här en filväljardialog med Windows-look-and-feel:

swing-demo-06-small

Sama filväljardialog med default-look-and-feel:

swing-demo-07-small

Några fler exempel ur Swing-demon. (Provkör gärna själv!)

swing-demo-03-small swing-demo-04-small swing-demo-05-small swing-demo-08-small swing-demo-09-small swing-demo-10-small

Ett exempel på en Swing-komponent: JComboBox

Programmet ComboBoxTest.java:

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

public class ComboBoxTest extends JFrame {
    String[] description = { "Tripp", "Trapp", "Trull" };
    JComboBox c = new JComboBox();
    JTextField t = new JTextField(5);

    public ComboBoxTest() {
        c.addItem("Tripp");
        c.addItem("Trapp");
        c.addItem("Trull");
        t.setEditable(false);

        c.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e){
                    JComboBox box = (JComboBox)e.getSource();
                    t.setText(box.getSelectedItem().toString());
                }
            });

        Container cp = getContentPane();
        cp.setLayout(new FlowLayout());
        cp.add(t);
        cp.add(c);
    }

    public static void main(String[] args) {
        ComboBoxTest frame = new ComboBoxTest();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 80);
        frame.setVisible(true);
    }
} // class ComboBoxTest

Ett exempel på en Swing-komponent: ButtonGroup

Programmet ButtonGroupTest.java:

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

public class ButtonGroupTest extends JFrame {
    public ButtonGroupTest() {
        super("ButtonGroupTest");
        Container cp = getContentPane();
        cp.setLayout(new FlowLayout());

        ButtonGroup group = new ButtonGroup();
        JPanel panel = new JPanel();
        panel.setBorder(new TitledBorder("3 x JRadioButton"));

        JRadioButton button1 = new JRadioButton("Tripp");
        group.add(button1);
        panel.add(button1);

        JRadioButton button2 = new JRadioButton("Trapp");
        group.add(button2);
        panel.add(button2);

        JRadioButton button3 = new JRadioButton("Trull");
        group.add(button3);
        panel.add(button3);

        cp.add(panel);
    }

    public static void main(String[] args) {
        ButtonGroupTest frame = new ButtonGroupTest();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300, 100);
        frame.setVisible(true);
    }
} // class ButtonGroupTest

En ButtonGroup kan innehålla olika sorters knappar

Programmet ButtonGroups1.java:

// Adapted from Bruce Eckel's "Thinking in Java", 3d Ed, Chapter 14

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

public class ButtonGroups1 extends JFrame {
    private static String[] ids = {
        "Tripp", "Trapp", "Trull",
    };

    public ButtonGroups1() {
        super("ButtonGroups");
        Container cp = getContentPane();
        cp.setLayout(new FlowLayout());

        ButtonGroup group = new ButtonGroup();
        JPanel panel = new JPanel();
        panel.setBorder(new TitledBorder("3 x JButton"));
        for (int i = 0; i < ids.length; i++) {
            JButton button = new JButton(ids[i]);
            group.add(button);
            panel.add(button);
        }
        cp.add(panel);

        group = new ButtonGroup();
        panel = new JPanel();
        panel.setBorder(new TitledBorder("3 x JToggleButton"));
        for (int i = 0; i < ids.length; i++) {
            JToggleButton button = new JToggleButton(ids[i]);
            group.add(button);
            panel.add(button);
        }
        cp.add(panel);

        group = new ButtonGroup();
        panel = new JPanel();
        panel.setBorder(new TitledBorder("3 x JCheckBox"));
        for (int i = 0; i < ids.length; i++) {
            JCheckBox button = new JCheckBox(ids[i]);
            group.add(button);
            panel.add(button);
        }
        cp.add(panel);

        group = new ButtonGroup();
        panel = new JPanel();
        panel.setBorder(new TitledBorder("3 x JRadioButton"));
        for (int i = 0; i < ids.length; i++) {
            JRadioButton button = new JRadioButton(ids[i]);
            group.add(button);
            panel.add(button);
        }
        cp.add(panel);
    }

    public static void main(String[] args) {
        ButtonGroups1 frame = new ButtonGroups1();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300, 300);
        frame.setVisible(true);
    }
} // class ButtonGroups1

Överkurs, men nödvändigt ibland: Reflektion

Med reflektionsmekanismerna i Java kan man till exempel få fram vilka metoder ett objekt har, och sen anropa dem, utan att man från början vet något om objektets klasstillhörighet.

Programmet ReflectionTest.java:

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

public class ReflectionTest extends JFrame {
    public static void showClassInfo(Object o) {
        Class c = o.getClass();
        System.out.println("Klass: " + c.getName());    

        Class sc = c.getSuperclass();
        System.out.println("Superklass: " + sc.getName());      

        Method[] methods = c.getDeclaredMethods();
        System.out.println("Metoder:");
        for (int i = 0; i < methods.length; ++i)
            System.out.println("    " + methods[i].getName());

        Constructor[] constructors = c.getDeclaredConstructors();
        System.out.println("Konstruktorer:");
        for (int i = 0; i < constructors.length; ++i)
            System.out.println("    " + constructors[i].getName());

        Field[] fields = c.getDeclaredFields();
        System.out.println("Fält:");
        for (int i = 0; i < fields.length; ++i)
            System.out.println("    " + fields[i].getName());
    }

    public static void main(String[] args) {
        JButton button = new JButton("Hej");
        showClassInfo(button);
    }
} // class ReflectionTest
Utskrifter:
Klass: javax.swing.JButton
Superklass: javax.swing.AbstractButton
Metoder:
    writeObject
    getAccessibleContext
    paramString
    removeNotify
    configurePropertiesFromAction
    getUIClassID
    isDefaultButton
    isDefaultCapable
    setDefaultCapable
    updateUI
Konstruktorer:
    javax.swing.JButton
    javax.swing.JButton
    javax.swing.JButton
    javax.swing.JButton
    javax.swing.JButton
Fält:
    uiClassID
    defaultCapable

Samma knappgruppsexempel som ovan, men nu med en metod som använder reflektion.

Programmet ButtonGroups2.java:

// Adapted from Bruce Eckel's "Thinking in Java", 3d Ed, Chapter 14
// Uses reflection to create groups
// of different types of AbstractButton.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.border.*;
import java.lang.reflect.*;

public class ButtonGroups2 extends JFrame {
    private static String[] ids = {
        "Tripp", "Trapp", "Trull",
    };

    private JPanel makeButtonPanel(Class klass, String[] ids) {
        ButtonGroup group = new ButtonGroup();
        JPanel panel = new JPanel();
        String title = klass.getName();
        panel.setBorder(new TitledBorder(title));
        for (int i = 0; i < ids.length; i++) {
            AbstractButton button = new JButton("failed");
            try {
                // Get the dynamic constructor method
                // that takes a String argument:
                Constructor ctor =
                    klass.getConstructor(new Class[] {String.class});
                // Create a new object:
                button = (AbstractButton)
                    ctor.newInstance(new Object[] {ids[i]});
            }
            catch (Exception e) {
                System.out.println("Exception caught: " + e);
            }
            group.add(button);
            panel.add(button);
        }
        return panel;
    }

    public ButtonGroups2() {
        super("ButtonGroups2");
        Container cp = getContentPane();
        cp.setLayout(new FlowLayout());
        cp.add(makeButtonPanel(JButton.class, ids));
        cp.add(makeButtonPanel(JToggleButton.class, ids));
        cp.add(makeButtonPanel(JCheckBox.class, ids));
        cp.add(makeButtonPanel(JRadioButton.class, ids));
    }

    public static void main(String[] args) {
        ButtonGroups2 frame = new ButtonGroups2();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300, 300);
        frame.setVisible(true);
    }
} // class ButtonGroups2