Java: Föreläsning 2

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

Ungefär motsvarande föreläsningsanteckningar från förra året: java012.pdf

Innehåll i föreläsning 2

Instans- kontra klassvariabler

Instans- kontra klassmetoder

Konstanter: final

Variabler kan deklareras som final, så blir de konstanter:
final int gnorble = 17;
gnorble = 4; // Ger kompileringsfel!
Gäller även metodparametrar:
void f(final Kanin k) {
    k = null; // Ger kompileringsfel!
}
Som C++:s const, men inte lika avancerat. C++-exempel:
class Foo {
public:
  const int k;
  int fum;
  void f(const int* const p) const {
    int i;
    k = 17;     // Fel pga första const
    *p = 17;    // Fel pga andra const
    p = &i;     // Fel pga tredje const
    fum = 17;   // Fel pga fjärde const
  }
};

Exempel: static, final

Filen Heltal.java:
public class Heltal {
    private int talet;
    public void inkrementera() {
	++talet;
    }
    private static int antal = 0;
    public final static int instansnummer = antal++;
    public static int konvertera(String s) {
	return Integer.parseInt(s);
    }
    public static void main(String[] args) {
        Heltal h = new Heltal();
	Heltal.konvertera("13");
	h.konvertera("13");
	konvertera("13");
	h.inkrementera();
    } // main
} // class Heltal

Att skriva C-program i Java

Gör allting static, och använd en eller flera klasser enbart för att ha någonstans att stoppa static-metoderna och static-variablerna.

Exempel: Rektangelkalkylator.java, från föreläsning 1.

Rekommenderas normalt inte.

Exempel: Komplexa tal

Filen Complex.java:
public class Complex {
    public static int antalKomplexaTal = 0;

    public Complex(float re, float im) {
        this.re = re;
        this.im = im;
        Complex.antalKomplexaTal++;
    } // Complex

    public Complex() {
        this(0, 0);
    } // Complex

    public void skrivTal() {
      System.out.print(re + " + " + im + "i" );
    } // skrivTal
    }
    private float re, im;

    public static void main(String[] args) {
        Complex carray[] = new Complex[3];
        for (int index = 0; index < 3; index++) {
            carray[index] = new Complex();
        } // while
        System.out.println(Complex.antalKomplexaTal +
                           " objekt allokerade.");
    } // main
} // class Complex
Körexempel:
pc105-246.oru.se complex > javac Complex.java
pc105-246.oru.se complex > java Complex
3 objekt allokerade.
pc105-246.oru.se complex > 

Problem

Instansräknaren räknas upp, men aldrig ner. Om det ändå fanns en destruktor...

finalize

Motsvarigheten till en destruktor i C++, men behövs inte alls lika mycket.

Dessutom vet man inte alls när den kommer att köras, utom att det blir sen nån gång (kanske).

    protected void finalize() throws Throwable {
        antalKomplexaTal--;
        super.finalize();
    } // finalize
Mall för hur finalize egentligen ska se ut:
    protected void finalize() throws Throwable {
        try {
            // Egen uppstädning här
        }
        finally {
            super.finalize();
        }
    } // finalize

Ny mainmetod - 1

Ur en annan version av Complex.java:
    public static void main(String[] args) {
        Complex carray[] = new Complex[3];
        for (int index = 0; index < 3; index++) {
            carray[index] = new Complex();
        } // while
        {
            Complex snartUrScope = new Complex();
            System.out.println(antalKomplexaTal + " med snartUrScope.");
        }
        System.out.println(antalKomplexaTal + " utan snartUrScope.");
    } // main
Körexempel:
pc105-246.oru.se complex-2 > javac Complex.java
pc105-246.oru.se complex-2 > java Complex
4 med snartUrScope.
4 utan snartUrScope.
pc105-246.oru.se complex-2 > 

Kaniner

Ett exempel för att visa att man inte ska lita på att skräpsamlingen sker vid någon viss tid.

Kanin.java:

public class Kanin {
    public static int antalSkapadeKaniner = 0;
    public static int antalKvarvarandeKaniner = 0;
    int nummer;
    // Funkar också: int nummer = antalSkapadeKaniner++;

