Klasser som objekter
Når man skal "presse" et såkaldt objektorienteret sprog mht. hvor objektorienteret det egentlig er, er en af mulighederne at undersøge i hvilken udstrækning klasserne er objekter. Det er det vi vil gøre i dette kapitel. Det forventes at læseren allerede har en god forståelse af klasse­begrebet.
Alt skal være objekter I et objektorienteret programmeringssprog er alt objekter og der er intet andet. Så enkelt kan det siges, men virkeligheden er trods alt ikke så enkel. At alt er objekter og der intet andet er, er i realiteten kun et absolut mål som man kan bruge til at måle sprogs objektorienterethed, snarere end til at kassere dem som kun værende objektbaserede.
I dette kapitel skal vi fokusere på objekt-egenskaberne ved klasser i java. Når alt skal være objekter i et objektorienteret sprog skal klasser det også. De skal være Singleton'er, der fabrikerer objekter når man sender en new-request til dem (se evt. Singleton Pattern og Factory Pattern).
Figur 1:
Instantiering ud fra en klasse, der er et objekt
På dette punkt kniber det lidt for java, men det er mere reglen end undtagelsen for objektorienterede sprog.
Som for objekter er der tre elementer vi skal have gjort rede for, før vi kan tale om at klasser optræder som objekter. De skal have tilstand (i.e. en datakerne), metoder og helst også konstruktorer. Alle tre er mulige med anvendelse af static i Java.
Static er en lap Lad det være sagt med det samme: Static er grimt — det er meget grimt. Static (dk.: statisk) er en lap på Java, som skal løse nogle problemer vedrørende java-klassers manglende objekt-egenskaber. Lappen løser i vid udstrækning disse problemer, men den bliver ikke mindre grim af det.
1. Klasse-variable
Ligesom et objekts datakerne består af instansvariable, består en klasses datakerne af klasse-variable. Når man erklærer en klassevariabel anvender man den samme syntaks som for instansvariable, og tilføjer det reserverede ord static foran typen, f.eks.:
Source 1:
Tal.java
public class Tal {
  private static int værdi;
  
  ...
  
}
Man kan også placere static foran private, men man plejer ikke at gøre det.
Lad os udbygge ovenstående eksempel, og se hvordan klassevariable fungerer:
Source 2:
Tal.java
public class Tal {
  private static int værdi;
  
  public Tal( int t ) {
    set( t );
  }
  
  public void set( int t ) {
    værdi = t;
  }
  
  public String toString() {
    return "[Tal: værdi=" + værdi + "]";
  }
}
Her har vi lavet en traditionel udbygning af Tal, så den fungerer som en integer-wrapper — og dog! værdi er ikke en instansvariabel, men en klassevariabel.
Følgende testanvendelse viser forskellen:
Source 3:
StaticTest.java
public class StaticTest {
  
  public static void main( String[] args ) {
    Tal t1 = new Tal( 3 );
    Tal t2 = new Tal( 5 );
    
    System.out.println( t1 );
    
    t2.set( 8 );

    System.out.println( t1 );
  }
}
[Tal: værdi=5]
[Tal: værdi=8]
Vi laver to instanser af Tal, og initialiserer dem tilsyneladende til to forskellige værdier: 3 og 5. Da vi udskriver t1, ser vi at den har den samme værdi som t2. Efterfølgende sætter vi t2 til en ny værdi: 8, og ved udskrift af t1, får vi bekræftet at t1 og t2 altid vil have samme værdi.
Fælles Det usædvanlige fænomen skyldes naturligvis static. En static variabel er fælles for alle instanser af en klasse.
Folding space Ovenstående anvendelse demonstrerer en grim egenskab ved static, da det ikke umiddelbart giver mening at objekters datakerne deles mellem dem - man føler sig hensat til science fiction og ormehuller; hvor objekternes datakerne er bøjet i rummet til det samme punkt — dybt mystisk!
Hvis vores statiske variabel værdi havde været public, ville det være muligt at tilgå den udefra. I så fald havde vi kunnet tilgå den fra main (og fra ethvert andet sted) ved at anføre klassenavnet før variabelnavnet, f.eks.:
Source 4:
Tal.værdi = 10;
Globale variable i Java! Dette gør public static variable globalt tilgængelige, og påstanden om at der ikke er globale variable i Java får visse problemer. Hvis man rigtig skal "gnide salt i såret" kan man lave en klasse Global og placere alle de globale variable man måtte ønske som public static variable i denne klasse!
Skal vi anvende statiske variable til noget mere fornuftigt, som rigtige klassevariable, skal vi have klasse-metoder ind i billedet.
2. Klasse-metoder
Man laver en klasse-metode analogt til en statisk variabel, ved at skrive det reserverede ord static foran metodens returtype.
Lad os se et eksempel:
Vi laver en klasse Pris, der skal repræsentere prisen på en vare. En fælles egenskab for alle priser, er at de skal pålægges den samme moms. Vi vælger derfor at gøre momsen til en klassevariabel:
Source 5:
Pris.java
public class Pris {
  /*
   * Klasse-variable/-metoder
   */
  private static double moms=0.25;
  
