Java: Föreläsning 1

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

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

Preliminär plan

v44       Java allmänt
          Objektorientering, polymorfism
          Instans- och klassvariabler (static)
          Skräpsamling

v45       Undantag ("exceptions")
          Arv, klasser och gränssnitt ("interfaces"), paket
          Applet, inledande (inför inlämningsuppgift 1)
          Trådar

v46       JDBC, inlämningsuppgift 2
          Java och nätverk

v47       Java och nätverk, forts.
          Genomgång av inlämningsuppgift 2
          Repetition

v48       Grafisk applikation och Javadoc
          Mera Applet
          Grafiska gränssnitt, händelsestyrning

v49       Grafikhantering (omritning)
          Strömmar I/O
          Filhantering

v50       Säkerhet i Java
          JavaBeans
          Repetition
Inlämningsuppgifterna ska redovisas före tentamen.

Innehåll i föreläsning 1

Kursen

C, C++ och Java

Filen hello.c:
#include <stdio.h>

int main(int argc, char* argv[]) {
    printf("Hello, world!\n");
    return 0;
} /* main */
Körexempel:
bimbatron fo-01 > gcc -Wall hello.c -o hello
bimbatron fo-01 > ./hello
Hello, world!
bimbatron fo-01 > 
Filen hello.cc:
#include <iostream>

int main(int argc, char* argv[]) {
    std::cout << "Hello, world!\n";
} // main
Körexempel:
bimbatron fo-01 > g++ -Wall hello.cc -o hello
bimbatron fo-01 > ./hello
Hello, world!
bimbatron fo-01 > 
Filen Hello.java:
public class Hello {
    public static void main(String args[]) {
        System.out.println("Hello, world!");
    }
} // Hello
Körexempel:
bimbatron fo-01 > javac Hello.java
bimbatron fo-01 > java Hello
Hello, world!
bimbatron fo-01 > 
Java-programmet är inte en körbar fil ("exe-fil"), utan måste köras av en Java-tolk!

Javafiler

Virtuell maskin

C#

C# ("C sharp") från Microsoft påminner mycket om Java:
class HelloWorld { 
    public static void Main() { 
        System.Console.WriteLine("Hello, world!"); 
    } 
}

C (och C++) är svårt, jobbigt och farligt

Exempel på farligt: filen helloname.c, med flera allvarliga fel:
#include <stdio.h>

int main(int argc, char* argv[]) {
    printf("What's your name: ");
    char* s;
    gets(s);
    printf("Hello, %s!\n", s);
    return 0;
} /* main */
Körexempel 1:
bimbatron fo-01 > ./helloname
What's your name: Bob
Hello, Bob!
bimbatron fo-01 > 
Körexempel 2:
bimbatron fo-01 > ./helloname
What's your name: Bob med det långa namnet
Hello, Bob med det långa namnet!
Segmentation fault (core dumped)
bimbatron fo-01 > 

Java på våra Windows-system

Flera olika versioner av Java finns installerade på våra Windows-maskiner. Dels finns flera olika versioner av Sun:s SDK (Software Development Kit), och dels finns Borland JBuilder, som är en fönsterbaserad programmeringsomgivning. Sun:s SDK kan laddas hem från Sun, och finns för Windows, Linux och flera andra operativsystem. Borland JBuilder finns i en personlig upplaga ("free full version for personal use") som kan laddas hem från Borland.

Det finns alltså flera sätt att köra Java. Här är ett av dem:

Java i ett kommandofönster

Omgivningsvariabler ("environment"):

Tips: Gör en batch-fil, som till exempel kan heta sp.bat:
set path=%path%;C:\Program\Java\j2sdk1.4.2_02\bin
set classpath=M:\Javakursen
Eller en klickbar fil för den late och smarte. Kalla den exempelvis startjava.bat:
@ECHO OFF
set path=%path%;C:\Program\Java\j2sdk1.4.2_02\bin
set classpath=M:\Javakursen
M:
cd M:\Javakursen
%SystemRoot%\SYSTEM32\CMD.EXE
(Kalla inte filen java.bat. Då kommer Windows att starta den i stället för java-tolken när du skriver java.)

Rektangelprogram

Filen Rektangelkalkylator.java:
// Det finns minst ett fel i det här programmet.

import java.io.*;                                            