    public Kanin() {
        nummer = ++antalSkapadeKaniner;
        ++antalKvarvarandeKaniner;
        System.out.println("Skapade just kanin nummer " + nummer +
                           ". Nu finns " + antalKvarvarandeKaniner +
                           " kaniner.");
    } // Kanin

    protected void finalize() throws Throwable {
        try {
            --antalKvarvarandeKaniner;
            System.out.println("Tar bort kanin nummer " + nummer +
                               ". Sen finns " + antalKvarvarandeKaniner +
                               " kaniner.");
        }
        finally {
            super.finalize();
        }
    } // finalize

    public static void main(String[] args) {
        Kanin k;
        while (true) {
            k = new Kanin();
        }
    } // main
} // class Kanin
Jämför med pekare i C++ - måste tas bort manuellt med delete:
    Kanin *k;
    while (true) {
      k = new Kanin();
      delete k;
    }
Jämför med objekt (ej pekare) i C++ - tas bort automatiskt, och direkt:
    Kanin k;
    while (true) {
      k = Kanin();
    }
Javaprogrammet skapar massor av kaniner, men behåller bara pekaren till sen senaste. Alla de andra kan alltså skräpsamlas. Gör de det? Ja, sen nån gång.

Java-körexempel: Antal kaniner som funktion av antalet varv i while-loopen, för de första 100000 varven:

Kurva som visar antalet kaniner

Först skapas alltså ca 4000 kaniner.
Sen tas ca 2000 kaniner bort.
Sen skapas ca 2000 till.
...

Plus att det blir konstigt, eftersom finalize kan köras till exempel mitt i en utskrift, eller när som helst garben känner för att gå igång:

Tar bort kanin nummer 2516. Sen finns 1503 kaniner.
Tar bort kanin nummer 2628. Sen finns 1502 kaniner.
Skapade just kanin nummer 3076. Nu finns 1613 kaniner.
Tar bort kanin nummer 2515. Sen finns 1501 kaniner.
Tar bort kanin nummer 2629. Sen finns 1500 kaniner.

Sammanfattning: Skräpsamling

Mer om arv

Slutgiltiga klasser: final

public final Complex