  public static void setMoms( double m ) {
    moms = m;
  }
  
  public static double getMoms() {
    return moms;
  }
  
  /*
   * Instans-variable/-metoder
   */
  private double pris;
  
  public Pris( double pris ) {
    this.pris = pris;
  }
  
  public String toString() {
    return "[Pris: excl=" + pris + ", incl=" + pris * ( 1 + getMoms() ) + "]";
  }
}
Til denne variabel knytter vi set- og get-metoder på sædvanlig vis.
Da vi endnu ikke har set på konstruktorer til klasser, er det naturlig at initialisere moms i erklæringen.
Rodet kode En af de grimme ting ved static i Java, er at man selv skal opdele koden i klasse-del og instans-del, som det er gjort ovenfor. Det hele står som udgangspunkt rodet sammen, med mindre man pålægger sig selv en form for opdeling.
Som for statiske variable kan man placere static foran public, men det plejer man ligeledes ikke at gøre.
Bryde indkapsling Bemærk at det ikke er strengt nødvendigt at kalde getMoms i toString-metoden. Den statiske variabel moms er private, og derfor kun tilgængelig inden for selve klassen. Dette udelukker derfor ikke at vi kunne have skrevet
Source 6:
... pris*( 1 + moms ) ...
da toString-metoden netop er erklæret i klassen.
Ser klassen som objekt Vi har valgt at bruge en get-metode for at det hele bliver mere objektorienteret. Havde vi brugt den direkte tilgang til moms, ville vi have tilgået klassens datakerne direkte fra instanserne - husk at vi vil betragte klassen som et objekt, da vores ultimative mål er at se/behandle alt som objekter.
Lad os se en testanvendelse af denne klasse:
Source 7:
StaticTest.java
public class StaticTest {
  
  public static void main( String[] args ) {
    Pris p1 = new Pris( 17.75 );
    Pris p2 = new Pris( 57.50 );
    
    System.out.println( p1 );
    System.out.println( p2 );
    
    Pris.setMoms( 0.30 );

    System.out.println( p1 );
    System.out.println( p2 );
  }
}
[Pris: excl=17.75, incl=22.1875]
[Pris: excl=57.5, incl=71.875]
[Pris: excl=17.75, incl=23.075]
[Pris: excl=57.5, incl=74.75]
Man ser hvordan klassemetoden setMoms sætter klassenvariabelen moms; hvilket indirekte kan aflæses af de nye priser på de to varer.
Men bemærker også, at statiske metoder er globalt tilgængelige på samme måde som statiske variable (instance-metoden i Singleton Pattern er et eksempel på en anvendelse af dette)
3. Klasse-konstruktorer
Når vi skal tale om klasse-konstruktorer må vi bøje begrebet lidt — for vi instantierer jo ikke klasser — de er der bare når vi skal bruge dem. Vi skal senere se nærmere på hvordan klasser kan komme "ind i vores program", men indtil videre vil vi tale om "klasse-instantiering", som noget der sker når programmet starter. [Dette har jeg ikke skrevet om endnu]
Hvis vi f.eks. ønskede at initialisere moms fra eksemplet ovenfor, i en klasse-konstruktor, kunne det gøres på følgende måde:
Source 8:
Pris.java
public class Pris {
  /*
   *  Klasse-variable/-metoder
   */
  private static double moms;
  
  static {
    setMoms( 0.25 );
  }
  
