Java: Föreläsning 11

Innehåll i föreläsning 11

Repetition: gränssnitt och abstrakta klasser

Gränssnitt (interface) är oftast bättre än abstrakta klasser.

Fråga:

En synpunkt på vad du gärna får repetera för min del.
  1. Jag har inte riktigt koll på hur man implementerar egna gränssnitt, och du får gärna reda ut begreppet gränssnitt i Java-sammanhang.
  2. Abstrakta klasser. Vet inte om det är något vi ska kunna men det är ju något som förkommer. Jag har inte riktigt greppat vad det är.
Kom ihåg programmet PresenterTask.java från föreläsning 10. Det använder en java.util.Timer för att köra en viss metod vid vissa tidpunkter.

Låt oss göra vår egen timer-klass!

Första försöket, DåligTajmer.java:

public class DåligTajmer {
    final private Object uppgiften;
    final private double väntetid;
    final private VäntansTråd tråden;

    public DåligTajmer(Object u, double väntetid) {
        uppgiften = u;
        this.väntetid = väntetid;
        tråden = new VäntansTråd();
        tråden.start();
    } // DåligTajmer

    private class VäntansTråd extends Thread {
        public void run() {
            try {
                Thread.sleep((int)(väntetid * 1000));
                uppgiften.utförUppgiften();
            }
            catch (InterruptedException e) {
                // Ja, vad ska man göra nu då?
            }
        }
    } // class VäntansTråd
} // class DåligTajmer
Hur vet man att det där objektet (av den mest generella klassen, Object) överhuvudtaget har någon metod som heter utförUppgiften?

Det vet man inte. I en del språk (som Pike) skulle detta fungera, och det kollas när programmet körs. Om det då visar sig att objektet tillhör en klass som inte har någon utförUppgiften-metod, uppstår ett fel, som signaleras på något sätt: undantag kastas, programmet kraschar, anropet returnerar 0...

Java kontrollerar redan vid kompileringen, och man får ett kompileringsfel:

DåligTajmer.java:17: cannot resolve symbol
symbol  : method utförUppgiften ()
location: class java.lang.Object
                uppgiften.utförUppgiften();
                         ^
1 error
Det är för det mesta bäst att upptäcka felen så tidigt som möjligt!

Skapa ett gränssnitt (=interface), som enbart säger att de klasser som förverkligar (=implementerar) det här gränssnittet ska ha en metod som heter utförUppgiften, som inte tar några argument, och som inte returnerar några värden:

Gränssnittet Tajmeruppgift.java:

public interface Tajmeruppgift {
    public void utförUppgiften();
} // interface Tajmeruppgift
Utvikning: Det hade fungerat (nästan) lika bra med en abstrakt klass:
abstract public class Tajmeruppgift {
    abstract public void utförUppgiften();
} // class Tajmeruppgift
Byt ut Object i DåligTajmer.java mot Tajmeruppgift, vilket ger programmet Tajmer.java:
public class Tajmer {
    final private Tajmeruppgift uppgiften;
    final private double väntetid;
    final private VäntansTråd tråden;

    public Tajmer(Tajmeruppgift u, double väntetid) {
        uppgiften = u;
        this.väntetid = väntetid;
        tråden = new VäntansTråd();
        tråden.start();
    } // Tajmer

    private class VäntansTråd extends Thread {
        public void run() {
            try {
                Thread.sleep((int)(väntetid * 1000));
                uppgiften.utförUppgiften();
            }
            catch (InterruptedException e) {
                // Ja, vad ska man göra nu då?
            }
        }
    } // class VäntansTråd
} // class Tajmer
Provkör tajmerklassen Tajmer med programmet Tajmertest1.java:
class SkrivLite implements Tajmeruppgift {
    public void utförUppgiften() {
        System.out.println("Nu har det gått fem sekunder!");
    }
} // class SkrivLite

