Java: Föreläsning 8 B

(Det här materialet hörde tidigare till föreläsning 9, men vi hann med det redan på föreläsning 8 i årets kurs.)

Innehåll i föreläsning 8 B

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<BufferedReader> files = new ArrayList<BufferedReader>();
            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
nrFiles = 1020
nrFiles = 1021
Exception caught: dummy.txt (Too many open files)
Inte så konstigt att programmet kraschar: Alla de öppnade filerna sparas i arraylistan files. Eller egentligen är det ju BufferedReader-objekt som sparas, men ett sådant innehåller eller refererar till en notering om den öppna filen, som är en operativsystemresurs, och denna öppna fil stängs inte förrän BufferedReader-objektet skräpsamlas. Och det görs aldrig i detta program, eftersom alla BufferedReader-objekten fortfarande är åtkomliga, via arraylistan 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

...
Vi har tur. Skräpsamlaren samlar ihop och stänger hela tiden de öppna filerna, innan de blir för många.

Programmet FileCounter3.java:

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

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

    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 (max 1)
nrFiles = 1
nrInstances up, now 2 (max 2)
nrFiles = 2
nrInstances up, now 3 (max 3)
nrFiles = 3

...

nrInstances up, now 598 (max 598)
nrFiles = 598
nrInstances up, now 599 (max 599)
nrFiles = 599
nrInstances down, now 598
nrInstances down, now 598
nrInstances down, now 597

...

nrInstances up, now 1014 (max 1014)
nrFiles = 1232
nrInstances up, now 1015 (max 1015)
nrFiles = 1233
Exception caught: dummy.txt (Too many open files)
nrInstances down, now 1015
nrInstances down, now 1014
nrInstances down, now 1013

...

nrInstances down, now 709
nrInstances down, now 708
nrInstances down, now 707
Övning: Den stannar på 707. 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

(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/installation/jdk1.6.0_03/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 (ny)

Sådär ser det ut 2007. Resten av bilderna på SwingSet2 (både från Linux och Windows) är från 2003, och då var Javas gränssnitt betydligt fulare.

Linux igen, från 2003:

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
    getUIClassID
    updateUI
    isDefaultButton
    isDefaultCapable
    setDefaultCapable
Konstruktorer:
    javax.swing.JButton
    javax.swing.JButton
    javax.swing.JButton
    javax.swing.JButton
    javax.swing.JButton
Fält:
    uiClassID

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


Thomas Padron-McCarthy (thomas.padron-mccarthy@tech.oru.se), 10 december 2007