Innehåll, föreläsning 2 (Grundläggande Java)

Föreläsning 2: Grundläggande Java

Varför Java?

Java har flera fördelar jämfört med de flesta andra programspråk: Ett Java-program kan exekveras på alla datorer som har en Java-interpretator och ett Java run-time system. Tillsammans kallas dessa delar JVM (Java Virtual Machine).

Det faktum att Java är interpreterat innebär att program i Java inte är lika snabba som t.ex. program i C, men dagens applikationer är nästan alla interaktiva och/eller nätverksbaserade och befinner sig mestadels i väntetillstånd. Dessutom finns till många system numera s.k. just-in-time (förkortningen JIT avser just detta) kompilatorer som kompilerar bytekoden till maskinkod just innan ett program körs.

Grundläggande datatyper

Det allra mest i Java är objekt. Enda undantaget är de enkla numeriska typerna, tecknen och den logiska typen boolean. Till skillnad från de flesta andra språk definierar Java exakt storleken på alla inbyggda typer. Alla Java-variabler har default-värden, d.v.s. värden som variabler som ej har getts ett värde får.

De inbyggda typerna är, med angivande av storlek i bit och default-värde:


Datatype char är alltså inte 8 bits utan varje tecken är en 16-bit Unicode kod. Vi programmerare behöver normalt inte tänka på detta: klassen String inkapslar stränghanteringen i Java - dessutom är de 256 värden som kan representeras i en byte identiska i ASCII-koden och Unicode-koden. Alla de numeriska typerna har tecken.

Klasser och objekt

Ett Java-program består av objekt - instanser av klasser - som interagerar. Objekt har begränsad livstid: de skapas, används och ändras, samt dör slutligen. Java innehåller konstruktioner för att skapa och använda objekt, däremot ingenting för att ta bort dem - Java sköter detta automatiskt.

Vad innehåller ett objekt?

En klass definierar ett objekts utseende, hur den ser ut för klassens användare, d.v.s. vi programmerare. Ett litet exempel:
class Circle 
{
  public int r;
  public int x;
  public int y;
  public move( int dx, int dy ) { x += dx; y += dy; }
}
Denna klass har attributen r, x och y samt metoden move(). Nyckelordet public anger att klassens användare kan komma åt allting. Datafält och metoder koms åt med hjälp av punktnotation: t.ex. circle.r eller circle.move(-10,-10) om circle är ett objekt av klassen Circle.

Hur man skapar ett objekt (en instans av en klass)

Förutom de inbyggda typerna har Java endast en datatyp: referens. Referens är en generell typ, vars variabler endera har värdet null (default) som betyder att variabeln inte refererar något eller också refererar variabeln en klassinstans. Eftersom Java är ett s.k. hårt typkontrollerat språk kan en viss variabel bara referera instanser av en viss klass (se dock avsnittet Arv för en mer nyanserad bild av verkligheten)

Skillnaden mellan de inbyggda typerna och referenser är att de förra hanteras by value, d.v.s. en kopia av variabeln överförs vid metodanrop, medan referenserna hanteras by reference, d.v.s endast adressen överförs.

I Java räcker det inte med att deklarera en variabel för att skapa ett objekt, man måste explicit skapa objektet med new:

Circle a;                // skapar tom referens
a = new Circle();        // skapar ett Circle-objekt
Circle b = new Circle(); // skapar både referens och objekt.
En tom referens har värdet null som alltså betyder att variabeln inte refererar någonting. Ordet null är reserverat i Java. Objekt av klassen String kan skapas genom uttryck som
String s = "Detta är en sträng";
Man kan alltså (och bör så ofta som möjligt) ge variabler värden direkt vid deklarationen. Detta gäller både de inbyggda typerna och klassinstanser. Något som skiljer Java från de flesta programspråk (men inte C++) är att variabler kan deklareras "var som helst", d.v.s. exekverande satser och deklarationer kan blandas.

Variabler existerar bara i det block de deklareras, d.v.s. från deklarationstillfället till nästa }:

{
 Circle a(10);
  {
    Circle b(20);
    b.draw();
  }
 // a finns här, men inte b:
 a.draw(); - ok
 b.draw(); - fel
}
I Java allokeras minne för alla objekt dynamiskt. Till skillnad från t.ex. C++ och C behöver man inte återlämna minne, Java har automatisk s.k. "garbage collection", d.v.s. ett objekt som inte längre behövs (inte har någon variabel som refererar till sig) tas automatiskt bort. Att Java saknar pekare i traditionell mening och dessutom saknar konstruktioner för att återlämna minne kan te sig konstigt om man tidigare programmerat i C, C++ eller Pascal t.ex. men detta är faktiskt något som underlättar programmeringen och undanröjer en mycket vanlig felkälla.