Superklass

  • Alla klasser har en superklass
  • Även om det inte står något extends. I så fall ärver klassen från Object.
  • Klassen Object:

    Skydda data och metoder - 1

    Tips

    Dålig felhantering i C

    Felhanteringen i C, och i ganska stor utsträckning C++, bygger på att programmeraren skriver kod som kontrollerar returvärden.

    Vad kan gå fel i följande C-program, unsafe-hello.c?

    #include <stdio.h>
    
    int main(int argc, char* argv[]) {
        printf("Hello, world!\n");
        return 0;
    } /* main */
    

    En säkrare version, safe-hello.c:

    #include <stdlib.h>
    #include <stdio.h>
    
    int main(int argc, char* argv[]) {
        if (printf("Hello, world!\n") != 14) {
            fprintf(stderr,
    		"Kunde inte skriva hela texten!\n");
            return EXIT_FAILURE;
        }
        if (fclose(stdout) != 0) {
            fprintf(stderr,
    		"Kunde inte skriva till filen!\n");
            return EXIT_FAILURE;
        }
        return 0;
    } /* main */
    

    Bra felhantering i Java: Undantag ("exceptions")

    Java "kastar ett undantag" ("throws an exception") när något blir fel. Programmet kan "fånga" ett undantag, kanske på ett helt annat ställe än där det kastades.

    Liknelse: När man använder sig av ett subsystem (till exempel att man anropar metoden tvätta i objektet t av klassen Tvättmaskin) ska man inte behöva stå och kontrollera att den verkligen tvättar. I stället låter man den tvätta bäst den vill, och om något går fel kommer det ett undantagsobjekt flygande och träffar en i huvudet.

    Livet blir lättare med undantag

    I många program består huvuddelen av programkoden av felhantering. Med felhantering med hjälp av undantag kan man både samla ihop felhanteringskoden, i stället för att ha den utspridd i hela programmet, och minska mängden felhanteringskod.

    En bra sak med undantag är att de kan kastas "genom" en metod. Även om den metoden inte gör någon felhantering alls, kan det kastade undantaget skickas vidare till anroparen. Studera detta utdrag ur ett Java-program som inte innehåller någon felhantering (dinner-1/Dinner.java):

        private void drinkCoffee() {
            // Drick kaffe
        }
    
        private void eatDinner() {
            eatAppetizer();
            eatMainCourse();
            eatDessert();
            drinkCoffee();
        }
    
        public static void main(String[] args) {
            Dinner d = new Dinner();
            d.eatDinner();
            System.out.println("Ätandet klart.");
            System.out.println("(Kanske i alla fall.)");
        } // main
    
    Vad händer om det uppstår ett fel någonstans, till exempel i drinkCoffee? Vi måste lägga till felhantering. Vi kan till exempel låta metoderna returnera true om allt gick bra, och false om något gick fel. Utdrag ur dinner-2/Dinner.java:
        private boolean drinkCoffee() {
            if (coffeePot == null)
                return false;
            else {
                // Drick kaffe. Det gick bra:
                return true;
            }
        }
    
        private boolean eatDinner() {
            if (eatAppetizer() == false)
                return false;
            if (eatMainCourse() == false)
                return false;
            if (eatDessert() == false)
                return false;
            if (drinkCoffee() == false)
                return false;
            return true;
        }
    
        public static void main(String[] args) {
            Dinner d = new Dinner();
            if (d.eatDinner() == false)
                System.out.println("Ätandet misslyckades!");
        } // main
    
    Notera hur mycket jobb det blev i eatDinner, och hur det programmet egentligen ska göra nästan försvinner bland all felhanteringen.

    Bättre med undantag! Utdrag ur dinner-3/Dinner.java:

        private void drinkCoffee() throws NoCoffeePotException {
            if (coffeePot == null)
                throw new NoCoffeePotException();
            else {
                // Drick kaffe
            }
        }
    
        private void eatDinner() throws NoCoffeePotException {
            eatAppetizer();
            eatMainCourse();
            eatDessert();
            drinkCoffee();
        }
    
        public static void main(String[] args) {
            Dinner d = new Dinner();
            try {
                d.eatDinner();
            }
            catch (NoCoffeePotException e) {
                System.out.println("Ingen kaffepanna!");
            }
            catch (Exception e) {
                System.out.println("Något annat gick snett.");
            }
        } // main
    
    Så här ser NoCoffeePotException ut:
    class NoCoffeePotException extends Exception {
    
    } // class NoCoffeePotException
    

    Inbyggda undantag

    Det här har de fått jobba länge på för att få så krångligt! (Designen har utvecklats i flera steg, i stället för på en gång.)

    Klasshierarki över undantagsklasserna

    Checked exceptions ("kontrollerade undantag") måste fångas med try och catch, eller deklareras med throws. Unchecked exceptions ("okontrollerade undantag") behöver inte.

    Normalt bygger man vidare på Exception.

    Exempel på program med undantag: DivZeroException

    Programmet DivZeroException/Number.java:
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    
    // Att kastas vid division med noll
    class DivZeroException extends Exception {
    
    } // class DivZeroException
    
    class Number {
        public Number(int tal) {
            this.num = tal;
        } // Number
    
        public void addera(int tal) {
            this.num = this.num + tal;
        } // addera
    
        public void dividera(int tal)
          throws DivZeroException {
            if (tal == 0)
                throw new DivZeroException();
            this.num = this.num / tal;
        } // dividera
    
        public int varde() {
            return this.num;
        }
    
        private int num = 0;
    
        public static void main(String[] args) {
          BufferedReader kbd_reader =
              new BufferedReader(
                     new InputStreamReader(System.in));
          int tal = 1;
          String buf;
          Number num = new Number(14);
    
          System.out.println("Startar med: " + num.varde());
          System.out.println("Adderar udda tal.");
          System.out.println("Dividerar jämna tal." );
    
          try {
            while (tal != 99) {
                System.out.print("Ge ett tal (99 avslutar): ");
                buf = kbd_reader.readLine();
                tal = Integer.parseInt(buf);
                if (tal % 2 == 1)
                    num.addera(tal);
                else
                    try {
                        num.dividera(tal);
                    }
                    catch (DivZeroException e) {
                        System.out.println("Ojdå! Division med 0.");
                    }
    
                System.out.println("Nu är talet: " + num.varde());
            } // while
          } // try
          catch (Exception e) {
              System.out.println("Fel inmatning / tangentbordsfel");
          } // catch
        } // main
    } // class Number
    
    Körexempel:
    pc105-246.oru.se DivZeroException > javac Number.java
    pc105-246.oru.se DivZeroException > java Number
    Startar med: 14
    Adderar udda tal.
    Dividerar jämna tal.
    Ge ett tal (99 avslutar): 13
    Nu är talet: 27
    Ge ett tal (99 avslutar): 4
    Nu är talet: 6
    Ge ett tal (99 avslutar): Bengt
    Fel inmatning / tangentbordsfel
    pc105-246.oru.se DivZeroException > 
    

    Gränssnitt (interface)

    Exempel: Integralprogram

    Gränssnitttet Funktion.java:
    public interface Funktion {
        public float f(float x);
        public float derivatan(float x);
        public float anti_derivatan(float x);
    } // interface Funktion
    
    Klassen Sinus.java, som implementerar funktionen f(x) = sin(x):
    public class Sinus implements Funktion {
    
        public float f(float x) {
    	return (float)Math.sin(x);
        }
    
        public float derivatan(float x) {
    	return (float)Math.cos(x);
        }
    
        public float anti_derivatan(float x) {
    	return -(float)Math.cos(x);
        }
    } // class Sinus
    
    Klassen Polynom.java, som implementerar funktionen f(x) = x - x2 + 3:
    public class Polynom implements Funktion {
        public float f(float x) {
    	return x*x - x + 3.0f;
        }
    
        public float derivatan(float x) {
    	return 2.0f*x - 1.0f;
        }
    
        public float anti_derivatan(float x) {
    	return x*x*x/3.0f - x*x/2.0f + 3.0f*x;
        }
    } // Polynom
    
    Klassen Integral.java:
    public class Integral {
        public float integralen(Funktion fkn, float a, float b) {
    	return fkn.anti_derivatan(b) - fkn.anti_derivatan(a);
        }
    } // Integral
    
    Klassen IntegralTest.java:
    import java.io.*;
    
    public class IntegralTest {
        public static void main(String[] args) {
    	Sinus sinfunktion = new Sinus();
    	Polynom polynom = new Polynom();
    	Integral losare = new Integral();
    	System.out.println("Sinusintegral: " +
    			   losare.integralen(sinfunktion, 1.0f, 3.0f));
    	System.out.println("Polynomintegral: " +
    			   losare.integralen(polynom, 1.0f, 3.0f));
        } // main
    } // IntegralTest
    
    Körexempel:
    Sinusintegral: 1.5302948
    Polynomintegral: 10.666667
    

    Paket i Java

    BufferedReader import

    Paketering av integral

    Klassen Polynom.java,
    package integralpaket;
    
    class Polynom implements Funktion {
        public float f(float x) {
    	return x*x - x + 3.0f;
        }
    
        public float derivatan(float x) {
    	return 2.0f*x - 1.0f;
        }
    
        public float anti_derivatan(float x) {
    	return x*x*x/3.0f - x*x/2.0f + 3.0f*x;
        }
    } // Polynom
    
    Körexempel på Linux:
    pc105-246.oru.se fo-02 > pwd
    /home/padrone/tmp/java/forelasningar/fo-02
    pc105-246.oru.se fo-02 > echo $CLASSPATH
    /home/padrone/tmp/java/forelasningar/fo-02
    pc105-246.oru.se fo-02 > java integralpaket.IntegralTest
    Sinusintegral: 1.5302948
    Polynomintegral: 10.666667
    pc105-246.oru.se fo-02 > 
    

    Nästa gång: Applet