class Rektangelkalkylator {
    public static void main(String args[]) {
        int a = 0, b = 0;
        String buf;
        BufferedReader kbd_reader
            = new BufferedReader(new InputStreamReader(System.in));

        try {
            System.out.print("Ge sida a: ");
            buf = kbd_reader.readLine();
            a = Integer.parseInt(buf);
            System.out.print("Ge sida b: ");
            buf = kbd_reader.readLine();
            b = Integer.parseInt(buf);

            System.out.println("Area: " + a * b);
            System.out.println("Omkrets: " + 2*a + 2*b);
        }
        catch (IOException e) {
            System.out.println("Kunde inte läsa!");
        }
    } // main
} // Rektangelkalkylator
Körexempel:
bimbatron fo-01 > java Rektangelkalkylator
Ge sida a: 5
Ge sida b: 4
Area: 20
Omkrets: 108
bimbatron fo-01 > 
Fixa felet. Sen:
bimbatron fo-01 > java Rektangelkalkylator
Ge sida a: Bengt
Exception in thread "main" java.lang.NumberFormatException: For input string: "Bengt"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
        at java.lang.Integer.parseInt(Integer.java:468)
        at java.lang.Integer.parseInt(Integer.java:518)
        at Rektangelkalkylator.main(Rektangelkalkylator.java:15)
bimbatron fo-01 > 
Jämför med vad som händer i C++. Programmet rektangelkalkylator.cc:
#include <iostream>

int main(int argc, char* argv[]) {
  int a = 0, b = 0;

  std::cout << "Ge sida a: ";
  std::cin >> a;
  std::cout << "Ge sida b: ";
  std::cin >> b;

  std::cout << "Area: " << a * b << "\n";
  std::cout << "Omkrets: " << 2*a + 2*b << "\n";
} // main
Körexempel (C++):
bimbatron fo-01 > ./rektangelkalkylator
Ge sida a: Bengt
Ge sida b: Area: 0
Omkrets: 0
bimbatron fo-01 > 
Fånga Java alla fel? Nej:
bimbatron fo-01 > java Rektangelkalkylator
Ge sida a: 1000000
Ge sida b: 1000000
Area: -727379968
Omkrets: 4000000
bimbatron fo-01 > 

Filhantering i Java

BufferedReader? InputStreamReader? Vilket krångel! Och jag som bara ville kunna mata ett heltal från tangentbordet.

Tips: IOStreamDemo.java ur Bruce Eckels Thinking in Java, 3d Ed, visar upp typiska användningar av Java-I/O. Det finns förklaringar i online-boken.

Härnäst

Samma problem med andra lösningar:

Rektangelklass

Filen Rektangel.java:
class Rektangel {
    public Rektangel(int a, int b) {
        this.a = a;
        this.b = b;
    }
    public int omkrets() {
        return 2 * this.a + 2 * this.b;
    }
    public int area() {
        return a * b;
    }
    private int a = 0, b = 0;
} // Rektangel
Filen Main.java:
import java.io.*;

class Main {
    public static void main(String args[]) {
        BufferedReader kbd_reader
            = new BufferedReader(new InputStreamReader(System.in));
        int a, b;
        String buf;
        try {
            System.out.print("Ge sida a: ");
            buf = kbd_reader.readLine();
            a = Integer.parseInt(buf);
            System.out.print("Ge sida b: ");
            buf = kbd_reader.readLine();
            b = Integer.parseInt(buf);

            Rektangel rektangel = new Rektangel(a, b);

            System.out.println("Area: " + rektangel.area());
            System.out.println("Omkrets: " + rektangel.omkrets());
        }
        catch (IOException e) {
            System.out.println("Kunde inte läsa!");
        }
    } // main
} // Main

Main i rektangelklassen

Onödigt med en särskild klass bara för att ha någonstans att lägga main-metoden. Ett vanligt trick är att lägga main i den klass man jobbar med.

Filen Rektangel.java:

import java.io.*;

class Rektangel {
    public Rektangel(int a, int b) {
        this.a = a;
        this.b = b;
    }
    public int omkrets() {
        return 2 * this.a + 2 * this.b;
    }
    public int area() {
        return a * b;
    }
    private int a = 0, b = 0;