public class Tajmertest1 {
    public static void main(String[] args) {
        Tajmer t = new Tajmer(new SkrivLite(), 5.0);
        System.out.println("Klar med main.");
    } // main
} // class Tajmertest1
Utvikning: Om Tajmeruppgift hade varit en abstrakt klass i stället för ett gränssnitt, hade vi fått byta ut implements Tajmeruppgift mot extends Tajmeruppgift.
Utskrift:
Klar med main.                    (följt av en paus i fem sekunder)
Nu har det gått fem sekunder!     (och sen avslutas programmet)
Programmet Tajmertest3.java klarar sig utan en särskild Tajmeruppgift-klass:
public class Tajmertest3 implements Tajmeruppgift {
    public void utförUppgiften() {
        System.out.println("Nu har det gått fem sekunder!");
    }

    private Tajmertest3() {
        Tajmer t = new Tajmer(this, 5.0);
    }

    public static void main(String[] args) {
        Tajmertest3 t = new Tajmertest3();
        System.out.println("Klar med main.");
    } // main
} // class Tajmertest3
Programmet Tajmertest4.java klarar sig också utan en särskild Tajmeruppgift-klass:
public class Tajmertest4 {
    private Tajmertest4() {
        Tajmer t = new Tajmer(new Tajmeruppgift() {
                public void utförUppgiften() {
                    System.out.println("Nu har det gått fem sekunder!");
                }
            }, 5.0);
    } // Tajmertest4

    public static void main(String[] args) {
        Tajmertest4 t = new Tajmertest4();
        System.out.println("Klar med main.");
    } // main
} // class Tajmertest4
Programmet Tajmertest6.java är en applet:
Det här hade inte fungerat om Tajmeruppgift hade varit en abstrakt klass!
// <applet code="Tajmertest6" width="400" height="200">
// </applet>

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

public class Tajmertest6 extends JApplet implements Tajmeruppgift {
    private JTextArea textarean = new JTextArea(10, 30);
    private JButton knappen = new JButton("5-sekundersknappen");

    private void log(String s) {
        textarean.append(s + "\n");
        textarean.setCaretPosition(textarean.getDocument().getLength());
    }

    private void startaTajmern() {
        Tajmer t = new Tajmer(this, 5.0);
    }

    public void init() {
        log("Nu körs metoden init.");
        Container cp = getContentPane();
        cp.setLayout(new BorderLayout());
        cp.add(BorderLayout.CENTER, new JScrollPane(textarean));
        cp.add(BorderLayout.SOUTH, knappen);
        knappen.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
		    log("Nu tryckte du på knappen.");
                    try {
                        startaTajmern();
                    }
                    catch (Exception e) {
                        log("Fångat: " + e);
                    }
                } // actionPerformed
            });
    }

    public void utförUppgiften() {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    log("Nu har det gått fem sekunder!");
                }
            });
    }

    public static void main(String[] args) {
        JApplet applet = new Tajmertest6();
        JFrame frame = new JFrame("Tajmertest6");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(applet);
        frame.setSize(300, 200);
        applet.init();
        frame.setVisible(true);
    } // main
} // class Tajmertest6
Appleten Tajmertest6, fem sekunder efter knapptryckning:

Appleten Tajmertest6, fem sekunder efter knapptryckning

Datum och tider