    ...
  
}
Statisk initialise-ringsblok Igen indgår det reserverede ord static i syntaksen - og denne gang helt alene. Den statiske initialiseringsblok, der er indholdt i tuborg-paranteserne, bliver udført "før klassen bliver brugt første gang".
Da vi ikke selv instantierer klassen, er det naturligt nok ikke muligt at angive parametre til klasse-konstruktoren.
Med statiske initialiseringsblokke er vi ved at være ude i det yderste af Java's "klasser som objekter". En af de mere rodede ting med disse blokke, er at man kan have vilkårlig mange af dem i den samme klasse. De vil i så fald blive udført alle sammen, i den rækkefølge de står "før klassen bruges første gang". Man kunne kalde det en "fragmenteret" konstruktor, og det er ved at være temlig grimt.
3.1 Initialiseringsblokke
Uden static Mens vi er ved de knap så pæne sider af Java, så er der også initialiseringsblokke, som ikke er statiske. Disse udføres før enhver konstruktor i forbindelse med instantiering. Syntaktisk fjerner man blot det reserverede ord static, og lader tuborg-paranteserne stå alene - imellem metoder og variable.
Ligesom de statiske kan man have flere af dem, og de udføres i den rækkefølge de står.
Man kan placere initialiseringer der er fælles for alle konstruktorerne i disse blokke, og på den måde løse problemet med at både this- og super-kald kun kan udføres som den første linie i en konstruktor. Med en initialiseringsblok kan this-kald af default-konstruktoren i samtlige konstruktorer udelades.
4. Static eksempler fra Java
Inden vi er nået så langt som til at se på static, har vi fra den Grundlæggende Programmering allerede brugt static flere gange. Vi skal i det følgende se nogle eksempler på dette, og hvad de hele tiden har betydet.
4.1 public static void main ...
Hvor tit har man ikke set disse ord!?
main er en statisk metode, og det er ikke muligt at lave en applikation i Java uden.
Den virtuelle maskine starter sin udførelse af et Java-program med at kalde denne metode. Da der fra programmets start ikke findes nogen instanser af nogen klasse, må programmet nødvendigvis starte statisk!
Mens vi er ved main-metoden, så lad os se på en anden ting, man også plejer at forbigå, når man først lærer at lave main-metoder, nemlig parameteren.
Man kan prøve at udskrive indholdet af det arrays af String's man får med som parameter:
Source 9:
StaticTest.java
public class StaticTest {
  
  public static void main( String[] args ) {
    System.out.println( "Antal argumenter: " + argv.length );
    
    for ( int i=0; i<argv.length; i++ )
      System.out.println( "argv[" + i + "] = " + argv[i] );
  }
}
Antal argumenter: 0
Ud over, at jeg i programmet antyder, at det har noget med argumenter at gør, er vi ikke blevet meget klogere. Hvad er det for argumenter? — Der er jo ingen!
Hvis man udelukkende programmerer Java fra Eclipse, eller et andet udviklingsmiljø, vil man muligvis være lykkelig uvidende om argumenter til et Java-program. Grundlæggende udføres et Java-program ved at køre den virtuelle maskine med følgende kommandolinie i Command Prompten:
java StaticTest
Forrest er navnet på Java-fortolkeren: java.exe og dernæst navnet på den klasse som indeholder vores main-metode. Efter dette er det muligt, at anføre argumenter til programmet som sendes ind til main-metoden, via args-parameteren.
F.eks.: vil følgende kommandolinie:
java StaticTest dette er en test
give følgende udskrift:
Antal argumenter: 4
argv[0] = dette
argv[1] = er
argv[2] = en
argv[3] = test
For en mere udførlig gennemgang af hvordan man kan arbejde med Java-programmer fra Command Prompten, henvises der til kapitlet: "Udførelse af Java-programmer".
4.2 System.out.println ...
Grimt I virkeligheden er ovenstående linie nok det mest mystiske man udsætter folk for, når de skal lære at programmere. "Hvorfor skal man skrive så meget?".
Med vores viden om static i Java kan vi nu forstå det. System er en klasse i java.lang. I denne klasse er der en public statisk variabel out, der er en instans af klassen PrintStream. Det er denne instans som vi kalder metoden println på.
Gråd Det er så smukt at man græder — eller er det fordi man græder af grin! Det er noget af det grimmeste i Java.