    public static void main(String args[]) {
        BufferedReader kbd_reader
            = new BufferedReader(new InputStreamReader(System.in));
        int a, b;
        String buf;
        try {
            System.out.print("Ge sida a: ");
            buf = kbd_reader.readLine();
            a = Integer.parseInt(buf);
            System.out.print("Ge sida b: ");
            buf = kbd_reader.readLine();
            b = Integer.parseInt(buf);

            Rektangel rektangel = new Rektangel(a, b);

            System.out.println("Area: " + rektangel.area());
            System.out.println("Omkrets: " + rektangel.omkrets());
        }
        catch (IOException e) {
            System.out.println("Kunde inte läsa!");
        }
    } // main
} // Rektangel
Körexempel:
bimbatron fo-01 > javac Rektangel.java
bimbatron fo-01 > java Rektangel
Ge sida a: 45
Ge sida b: 34
Area: 1530
Omkrets: 158
bimbatron fo-01 > 
Man kan ha en main-funktion i varje klass. Bra för testning!

Programtyper

Java-applet

Filen Hello.java:
import java.applet.*;
import java.awt.*;

public class Hello extends Applet {
    public void paint(Graphics gr) {
        gr.drawString("Hello, world!", 100, 100);
    } // paint
} // Hello
Filen hello.html:
<html>
<head>
<title>Ett appletexempel</title>
</head>

<body>
  <applet
    code="Hello.class"
    width="200" height="200">
  </applet>
</body>
</html>

Appleten i en webbläsare

Har du en Java-plugin i din webbläsare? I så fall bör du se appleten här, "på riktigt":

Syntaxen påminner starkt om C++

Kanske mest förvånande skillnaden: Man måste inte ha semikolon efter en klassdefinition.

Kommentarer

// Sträcker sig över resten av raden
/* Kan sträcka sig
      över flera
          rader */

Datatyper

Primitiva typer - storlek

boolean   1 bit      false eller true
char     16 bitar    Unicode-tecken
byte      8 bitar   -128 .. 127
short    16 bitar   -32768 .. 32767
int      32 bitar   -2147483648 .. 2147483647
long     64 bitar   -9223372036854775808 ..
                    9223372036854775807
float    32 bitar   3.40282347E+38 ..
                    1.40239846E-45
double   64 bitar   1.79769313486231570E+308 ..
                    4.94065645841246544E-324

Variabeldeklarationer

Arrayer

Java har flera olika "behållar"-typer, som kan innehålla andra objekt, till exempel listor, mängder och mappningar.

Det finns flera olika sorters arrayer! Ingen av dem fungerar som i C och C++.

Notera att Programutveckling med Java kallar alla arrayer för "vektorer".

De inbyggda arrayerna kan lagra både primitiva typer och objekt. Man måste (oftast) göra new.

Exempel:

int[] intarray = new int[10];
int intarray[] = new int[10]
String[] fruits = { "Äpplen", "Päron", "Apelsiner" };
int intarray[10]; // Ger kompileringsfel!

Tilldelning och likhetsjämförelse

Som i C och C++: = är tilldelning och == är jämförelse

Räkneoperationer

Som i C och C++: +, -, *, /, ++, --, +=, /= med flera.

Notera att bitskiftningsoperatorerna << och >>, som i C++ överlagrats för in- och utmatning, bara är bitskiftningsoperatorer i Java.

Kontrollflöden

(Nästan) precis som i C++: if, while, do ... while, for, switch, break, continue.

Några skillnader gentemot C++

Klasser och arv: djur, fiskar och fåglar

Basklassen Djur med subklasserna Fågel och Fisk

Ett djur har ett namn (variabeln name) och en vikt (variabeln weight), och det kan äta (metoden eat):

class Animal {
    protected String name;
    private int weight;
    public Animal(String n, int w) {
        name = n;
        weight = w;
    }
    public void eat(String food) {
        System.out.println(name + " eats some " + food + ".");
        weight += 0.5;
    }
} // class Animal
(Jag använder engelska namn för att slippa faglar som ater.)

En fisk är ett djur. Den har alla egenskaper som ett djur har (nämligen namn och vikt) och den kan allt som ett djur kan (nämligen äta). Men dessutom har den en största djup som den kan simma på, och den kan simma:

class Fish extends Animal {
    private int maxDepth;
    public Fish(String n, int w, int d) {
        super(n, w);
        maxDepth = d;
    }
    public void swim() {
        System.out.println(name + " swims.");
    }
} // class Fish
En fågel är också ett djur. Den har alla egenskaper som ett djur har (nämligen namn och vikt) och den kan allt som ett djur kan (nämligen äta). Men dessutom har den en högsta flyghöjd, och den kan flyga. Dessutom beter den sig lite speciellt när den äter: först fladdrar den med vingarna av lycka över att ha fått matm och därefter äter den precis som ett vanligt djur.
class Bird extends Animal {
    private int maxAltitude;
    public Bird(String n, int w, int a) {
        super(n, w);
        maxAltitude = a;
    }
    public void fly() {
        System.out.println(name + " flies.");
    }
    public void eat(String food) {
        System.out.println(name + " flutters its wings.");
        super.eat(food);
    }
} // class Bird
Vi provkör klasserna med hjälp av klassen Zoo och dess main-metod:
class Zoo {
    public static void main(String[] args) {
        Bird tweety = new Bird("Tweety", 3, 1000);
        tweety.eat("corn");
        tweety.fly();

        Fish b = new Fish("Bubbles", 13, 100);
        b.eat("fish food");
        b.swim();

        Animal w = new Fish("Willy", 4000, 1000);
        w.eat("tourists");
        // w.swim(); -- Doesn't work. See below.
    }
} // class Zoo
Utmatning:
Tweety flutters its wings.
Tweety eats some corn.
Tweety flies.
Bubbles eats some fish food.
Bubbles swims.
Willy eats some tourists.
Kommentarer:

Lägg märke till att klassen Bird har en egen metod som heter eat. Det finns en i Animal också, men den nya döljer (engelska: overrides) den gamla, och kommer att användas i alla Bird-objekt. Om man har en metod i en subklass med samma namn (och argument) som en metod i superklassen, så döljer metoden i subklassen metoden i superklassen. Om man trots det vill anropa metoden i superklassen kan man använda super, som är objektet men "tolkat som om det enbart hörde till superklassen". Det är vad vi gör i metoden eat: efter att ha fladdrat med vingarna så ska fågeln faktiskt äta, och det görs med ett anrop till super.eat.

Angående w.swim(): Variabelb w är av typen Animal, och Animal har ingen metod som heter swim. Variabeln w innehåller Willy, som är en Fish, och Fish har ju en swim-metod, så den skulle ha kunnat anropas när programmet körs. Men kompilatorn måste vara säker på att det aldrig kan uppstå ett typfel! Därför kontrollerar den vid kompileringstillfället att anropet aldrig kan misslyckas. Det betyder att varje objekt som kan lagras i variabeln w måste vara av en klass som har en swim-metod.

Polymorfism: figurer

Världen består av figurer, och en figur kan antingen vara en rektangel eller en cirkel:

Basklassen Figur med subklasserna Rektangel och Cirkel

En Figur har en mittpunkt, som anges med koordinaterna x_center och y_center. Man kan räkna ut dess area och omkrets, och den högsta x-koordinat och y-koordinat som täcks av figuren.

Men! Hur figuren ser ut, och hur man räknar ut area och omkrets, beror ju helt på vilken figur det är! Därför kan de metoderna inte implementeras i klassen Figur, utan "lämnas" till subklasserna. Det kallas att metoderna är abstrakta. Eftersom det finns abstrakta metoder i klassen, kan man inte skapa objekt av den typen, så hela klassen blir abstrakt.

public abstract class Figur {
    public Figur(int x, int y) {
        this.x_center = x;
        this.y_center = y;
    } // Figur

    public abstract int max_x();
    public abstract int max_y();
    public abstract int area();
    public abstract int omkrets();

    protected int x_center, y_center;
} // class Figur
En Rektangel är en sorts Figur, och den innehåller förstås ett sätt att räkna ut omkretsen och ett sätt att räkna ut arean. Klassen Rektangel är inte abstrakt, utan man kan skapa Rektangel-objekt. En Rektangel har inte bara en mittpunkt, utan dessutom två sidor. Vi skriver en konstruktor med de extra parametrarna.
public class Rektangel extends Figur {
    public Rektangel(int x, int y, int sida, int hojd) {
        super(x, y);
        this.sida = sida;
        this.hojd = hojd;
    } // Rektangel

    public int max_x() {
        return super.x_center + this.sida / 2;
    } // max_x

    public int max_y() {
        return super.x_center + this.hojd / 2;
    } // max_y

    public int area() {
        return this.sida * this.hojd;
    } // area

    public int omkrets() {
        return 2*this.sida + 2*this.hojd;
    } // omkrets

    private int sida, hojd;
} // class Rektangel
En Cirkel är en annan sorts Figur:
public class Cirkel extends Figur {
    public Cirkel(int x, int y, int radie) {
        super( x, y );
        this.radie = radie;
    } // Cirkel