Programmet Datumtest.java:
import java.util.*;
import java.text.*;

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

        // Klassen Date

        Date nu = new Date();
        // Milliseconds since January 1, 1970, 00:00:00 GMT
        Date start = new Date(0);
        Date biljon = new Date(1000000000000L);

        System.out.println("nu = " + nu);
        System.out.println("start = " + start);
        System.out.println("biljon = " + biljon);

        // Klassen TimeZone

        TimeZone dtz = TimeZone.getDefault();
        System.out.println("dtz = " + dtz);
        String[] ids = TimeZone.getAvailableIDs();

        System.out.println("Det finns " +
                           ids.length + " tidszoner:");
        for (int i = 0; i < ids.length; ++i)
            System.out.println("    " + i + ". " + ids[i]);

        TimeZone tokyo = TimeZone.getTimeZone("Asia/Tokyo");
        TimeZone.setDefault(tokyo);
        System.out.println("nu = " + nu);

        TimeZone.setDefault(dtz);
        System.out.println("nu = " + nu);

        // Klassen Calendar

        Calendar c = Calendar.getInstance();
        System.out.println("c = " + c);

        Calendar ct = Calendar.getInstance(tokyo);
        System.out.println("ct = " + ct);

        System.out.println("Veckonummer (locale-dependent!): " +
                           c.get(Calendar.WEEK_OF_YEAR));
        System.out.println("År: " + c.get(Calendar.YEAR));
        System.out.println("Månad: " + c.get(Calendar.MONTH));
        System.out.println("Dag: " + c.get(Calendar.DAY_OF_MONTH));
        System.out.println("Timmar: " + c.get(Calendar.HOUR_OF_DAY));
        System.out.println("Timmar (0-12): " + c.get(Calendar.HOUR));
        System.out.println("Minuter: " + c.get(Calendar.MINUTE));
        System.out.println("Sekunder: " + c.get(Calendar.SECOND));
        System.out.println("Millisekunder: " + c.get(Calendar.MILLISECOND));

        System.out.println("Tokyo 1: " +
                           ct.get(Calendar.HOUR_OF_DAY) +
                           ":" +
                           ct.get(Calendar.MINUTE));

        ct.setTimeZone(dtz);
        System.out.println("Tokyo 2: " +
                           ct.get(Calendar.HOUR_OF_DAY) +
                           ":" +
                           ct.get(Calendar.MINUTE));

        ct.setTime(biljon);
        System.out.println("Tokyo 3: " +
                           ct.get(Calendar.HOUR_OF_DAY) +
                           ":" +
                           ct.get(Calendar.MINUTE));

        ct.add(Calendar.MINUTE, 99);
        System.out.println("Tokyo 4: " +
                           ct.get(Calendar.HOUR_OF_DAY) +
                           ":" +
                           ct.get(Calendar.MINUTE));

        // Klassen DateFormat

        DateFormat f1 = DateFormat.getInstance();
        DateFormat f2 = DateFormat.getTimeInstance();
        DateFormat f3 = DateFormat.getDateInstance();
        DateFormat f4 = DateFormat.getDateInstance(DateFormat.SHORT);
        DateFormat f5 = DateFormat.getDateInstance(DateFormat.MEDIUM);
        DateFormat f6 = DateFormat.getDateInstance(DateFormat.LONG);
        DateFormat f7 = DateFormat.getDateInstance(DateFormat.FULL);
        DateFormat f8 = DateFormat.getDateTimeInstance(DateFormat.LONG,
                                                       DateFormat.LONG);

        System.out.println("f1.format(nu) = " + f1.format(nu));
        System.out.println("f2.format(nu) = " + f2.format(nu));
        System.out.println("f3.format(nu) = " + f3.format(nu));
        System.out.println("f4.format(nu) = " + f4.format(nu));
        System.out.println("f5.format(nu) = " + f5.format(nu));
        System.out.println("f6.format(nu) = " + f6.format(nu));
        System.out.println("f7.format(nu) = " + f7.format(nu));
        System.out.println("f8.format(nu) = " + f8.format(nu));

        // Lokala konventioner

        Locale[] locales = Calendar.getAvailableLocales();
        System.out.println("Det finns " +
                           locales.length + " locales:");
        for (int i = 0; i < locales.length; ++i)
            System.out.println("    " + i + ". " + locales[i]);

        Locale dl = Locale.getDefault();
        System.out.println("dl = " + dl);

        Locale sverige = new Locale("sv", "SE");
        Locale.setDefault(sverige);

        DateFormat f9 = DateFormat.getDateTimeInstance(DateFormat.LONG,
                                                       DateFormat.LONG);
        System.out.println("f9.format(nu) = " + f9.format(nu));
    }
} // class Datumtest
Utskrifter:
nu = Sat Dec 15 21:04:33 CET 2007
start = Thu Jan 01 01:00:00 CET 1970
biljon = Sun Sep 09 03:46:40 CEST 2001
dtz = sun.util.calendar.ZoneInfo[id="Europe/Stockholm",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=119,lastRule=java.util.SimpleTimeZone[id=Europe/Stockholm,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
Det finns 592 tidszoner:
    0. Etc/GMT+12
    1. Etc/GMT+11
    2. MIT
    3. Pacific/Apia

...

    589. Pacific/Kiritimati
    590. America/Indiana/Petersburg
    591. America/Indiana/Vincennes
nu = Sun Dec 16 05:04:33 JST 2007
nu = Sat Dec 15 21:04:33 CET 2007
c = java.util.GregorianCalendar[time=1197749073209,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Stockholm",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=119,lastRule=java.util.SimpleTimeZone[id=Europe/Stockholm,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2007,MONTH=11,WEEK_OF_YEAR=50,WEEK_OF_MONTH=3,DAY_OF_MONTH=15,DAY_OF_YEAR=349,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=3,AM_PM=1,HOUR=9,HOUR_OF_DAY=21,MINUTE=4,SECOND=33,MILLISECOND=209,ZONE_OFFSET=3600000,DST_OFFSET=0]
ct = java.util.GregorianCalendar[time=1197749073210,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Tokyo",offset=32400000,dstSavings=0,useDaylight=false,transitions=10,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2007,MONTH=11,WEEK_OF_YEAR=51,WEEK_OF_MONTH=4,DAY_OF_MONTH=16,DAY_OF_YEAR=350,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=3,AM_PM=0,HOUR=5,HOUR_OF_DAY=5,MINUTE=4,SECOND=33,MILLISECOND=210,ZONE_OFFSET=32400000,DST_OFFSET=0]
Veckonummer (locale-dependent!): 50
År: 2007
Månad: 11
Dag: 15
Timmar: 21
Timmar (0-12): 9
Minuter: 4
Sekunder: 33
Millisekunder: 209
Tokyo 1: 5:4
Tokyo 2: 21:4
Tokyo 3: 3:46
Tokyo 4: 5:25
f1.format(nu) = 12/15/07 9:04 PM
f2.format(nu) = 9:04:33 PM
f3.format(nu) = Dec 15, 2007
f4.format(nu) = 12/15/07
f5.format(nu) = Dec 15, 2007
f6.format(nu) = December 15, 2007
f7.format(nu) = Saturday, December 15, 2007
f8.format(nu) = December 15, 2007 9:04:33 PM CET
Det finns 152 locales:
    0. ja_JP
    1. es_PE
    2. en
    3. ja_JP_JP

...

    149. sv_SE
    150. da_DK
    151. es_HN
dl = en_US
f9.format(nu) = den 15 december 2007 21:04:33 CET
(Utvecklingen går framåt: 2003 fanns det bara 558 tidszoner och 134 locales.)

Formatering av tal

Programmet Taltest.java:
import java.util.*;
import java.text.*;

public class Taltest {
    public static void main(String[] args) {
        int miljard = 1000000000;
        double glass = 14.50;

        System.out.println("miljard = " + miljard);
        System.out.println("glass = " + glass);

        NumberFormat nf = NumberFormat.getInstance();
        System.out.println("miljard = " + nf.format(miljard));
        System.out.println("glass = " + nf.format(glass));

        Locale dl = Locale.getDefault();
        System.out.println("dl = " + dl);

        Locale sverige = new Locale("sv", "SE");
        Locale.setDefault(sverige);

        NumberFormat nf2 = NumberFormat.getInstance();
        System.out.println("miljard = " + nf2.format(miljard));
        System.out.println("glass = " + nf2.format(glass));

        nf2.setMinimumFractionDigits(2);
        nf2.setMaximumFractionDigits(2);
        System.out.println("glass = " + nf2.format(glass));
    }
} // class Taltest
Utskrifter:
miljard = 1000000000
glass = 14.5
miljard = 1,000,000,000
glass = 14.5
dl = en_US
miljard = 1 000 000 000                Obs: nbsp, inte space!
glass = 14,5
glass = 14,50
Programmet Taltest2.java:
import java.util.*;
import java.text.*;

public class Taltest2 {
    public static void main(String[] args) {
        Locale sverige = new Locale("sv", "SE");
        Locale.setDefault(sverige);

        // Obs: nbsp, inte space!
        String miljarden = "1 000 000 000";
        String glassen = "14,50";

        NumberFormat nf = NumberFormat.getInstance();

        int miljard;
        double glass;

        try {
            Number miljardObjekt = nf.parse(miljarden);
            Number glassObjekt = nf.parse(glassen);

            // ReflectionTest.showClassInfo(miljardObjekt);
            miljard = miljardObjekt.intValue();

            // ReflectionTest.showClassInfo(glassObjekt);
            glass = glassObjekt.doubleValue();

            System.out.println("miljard = " + miljard);
            System.out.println("glass = " + glass);
        }
        catch (java.text.ParseException e) {
            System.out.println("Fångat: " + e);
        }
    }
}
Utskrifter:
miljard = 1000000000
glass = 14.5

Namngivna break

Satsen break hoppar ut ur den loop (eller switch-sats) den står i:
while (true) {
    break;
}

Oändlig loop:
while (true) {
    while (true) {
        break;
    }
}

Hur gör man för att hoppa ur flera nivåer av loopar på en gång?

C++-programmet breaktest.cpp:

#include <iostream>

int main(int argc, char *argv[]) {
    int loops = 0;
    for (int x = 0; x < 1000; ++x) {
        for (int y = 0; y < 1000; ++y) {
            for (int z = 0; z < 1000; ++z) {
                ++loops;
                if (x == 245 && y == 201 && z == 270)
                    goto after_x_loop;
            }
        }
    }
    after_x_loop:
        ;
    std::cout << loops << " loops.\n";
} // main
(Återhopp med return är också vanligt för att åstadkomma detta i C och i C++.)

Utskrift:

245201271 loops.
Java-programmet BreakTest.java:
public class BreakTest {
    public static void main(String[] args) {
        int loops = 0;  
        xLoop:
        for (int x = 0; x < 1000; ++x) {        
            for (int y = 0; y < 1000; ++y) {
                for (int z = 0; z < 1000; ++z) {
                    ++loops;
                    if (x == 245 && y == 201 && z == 270)
                        break xLoop;
                }
            }
        }
        System.out.println(loops + " loops.");
    } // main
} // class BreakTest

Utvikning: Benchmark

Vi jämför C++-programmet och Java-programmet, på ett visst system, med vissa kompilatorer. Resultat:

HTML-parametrar till applets

Java-appleten Parametrar.java:
// <applet code="Parametrar" width="300" height="200">
// </applet>

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

public class Parametrar extends JApplet {
    private JTextArea textarean = new JTextArea(10, 30);
    private JButton knappen = new JButton("foo & fum");

    private void log(String s) {
        textarean.append(s + "\n");
        textarean.setCaretPosition(textarean.getDocument().getLength());
    }

    public void init() {
        log("init");
        Container cp = getContentPane();
        cp.setLayout(new BorderLayout());
        cp.add(BorderLayout.CENTER, new JScrollPane(textarean));
        cp.add(BorderLayout.SOUTH, knappen);
        knappen.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    try {
                        String foo = getParameter("foo");
                        log("foo = " + foo);
                        String fum = getParameter("fum");
                        log("fum = " + fum);
                    }
                    catch (Exception e) {
                        log("Fångat: " + e);
                    }
                } // actionPerformed
            });
    }

    public void start() {
        log("start");
    }

    public void stop() {
        log("stop");
    }

    public static void main(String[] args) {
        JApplet applet = new Parametrar();
        JFrame frame = new JFrame("Parametrar");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(applet);
        frame.setSize(300, 200);
        applet.init();
        frame.setVisible(true);
    }
} // class Parametrar
Ur HTML-filen Parametrar.html:
<applet code="Parametrar" width="300" height="200">
<param name="foo" value="Tjolahopp!">
<param name="bar" value="Tjolahej!">
</applet>
Appleten Parametrar, körd som applet respektive som applikation, efter en knapptryckning:

Appleten Parametrar, körd som applet   Appleten Parametrar, körd som applikation


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