Arrayer

Arrayer är samlingar av objekt. De är referenstyper och följer reglerna för dessa. Arrayer skapas med:
int[] intBuf = new int[1024];
Circle[] circles = new Circle[10];
double[] doubleTabl = { 1.0, 1.5, 2.0, 3.0, 6.0 };
int[][] twoDArray = new int[8][8];
Arrayers element initieras med respektive typs defaultvärde, i fallet objekt alltså med null, d.v.s. inga Circle-objekt skapas i exemplet, endast 10 st. tomma referenser. Man kan också som i exemplets doubleTable ge arrayens element värden direkt i deklarationen.

Observera att i Java, till skillnad från C++, kan man direkt dynamiskt allokera flerdimensionella arrayer. Java implementerar multidimensionella arrayer som arrayer av arrayer varför man, liksom i C++ och C, kan skapa ickerektangulära arrayer:

int[][] strange2Darray = { {5,6}, {3}, {3,4,5} }; 
double[][] triangular = new double[10][]; // triangular array: x
for (int i=0;i < 10; i++)                 //                   xx
  double[i] = new double[i+1];            //                   xxx ...
Man kommer åt elementen i en array på vanligt sätt:
int i = strange2Darray[0][1];
int j = intBuf[123];
Som i C och C++ är index för första elementet 0. Java kontrollerar alltid att index håller sig i tillåtet område, annars "kastas ett undantag", se Undantag.

Varje array har ett fält, length som talar om hur många element den innehåller:

int[] a = new int[10];
int[][] b = new int[10][5];
int[][][] c = new int[5][6][4];
//
// a.length       = 10
// b.length       = 10
// c.length       =  5
// c[3].length    =  6
// c[3][3].length =  4
//

Generella arrayer

I Java finns också en mer generell arraytyp för lagring av instanser av Object: Vector. En Vector har ingen bestämd storlek utan växer efter behov. Eftersom s.k. operatoröverlagring (möjligheten att överlagra operatorer som +, -, etc.) ej finns i Java kan man inte använda []-syntaxen för elementåtkomst i en Vector. I stället finns en mängd metoder, t.ex. elementAt för manipulering av enskilda element.

Metoder

Konstruktor

När man skapar ett objekt av en klass med new skriver man något som ser ut som ett metodanrop:
Circle = new Circle();
Circle = new Circle( 10, 100, 150):
Det är också vad som sker, systemet anropar en s.k. konstruktor, en metod som initierar objektet enligt parametrarna. Observera att i Java kan man inte skriva t.ex. Circle = new Circle; som man kan i C++, uttrycket efter new måste alltid se ut som ett metodanrop. Normalt skriver man en eller flera konstruktorer men gör man det inte skapar Java en s.k. default-konstruktor utan argument som inte gör någonting. Man kan skriva flera konstruktorer med olika parameteruppsättning, s.k. överlagring (eng. overload, observera skillnaden mellan overload och override). Exempel på en klass med konstruktor:
class Circle
{
  public Circle( int ir, int ix, int iy )
   { r = ir; x = ix; y = iy; }
  private int r;
  private int x;
  private int y;
}
Här har attributen deklarerats private vilket beyder att endast klassens metoder kommer åt dem.

Om man skriver en metod som heter void finalize() kommer denna att anropas när objektet inte längre refereras men innan objektets minne återlämnas. Oftast behöver man ingen sådan metod men ibland kan man behöva återlämna någon systemresurs som inte Javas skräphanterare tar hand om.

Metoder - och något om attribut

En klass som består av endast en konstruktor är inte användbar till speciellt mycket mer än en struct i C: man kan initiera attribut (och läsa och ändra dem om de är deklarerade public). Låt oss titta litet mer på en klass som beskriver en rektangel:
class Rectangle
{
  public Rectangle( int ix, int iy, int iw, int ih)
   { x = ix; y = iy; w = iw; h = ih; }
  public Rectangle( int ix, int iy, int side )
   { x = ix; y = iy; w = side; h = side; }
  public int area()
   { return w * h; }
  private int x;
  private int y;
  private int w;
  private int h;
}
Här ser vi exempel på överlagrade konstruktorer och dessutom en metod som beräknar rektangelns yta. Metoden area() är en s.k. instansmetod, en metod som (med hjälp av punktnotation) anropas med en klassinstans.
Rectangle r = new Rectangle( 500, 500, 200, 300);
int ytan = r.area();
I en metod kan man referera "sig själv" med this. Man skulle t.ex. kunna skriva instansmetoden area() så här:
 public int area() { return this.w * this.h; }