    public int max_x() {
        return super.x_center + this.radie;
    } // max_x

    public int max_y() {
        return super.y_center + this.radie;
    } // max_y

    public int area() {
        return (int)(Math.PI * this.radie * this.radie);
    } // area

    public int omkrets() {
        return (int)(2*Math.PI * this.radie);
    } // omkrets

    public int diameter() {
        return this.radie + this.radie;
    } // diameter

    private int radie;
} // class Cirkel
Klassen FigurMain innehåller metoden main: Kod:
public class FigurMain {
    public static void main(String args[]) {
        Figur[] figurer = new Figur[4];

        figurer[0] = new Rektangel(
                                   10,   // x
                                   10,   // y
                                   10,   // sida
                                   10 ); // hojd

        figurer[1] = new Cirkel(
                                10,   // x
                                10,   // y
                                10 ); // radie

        figurer[2] = new Rektangel(
                                   20,   // x
                                   20,   // y
                                   20,   // sida
                                   20 ); // hojd

        figurer[3] = new Cirkel(
                                20,   // x
                                20,   // y
                                20 ); // radie

        for (int index = 0; index < figurer.length; index++) {
            if (figurer[index] instanceof Cirkel) {
                System.out.println("Cirkel:");
                System.out.println("  Diameter: " +
                                   ((Cirkel)figurer[index]).diameter() );
            }
            else {
                System.out.println("Rektangel:");
            } // if

            System.out.println("  Högsta x: " + figurer[index].max_x());
            System.out.println("  Högsta y: " + figurer[index].max_y());
            System.out.println("  Area: " + figurer[index].area() );
            System.out.println("  Omkrets: " + figurer[index].omkrets());
        } // for
    } // main
} // class FigurMain
Körexempel:
Rektangel:
  Högsta x: 15
  Högsta y: 15
  Area: 100
  Omkrets: 40
Cirkel:
  Diameter: 20
  Högsta x: 20
  Högsta y: 20
  Area: 314
  Omkrets: 62
Rektangel:
  Högsta x: 30
  Högsta y: 30
  Area: 400
  Omkrets: 80
Cirkel:
  Diameter: 40
  Högsta x: 40
  Högsta y: 40
  Area: 1256
  Omkrets: 125

Vad är en klass?

Vad är ett objekt?

Ett objekt är en instans av en klass. Exempel:
Cirkel cirkel; // får värdet null
cirkel = new Cirkel( 10, 10, 10 );
Nu har vi en instans av Cirkel. Variabeln cirkel refererar till den cirkeln. (Ungerfär som pekare i C.)
Cirkel cirkel2; // får värdet null
cirkel = cirkel;
Nu refererar både cirkel och cirkel2 till samma objekt!

C++:s delete finns inte i Java, och behövs inte. Java har automatisk skräpsamling (engelska: garbage collection).

Syntax

<modifier> 
class 
<name>
 [ extends <baseclassname> ]
 [ implements <interfacename> ]
  {
    . . .
    <variable definitions>
    . . .
    <function definitions>
    . . .
  }

Anrop av instansmetod

Cirkel cirkel = new Cirkel( 10, 10, 10 );
System.out.println("Area: " + cirkel.area());
Anrop av area-metoden i Cirkel. Inuti metoden refererar variabeln this till objektet, dvs cirkeln cirkel.

Peta på data i ett objekt

Gör radie allmänt åtkomlig (public) i Cirkel:
public int radie;
Nu kan man peta på den utifrån:
Cirkel cirkel = new Cirkel( 10, 10, 10 );
cirkel.radie = 27; // som en struct i C

Överlagring av metoder

Flera funktioner med samma namn, men med olika typer på parametrar, olika antal parametrar, olika returtyper. Exempel från klassen Cirkel:
  public void rita(int skala) { /* ... */ }
  public void rita(int x, int y) { /* ... */ }

Konstruktor

Flera konstruktorer

Överlagring även här. Exempel från klassen Figur:
public Figur(int x, int y) { /* ... */ }
public Figur() { /* ... */ }

Destruktor: metoden finalize

Men Java har automatisk skräpsamling! Inte som i C++, där destruktorn måste städa upp allt som ett objekt allokerat.

Lita inte på finalize, till exempel för att stänga filer och ta bort fönster. Man har ingen aning om när den körs.