Nu gör man naturligtvis inte så men det finns tillfällen när this verkligen behövs, nämligen när man behöver skicka en referens till sig själv till en metod i en annan klass. Man kan också använda this för att anropa en annan konstruktor, t.ex:
 public Rectangle( int ix, int iy, int side )
   { this(ix,iy,side,side); }
De flesta metoder är instansmetoder, men det finns också s.k. statiska metoder (eller klassmetoder). Antag t.ex. att man vill kunna räkna ut ytan på en rektangel utan att skapa ett objekt. Då är en klassmetod ett alternativ:
public static int area( int w, int h ) 
 { return w*h;}
Man kommer åt statiska metoder genom klassnamnet:
int area = Rectangle.area(5,4);
Det är nyckelordet static som anger att metoden är en klassmetod. Anledningen till att man skriver area() som klassmetod i klassen Rectangle är att endast denna klass bör veta hur en rektangels yta beräknas (inkapsling inte bara av data utan också av metoder).

Även attribut kan vara statiska, i vårt exempel kan man tänka sig att man vill flytta alla rektanglar likadant (translatera dem). Ett sätt att implementera den möjligheten är att deklarera en par klassattribut:

 private static int dx=0;
 private static int dy=0;
Som synes kan man också ge initialvärden till statiska attribut. Dessa initieras när klassen laddas. Initialvärden kan också ges till vanliga instansattribut: dessa initieras när instansen skapas. Oinitierade attribut ges defaultvärden av kompilatorn.

Liksom i fallet med konstruktorer kan man överlagra metoder, d.v.s. skriva flera metoder med samma namn men med olika parameteruppsättningar, t.ex. kan man i fallet Rectangle tänka sig en klassmetod som beräknar en kvadrats yta:

public static area( int side)
   { return side*side; }
Eftersom Java inte har funktioner i vanlig mening är statiska metoder vanliga: de kan ju nästan ses som "vanliga globala funktioner". En del klasser har bara statiska metoder, t.ex. klassen Math. Några av dess deklaration och exempel på användning:
class Math 
{
  public static final double E  = ...; // konstanten e (2.718...)
  public static final double PI = ...; // konstanten pi (3.142...)
  public static double sin(double a) { .. }
  public static double cos(double a) { .. }
  public static double sqrt(double a) { .. }
  public static double exp(double a) { .. }
  public static double pow(double a, double b) { .. }
}
double a = Math.sqrt( 1.2);
double cirkelyta = 2 * Math.PI * radius;
Ännu en modifierare till attribut, final, dyker upp i Math. Detta är Javas konstantdeklaration, E och PI får inte ändras.

Operatorer

Java har i stort sett samma aritmetiska och logiska operatorer som C/C++, klicka här för en genomgång.

De operatorer som opererar på referenser (objekt) är (alla utom '+' opererar på samtliga typer av objekt):

(type) - cast operator, se vidare nedan i avsnittet om arv.
+      - med String-objekt som operander - strängsammanslagning
==     - likhet (de båda operanderna refererar samma objekt)
!=     - ej likhet (de båda operanderna refererar ej samma objekt)
=      - tilldelning
Exempel på användning:
Shape a = new Circle();
Circle b = (Circle) a;       
String b = "Kalle " + "Anka";
Circle c;
if (a==b) c = a;              // likhet och tilldelning
if (a!=b) c = new Circle();   // olikhet och tilldelning
Uttrycket c=a i exemplet betyder att c kommer att referera samma objekt som a (eller inget objekt alls om a har värdet null). Om man verkligen vill kopiera objektet kan man använda metoden clone(). Metoden equals() används för att ta reda på om två objekt är lika:
if (a.equals(b)) c = a.clone(); // riktig likhet och riktig tilldelning

Strängar

I Java finns en speciell klass för att hantera strängar: String. Genom att representationen av strängar göms här behöver man som programmerare inte bry sig om hur tecken lagras (d.v.s. att Unicode används i stället för ASCII). Några exempel på användning visar bäst klassens metoder:
//
// Användning av klassen String.
//
String a = "detta är en sträng ";
int i = 324;
a = a + i;                        // a = "detta är en sträng 324"
String b = a.substring(9, 18);    // b = "en sträng"
String c = a.substring(9);        // c = "en sträng 324" (till slutet)
char d = a.charAt(0);             // d = 'd'
int p1 = a.indexOf('e');          // p1 = 1 (första 'e')
int p2 = a.indexOf("ng");         // p2 = 16 (första "ng")
int l  = a.length();              // l = 22
Observera att metoden substring(startindex,endindex) returnerar en sträng som börjar i startindex och slutar i endindex-1.

Klassen String har metoder för konvertering av tal till sträng. Metoderna är överlagringar av varandra och heter

public static String valueOf(typ)

där typ är en av de inbyggda typerna.

Strängar av klassen String får inte ändras. Om man vill kunna ändra i en sträng kan man använda klassen StringBuffer i stället:

//
// Användning av klassen StringBuffer
//
StringBuffer a = new StringBuffer("detta är en sträng ");
int i = 324;
a.append ( i);        // a = "detta är en sträng 324"
a.setCharAt( 0, 'D'); // a = "Detta är en sträng 324"
a.insert(9, "inte "); // a = "Detta är inte en sträng 324"
Metoderna append() och insert() är överlagrade för alla typer: String.valueOf() används för konvertering till sträng (valueOf() i sin tur använder toString() om argumentet är en instans av en klass).

Typklasser för de inbyggda typerna

Ibland vill man kunna hantera variabler av de grundläggande typerna som objekt: man kan ha behov att placera numeriska värden i ett Vector-objekt eller man vill kanske kunna utföra en operation på en double.

För detta ändamål finns i Java till varje inbyggd typ en motsvarande typklass:

TYP      TYPKLASS
boolean  Boolean
byte     Byte
char     Character
double   Double
float    Float
int      Integer
long     Long
short    Short
Alla typklasser har följande konstruktorer och metoder (där Typklass är en typklass och typ själva typen):
Typklass( typ v);     // ex: Integer a = new Integer(12);
Typklass( String s);  // ex: Double d  = new Double("123.45");
String toString();    // konvertera till sträng, ex: String a = a.toString();
typ typValue();       // returnerar värdet, ex int i = a.intValue();
boolean equals(typ v);// sant om de två objekten har samma värde.
Typklasserna innehåller alltså metoder för konvertering mellan tal och strängar. Metoden toString() anropas automatiskt i uttryck som t.ex.
Integer a = new Integer(10);
String s = "Value of a is " + a;
System.out.println( "Value of a is " +  a );
Utskrift till terminalfönstret av strängar görs med anrop till System.out.println med en sträng som parameter. Faktum är att även vanliga tal, som inte lagras i en instans av en typklass, konverteras på samma sätt och med samma metod.

Konvertering av instanser av egna klasser till strängrepresentation

Som vi sett konverteras tal automatiskt med toString(). Om man skriver egna klasser bör man skriva toString (egentligen överlagra eftersom metoden finns i Object) så får man samma uppförande som de inbyggda typerna har.

Satser

Liksom i C/C++ används { och } för att dela in koden i block. På de ställen man kan skriva en Java-sats kan man skriva flera satser genom att omge dem med {}.

Javas satskonstruktioner liknar mycket C++. I detta avsnitt visas med exempel samtliga konstruktioner som finns i språket utom de som har med undantag att göra.

if..else

int i=0;
.
if (i > 0) i++;
else i--;
Skillnaden mellan Java och C/C++ är att villkorsuttrycket i Java måste vara av typen boolean. Värdena 0 och null t.ex. har inte värdet falskt utan man måste skriva "if (anObject!=null) {}" och "if (a!=0) {}".

for

int i;
for (i = 0; i < 10; i++)
 System.out.println( i );
for (int j=5; j >= 0; j--)
 {}
for (int i=0, j=100; i < 100; i++, j-=5)
 {}
Exemplen visar de vanligaste formerna av for-loopen. Observera att man kan göra flera saker i initierings- och inkrementeringsdelarna av loopen genom att använda komma.

Viktigt att veta är att variabler som deklareras i initieringsdelen existerar bara inne i loopen, inte efteråt.

while och do/while

int i = 10;
while (i > 0)
{
  Shape a = firstShape(i);
  if (a != null) 
    do 
     {
       a.do_it(); 
       a = nextShape(a);
     }
  while (a != null);
  i--;
}
Dessa konstruktioner ser ut som i de flesta programspråk. Viktigt att komma ihåg är att vi måste testa explicit mot 0 och null i villkorsutrrycken.

switch

switch ( i )
{
  case 1: i += 123; break;
  case 3: i += 456; break;
  case 5: i += 789; break;
  default: i = -1; break;
}
Variabler av typerna byte, char, short, int och long är tillåtna som case-labels. Notera att break är nödvändigt för att inte fortsätta exekveringen med nästa case.

break och continue

Nyckelorden break och continue används på samma sätt som i C/C++ med tillägget att man kan hoppa flera nivåer med hjälp av etiketterade break och continue. Man bör vara sparsam med användandet av dessa båda satser eftersom de ofta leder till svårläst och svårunderhållen kod; inga exempel förutom i switch ges.

Java-program - applikationer och applets

I Java finns ingenting som heter huvudprogram, subrutiner eller funktioner: allting är klasser och Java-program är en samling klasser som kommunicerar med varandra.

Java är ett generellt programspråk som kan användas också för applikationer utanför Internet. Skillnaden mellan en applet och en applikation ligger i hur och av vem programmen startas: en applet startas av en nätbläddrare medan en applikation startas i en speciell metod som heter main(). Flera av de klasser som tillsammans utgör en applikation kan innehålla main()-metoder: programmet startas i den main() som finns i den klass som anges på kommandoraden.

En applet å andra sidan administreras och körs av nätbläddraren. Programmeraren skapar en applet genom att skriva en subklass till Applet och ersätta en eller flera av följande metoder:


Dessa metoder anropas automatiskt av nätbläddraren.

Mer om hur man använder metoderna kommer i avdelningen om trådar. Metoden paint() är inte appletspecifik utan används av alla GUI-komponenter (se vidare avsnittet Java AWT och händelsehantering).

Oavsett om man har skrivit en applet eller en applikation kompileras programmet med javac:

javac myclass.java
En applikation startas sedan genom att man ger kommandot java följt av namnet på den klass man vill starta i (en klass som har en main-metod): java myclass.class medan appleten startas med appletviewer eller i nätbläddraren på det sätt vi tidigare visat.

Program som applikation och applet

Ibland vill man ha möjlighet att köra program som både applikation och applet. Detta går bra och standardlösningen är att skriva en applet med en main-metod (ingenting hindrar detta, nätbläddraren bryr sig inte om denna metod) som skapar och öppnar ett fönster. Vår välkomstapplet utbyggd så den kan användas både som applet och applikation:
import java.applet.*;
import java.awt.*;

public class WelcomeApplication extends Applet 
{

    public static void main( String[] args )
    {
	WelcomeApplication wapp = new WelcomeApplication();
	Frame frame = new Frame("Welcome");
	frame.add(wapp);
	frame.setSize( 500, 200);
	wapp.init();
	wapp.start();
	frame.show();
    }

    
    public void paint( Graphics g)
    {
	g.drawString("Welcome to the World of Internet Programming", 100, 100);
    }

};
Metoden main() skapar en instans av appleten, ett fönster (en Frame) att visa den i, placerar appleten i fönstret (metoden add), definierar fönsterstorleken, anropar init och start (som nätbläddraren annars skulle ha gjort), samt visar fönstret.

Lägg märke till att main() är en klassmetod (static) som inte returnerar någonting. Argumentet är en array av strängar - de argument som gavs på kommandoraden efter klassens namn. Eftersom ingenting kan returneras från main() kan man inte returnera ett värde till systemet den vägen: använd System.exit(n) där n är det du vill returnera om du måste meddela systemet något.

Applikationen ovan saknar någonting viktigt: den går inte att avsluta! Nästa avsnitt handlar om AWT (Abstract Windowing Toolkit) och hur man tar hand om användarens önskemål.

Sidospår: parametrar till applets

En applet refereras på en HTML-sida med hjälp av formatteringskommandot <APPLET>:
<APPLET CODE="filnamn" WIDTH=pixelbredd HEIGHT=pixelhöjd
        CODEBASE=appletdirectory ALIGN=sidplacering>
<PARAM NAME=parameter VALUE=värde>
</APPLET>
(dessa är de vanligaste kommandona, det finns fler)

Man måste alltid ange CODE, WIDTH och HEIGHT, resten kan utelämnas. CODEBASE skall vara den katalog där appleten ligger, inte själva appleten. Om CODEBASE utelämnas används den katalog där det refererande dokumentet finns.

Nyckelordet PARAM kan förekomma många gånger, där varje förekomst svarar mot ett parameternamn med värde. Parametrarna kan läsas av appleten, se exemplet Scribble). Metoden getParameter( String par ) returnerar värdet av parametern par i form av en sträng.