Forrige kapittel Neste kapittel

Kapittel 4 — Kontrollstrukturer, del 1

Dersom ikke annet er bestemt, vil instruksjonene i et program bli utført i den rekkefølge de står, altså sekvensielt. For et applikasjonsprogram begynner utførelsen med første instruksjon i main-metoden. I praksis vil det imidlertid være kun for de aller enkleste programmene at instruksjonene alltid skal utføres i en bestemt rekkefølge når programmet blir kjørt, en rekkefølge som programmereren kjenner til når programmet blir skrevet. Svært ofte vil det være ønskelig å kunne ha alternative rekkefølger for instruksjonene, avhengig av hva som skjer når programmet blir kjørt. I dette kapitlet og det neste skal vi se nærmere på hvilke muligheter vi har for å få til dette.

Ønskelige endringer for rekkefølgen for utførelsen av instruksjonene kan være av to hovedtyper:

Vi skal ta for oss språkkonstruksjoner som du kan bruke for å oppnå disse ønskene.

if-setninger og if-else-setninger

Vi har allerede (se kapittel 2) tatt for oss den enkle if-setningen som har formen

  if ( betingelse )
    instruksjon;

Vi kan si at her oppnår vi valget mellom alternativet at noe utføres og at det ikke utføres. I mange situasjoner er det imidlertid slik at dersom ett alternativ ikke skal utføres, så er det et annet som skal utføres isteden. Det får vi til ved å utvide if-setningen med en else-grein. En slik konstruksjon har denne formen:

  if ( betingelse )
    instruksjon1;
  else
    instruksjon2;

Merk at det også her, som alltid ellers, er slik at hver instruksjon skal avsluttes med semikolon. En slik if-else-setning har følgende virkning: Dersom betingelse har verdi true, vil instruksjon1 bli utført, men ikke instruksjon2. Dersom betingelse har verdi false, vil ikke instruksjon1 bli utført, men derimot instruksjon2.

Eksempel 1

Vi antar at int-variablene a, b og max er blitt deklarert, samt at a og b er blitt tilordnet verdi. Vi ønsker at variabelen max skal få samme verdi som den største av a og b. Da kan vi skrive følgende kode:

  if ( a <= b )
    max = b;
  else
    max = a;

Merk deg at vi kunne oppnådd samme resultat ved å skrive slik:

  if ( a <= b )
    max = b;
  if ( a > b )
    max = a;

Men dersom vi hadde skrevet slik, ville begge sammenlikningsoperasjonene a <= b og
a > b bli utført, noe som er unødvendig. For dersom den første ikke er oppfylt, så nødvendigvis den andre være oppfylt! if-else-alternativet er derfor mer effektivt (siden vi sparer oss for en sammenlikningsoperasjon), og bør på grunn av dette foretrekkes. Dessuten har det en klarere logisk struktur.

Oppgave 1

Da du skrev program for oppgave 14 i kapittel 2, brukte du trolig to if-setninger for å få riktig utskrift fra programmet. Gjør om programmet ditt slik at du isteden bruker en if-else-setning.

***

Det er tillatt å utvide if-else-strukturen med flere greiner, slik det er vist i neste eksempel.

Eksempel 2

For porto på innenlandske brev (A-post) gjelder (2014) følgende takster:

Vekt i g til og med2050100350 10002000
Porto10.0015.0019.0030.00 60.0095.00

Største tillatte vekt er altså 2000 gram. Vi tenker oss at int-variabelen vekt inneholder vekten til et slikt brev (i antall hele gram). Vi skal skrive kode som tilordner variabelen porto riktig portoverdi. Dersom brevet er for tungt, skal porto tilordnes verdien 0.0 som indikasjon på dette. Følgende java-kode kan da brukes:

  double porto = 0.0;
  if ( vekt <= 20 )
    porto = 10.00;
  else if ( vekt <= 50 )
    porto = 15.00;
  else if ( vekt <= 100 )
    porto = 19.00;
  else if ( vekt <= 350 )
    porto = 30.00;
  else if ( vekt <= 1000 )
    porto = 60.00;
  else if ( vekt <= 2000 )
    porto = 95.00;

Variabelen porto vil bli tilordnet verdi så snart programutførelsen kommer til en if-test som resulterer i true. Eventuelle gjenstående if-tester vil da bli hoppet over, siden det står else foran alle disse. Dersom variabelen vekt har en verdi større enn 2000, vil ingen av if-testene resultere i true. Variabelen porto vil i så fall beholde sin startverdi 0.0. Legg ellers merke til at dersom vi ikke hadde skrevet else foran alle if-tester (unntatt den første), ville programkoden ikke virket riktig. Dersom da for eksempel vekt hadde hatt verdien 18, ville porto fått verdien 145.00, mens riktig verdi (som den vil få slik det står ovenfor), ville være 9.00.

Oppgave 2

Lag et applikasjonsprogram som leser inn vekten til et brev i antall hele gram og som skriver ut riktig porto (på grunnlag av portotabellen ovenfor). Dersom brevet er for tungt, skal melding om dette skrives ut istedenfor portoen. (Gjør bruk av if- og/eller if-else-setninger.)

Betingelsesoperatoren ?:

Operatoren som består av de to symbolene ? og : kan brukes som et alternativ til en if-else-instruksjon (som har bare én else-grein.)

Eksempel

Istedenfor if-else-instruksjonen i eksempel 1 ovenfor kunne vi skrevet

  max = ( a <= b ) ? b : a;

Det at ?: er en operator, betyr at den returnerer en verdi. Verdien blir i dette tilfelle tilordnet variabelen max. Hvilken verdi som blir returnert er bestemt av det logiske uttrykket foran ?. Dersom dette uttrykket har verdi true, er det verdien bak ? som blir returnert, i vårt eksempel b. I motsatt tilfelle er det verdien bak : som blir returnert, i vårt eksempel a.

Blokker

En blokk i et java-program er en programbit som er avgrenset av krøllparenteser { og }. En blokk kan inneholde ingen, én, eller flere instruksjoner. Vanligvis inneholder den flere instruksjoner. Vi kan bruke en blokk alle steder der vi ellers kan bruke én instruksjon. Dersom det for eksempel er mer enn én instruksjon som skal utføres i tilfelle en if-betingelse er oppfylt, putter vi instruksjonene inn i en blokk.

Eksempel 3

Anta at vi i eksempel 1 ovenfor, i tillegg til det som er gjort med å gi variabelen max den største av verdiene a og b, også ville registrere i en tekst hvilken det var av a eller b som hadde størst verdi. Da kunne vi endre koden til det følgende:

  String størst = "";
  if ( a <= b )
  {
    max = b;
    størst = "b har størst verdi";
  }
  else
  {
    max = a;
    størst = "a har størst verdi";
  }

Noen vanlige programmeringsfeil

Programmeringsfeil kan vi dele inn i to hovedtyper: Syntaksfeil og logiske feil.

Syntaksfeil er brudd på programmeringsspråkets regler, dets syntaks. De svarer til grammatiske feil i vanlig språk. Feilene fører til at vi ikke får kompilert programmet. Kompilatoren skriver ut feilmeldinger når vi prøver å kompilere det. Feilene blir derfor også kalt kompileringsfeil. Feilmeldingene inneholder opplysninger som gir oss god hjelp til å finne feilene og rette dem opp.

Logiske feil er feil som gjør at programmet ikke virker slik vi har til hensikt at det skal gjøre. Det virker kanskje riktig for det meste av det det skal utføre, men ikke for alt. Slike feil kan være ganske vanskelige å finne. Vi skal i denne omgang ta for oss noen typiske feil som det er vanlig å gjøre i forbindelse med if-setninger og if-else-setninger.

Vanlig feil 1: Glemme nødvendige blokkparenteser

Dersom det til en betingelse bare hører til én enkelt instruksjon, er det ikke nødvendig å ramme inn instruksjonen med krøllparenteser { og } slik at vi får opprettet en blokk. Dette er for eksempel tilfelle med instruksjonen

  if (antall > 0)
    snitt = sum / antall;

der vi tenker oss at vi skal regne ut et snitt etter å ha fått summert opp et antall verdier. Hensikten med betingelsen er å unngå divisjon med null. Vi hadde fått nøyaktig samme virkning om vi isteden hadde skrevet instruksjonen på denne måten:

  if (antall > 0)
  {
    snitt = sum / antall;
  }

der det er satt inn blokkparenteser. Dersom det er mer enn én instruksjon som hører til if-betingelsen, er vi nødt til å ramme inn instruksjonene med krøllparenteser for at programmet skal virke riktig. Som et eksempel på dette går vi tilbake til kapittel 2, oppgave 5. Oppgaven går ut på å lage et program som leser inn radien til en sirkel og skrive ut sirkelens areal. Som radius er det mulig å skrive inn et hvilket som helst desimaltall, også negative tall. Vi vet at ingen sirkel har negativ radius og at det derfor også er meningsløst å snakke om arealet til en slik sirkel. Nå som vi har lært om if-setninger, tenker vi oss at vi skal legge inn en test på radien før vi beregner og skriver ut arealet. Glemmer vi krøllparenteser, kan koden for dette komme til å se ut slik:

 if (radius >= 0)
   areal = pi * radius * radius;
   JOptionPane.showMessageDialog(null, "Areal: " + areal);

Men dette vil virke feil i tilfelle radien er negativ! Utskriftsinstruksjonen vil nemlig bli utført også for negativ verdi av radius, for det er bare instruksjonen for beregning av areal som er knyttet til if-setningen. For å få korrekt program også for negative radier, må vi skrive slik:

 if (radius >= 0)
 {
   areal = pi * radius * radius;
   JOptionPane.showMessageDialog(null, "Areal: " + areal);
 }

Vanlig feil 2: Semikolen etter if-betingelse

En vanlig feil, særlig for nybegynnere, er å plassere et semikolon ; på slutten av if-linja, slik som i følgende eksempel:

 if (radius > 0);
 {
   areal = pi * radius * radius;
   JOptionPane.showMessageDialog(null, "Areal: " + areal);
 }

Dette gir ingen kompileringsfeil, men er likeverdig med følgende kode:

 if (radius > 0) {//Tom blokk!};
 {
   areal = pi * radius * radius;
   JOptionPane.showMessageDialog(null, "Areal: " + areal);
 }

Feilen kan være svært vanskelig å oppdage. Som du ser, vil det i virkeligheten være en tom blokk, det vil si ingen instruksjon, som er knyttet til if-betingelsen, mens den blokka som du hadde tenkt skulle høre til betingelsen ikke vil være knyttet til noen betingelse i det hele tatt og derfor vil bli utført uansett.

Vanlig feil 3: else-setning knyttet til gal if-betingelse

Vi tenker oss at det fra brukeren skal leses inn et månedsnummer, det vil si en verdi fra 1 til 12. Brukeren skal få melding tilbake om innlest verdi kunne godtas eller ikke. Som utgangspunkt tar vi for oss følgende kodebit for dette:

  String måned = JOptionPane.showInputDialog("Skriv månedsnummer:");
  int mnd = Integer.parseInt(måned);
  if (mnd >= 0)
    if (mnd <= 12)
      JOptionPane.showMessageDialog(null, "Månedsnummer OK");
  else
    JOptionPane.showMessageDialog(null, "Gal verdi. Månedsnummer må være fra 1 til 12");

Det vi kan lure på her, er hvilken av if-setningene else-setningen hører til. Slik innrykkene er plassert, ser det ut til at den hører til den første if-setningen. Regelen for dette er imidlertid slik: En else-setning hører alltid til den nærmest foregående if-setning som ikke allerede er matchet med en else-setning. I dette tilfelle er det faktisk den siste av if-setningene. Koden ovenfor er nemlig likeverdig med følgende, der det også er korrekt bruk av innrykk (indentering):

  String måned = JOptionPane.showInputDialog("Skriv månedsnummer:");
  int mnd = Integer.parseInt(måned);
  if (mnd >= 0)
    if (mnd <= 12)
      JOptionPane.showMessageDialog(null, "Månedsnummer OK");
    else
      JOptionPane.showMessageDialog(null, "Gal verdi. Månedsnummer må være fra 1 til 12");

Ser vi litt nøyere på denne koden, vil vi se at det ikke vil bli skrevet ut noe som helst dersom innlest månedsnummer er 0 eller negativt. For verdier fra 1 til 12 vil korrekt tilbakemelding bli skrevet ut, mens det bare er for verdier større enn 12 at meldingen i else-delen vil bli skrevet ut. For styre hvilken if-setning en bestemt else-setning skal tilhøre, kan vi legge inn parenteser, slik som i følgende kode:

  String måned = JOptionPane.showInputDialog("Skriv månedsnummer:");
  int mnd = Integer.parseInt(måned);
  if (mnd >= 0)
  {
    if (mnd <= 12)
      JOptionPane.showMessageDialog(null, "Månedsnummer OK");
  }
  else
    JOptionPane.showMessageDialog(null, "Gal verdi. Månedsnummer må være fra 1 til 12");

Slik det nå står, vil else-setningen vår høre til den første if-setningen. Men vil koden nå virke riktig? La oss sjekke i hodet: For verdier mindre enn eller lik null vil feilmeldingen bli skrevet ut. For verdier fra 1 til 12 vil vi også få riktig utskrift om at verdien er OK. Men hva med verdier større enn 12? Da vil vi faktisk ikke få skrevet ut noe som helst! En mulig måte å fikse dette på, kan være følgende:

  String måned = JOptionPane.showInputDialog("Skriv månedsnummer:");
  int mnd = Integer.parseInt(måned);
  if (mnd >= 0)
  {
    if (mnd <= 12)
      JOptionPane.showMessageDialog(null, "Månedsnummer OK");
    else
      JOptionPane.showMessageDialog(null, "Innlest verdi er større enn 12!");
  }
  else
    JOptionPane.showMessageDialog(null, "Innlest verdi er mindre enn 1!");

Merk deg for øvrig at vi aldri kan ha en løsrevet else-setning. Den være knyttet til en foregående if. Hadde vi derfor i koden ovenfor droppet krøllparentesene, ville vi fått kompileringsfeil.

Merknad Problemet ovenfor, der innlest verdi skal være mellom to grenser, kan vi løse på en annen måte når vi har lært om logiske operatorer, det kommer seinere.

Vanlig "feil" 4: Unødvendig testing av logiske variable

Under Vanlig feil 1 ovenfor ble det som eksempel brukt beregning av snitt for et antall verdier som er telt opp og summert. Anta nå at vi ønsker å informere brukeren om det opptelte antallet er et partall eller ikke. En mulig "rett fram"-kodebit for dette kan være følgende:

  boolean partall;
  if ( (antall % 2) == 0 ) //beregner rest ved divisjon med 2
    partall = true;
  else
    partall = false;
  if (partall == true)
    JOptionPane.showMessageDialog(null, "Antallet er et partall.");
  else
    JOptionPane.showMessageDialog(null, "Antallet er et oddetall.");

Det er ikke noe feil i denne koden, men det er en del unødvendig testing. Merk deg for det første at uttrykket

  (antall % 2) == 0

har verdi true eller false. Istedenfor den første if-else kunne vi derfor kort og godt skrevet

  boolean partall = (antall % 2) == 0;

Det er for øvrig også tillatt å droppe parentesene som er brukt, men de bidrar til å klargjøre strukturen i instruksjonen.

I den andre if-testen har variabelen partall allerede fått riktig verdi true eller false. Det er ikke nødvendig å teste på den. Derfor kunne vi kort og godt skrevet

  if (partall)
    JOptionPane.showMessageDialog(null, "Antallet er et partall.");
  else
    JOptionPane.showMessageDialog(null, "Antallet er et oddetall.");

Repetisjonsstrukturer (løkker)

En løkke består av én eller flere instruksjoner som skal utføres gjentatte ganger inntil en eller annen betingelse ikke lenger er oppfylt.

I java finnes det flere typer av slike løkker. Den mest generelle av dem er while-løkka. Den har følgende form:

  while ( betingelse )
    instruksjon;

Tilsvarende som betingelsen i en if-setning må også betingelsen i en while-løkke være et logisk uttrykk, altså et uttrykk som har verdi true eller false. Det som ovenfor er kalt instruksjon kan selvsagt være en blokk bestående av flere instruksjoner. For øvrig er det slik både her og ellers, at selv om det står bare én instruksjon, er det tillatt å plassere denne inni en blokk, slik at vi isteden kunne skrive

  while ( betingelse )
  {
    instruksjon;
  }

En while-løkke virker på følgende måte: Instruksjonen(e) som står etter betingelsen utføres om og om igjen så lenge betingelsen har verdi true. (Foran hver ny utførelse blir det sjekket om betingelsen fortsatt har verdi true.) Det betyr blant annet at dersom betingelsen har verdi false når programutførelsen kommer til while-løkka, så blir ikke instruksjonen(e) som hører til while-løkka utført i det hele tatt.

Kommer programutførelsen inn i while-løkka, så kommer den ikke ut av den igjen før betingelsen skifter verdi til false. Skjer ikke dette, vil programmet 'henge seg opp' fordi vi har fått det som kalles en evig (eller uendelig) løkke. Vi må derfor sørge for at det er instruksjoner som endrer på betingelsen slik at den får verdi false når løkka ikke skal utføres flere ganger.

while-løkker bruker vi når vi ikke på forhånd vet hvor mange ganger instruksjonen(e) som står inni løkka skal gjentas, eventuelt om de skal utføres en eneste gang.

Eksempel 4

Vi skal lage et program som gjentatte ganger leser inn et antall sekunder og skriver ut dette konvertert til timer, minutter og sekunder. Programmet skal avsluttes når brukeren skriver et negativt tall.

Merknad: Begrepet algoritme
Ordet algoritme brukes generelt som betegnelse på en bestemt framgangsmåte som kan følges for å oppnå et bestemt resultat. Vi kan tenke på en algoritme som en form for 'kokebokoppskrift'. I en kokebokoppskrift er det imidlertid som regel litt rom for 'slumsing': vi trenger ikke alltid følge den helt til punkt og prikke for å kunne oppnå et bra resultat. En algoritme gir ikke rom for noen 'slumsing': Vi må følge den til punkt og prikke for at resultatet skal være garantert.

For det nevnte programmet vårt kan vi sette opp følgende algoritme uttrykt i pseudo-kode, der variabelen tid er det antall sekunder som skal konverteres:

  while ( tid >= 0 )
  {
    < konverter tid >
    < skriv ut resultat >
    < les inn tid >
  }

Her støter vi imidlertid på et problem: variabelen tid brukes for å teste om programutførelsen skal gå inn i while-løkka. Det krever at den på forhånd har fått en verdi som det kan testes på. En mulighet er at brukeren skriver inn en tid allerede før programmet kommer til while-løkka. Med denne løsningen kan vi utvide pseudo-koden vår til følgende skisse:

  int tid;
  < les inn tid >
  while ( tid >= 0 )
  {
    < konverter tid >
    < skriv ut resultat >
    < les inn tid >
  }

En annen mulighet er at vi (i egenskap av programmerer) gir variabelen tid en startverdi som sikrer at programmet kommer inn i while-løkka. Denne startverdien vil i så fall bare være en hjelpeverdi for å komme inn i løkka, ikke en verdi som programmet skal konvertere til timer, minutter og sekunder. For å unngå at dette skjer, må vi endre noe på den opprinnelige løkka. Vi kan for eksempel skrive som skissert i følgende pseudo-kode:

  int tid = 0;
  while ( tid >= 0 )
  {
    < les inn tid >
    if ( tid >= 0 )
    {
      < konverter tid >
      < skriv ut resultat >
    }
  }

Begge de nevnte muligheter kan brukes av programmet vårt. De er stort sett likeverdige, men ser vi litt mer kritisk på dem, ser vi at i den siste versjonen må vi utføre testen
tid >= 0 to ganger. Det er unødvendig i den første versjonen. Den vil derfor være å foretrekke dersom vi ønsker at programmet skal være mest mulig effektivt med hensyn på kjøretid.

Et annet problem er hvordan vi skal få foretatt selve konverteringen av et innlest antall sekunder til timer, minutter og sekunder. Til det kan vi bruke javas divisjonsoperator og restoperator (modulusoperator). Fra tidligere vet vi at dersom begge operander er av heltallstype, så vil divisjonsoperatoren / utføre heltallsdivisjon. Dersom variabelen tid inneholder det innleste antall sekunder, vil derfor uttrykket tid / 3600 gi antall hele timer, siden det er 3600 sekunder i hver time. Antallet timer må vi selvsagt lagre i en variabel til bruk ved utskriften. Når antall hele timer er beregnet, må vi finne ut hvor mange overskytende sekunder det er, det vil si antall sekunder som i neste omgang skal fordeles på minutter og sekunder. En mulighet for å finne dette overskytende antallet, er at vi skriver instruksjonen

  tid = tid - timer * 3600;

der timer er variabelen vi har brukt for å lagre antall hele timer som er regnet ut. Men det er enklere og mer elegant å bruke javas operator % for beregning av rest ved heltallsdivisjon. For antall overskytende sekunder er nettopp resten som vi får ved divisjonen for å beregne antall hele timer. Istedenfor instruksjonen ovenfor, kan vi derfor heller skrive

  tid = tid % 3600;

På tilsvarende måte går vi fram når vi skal finne antall minutter og til slutt det overskytende antall sekunder. Forskjellen blir at vi nå må dividere med andre tall.

I oppgave 3 nedenfor får du som oppgave å lage et fullstendig program på grunnlag av det som er skissert ovenfor.

Design-strategi for løkker

Det å kunne skrive en korrekt løkke er ikke noen lett oppgave for nybegynnere i programmering. Her er noen retningslinjer som kanskje kan være til hjelp:

  1. Identifiser hvilke instruksjoner som skal utføres gjentatte ganger.
  2. Pakk disse instruksjonene inn i en løkke på denne måten (i tilfelle det er while-løkke du skal bruke):
      while (true)
      {
        < Instruksjoner >;
      }
      
  3. Skriv kode for løkke-betingelsen og legg til passende instruksjoner for løkke-kontrollen slik at du ender opp med noe omtrent som i følgende skisse:
      while (løkke-betingelse)
      {
          < Instruksjoner >;
          < Tilleggsinstruksjoner for løkke-kontrollen >;
      }
      

Som du har sett av diskusjonen ovenfor vil imidlertid detaljene og rekkefølgen av instruksjonene avhenge av hvordan vi velger å løse enkelte av detaljene knyttet til startverdier og løkkebetingelse.

Oppgave 3

Skriv et fullstendig program for eksempel 4 ovenfor.

Oppgave 4

Lag en forbedret versjon av programmet fra oppgave 3. Forbedringen skal gå ut på at dersom det innleste antall sekunder er mindre enn 3600, er det bare antall minutter og sekunder som skal skrives ut. Dersom det innleste antall sekunder er mindre enn 60, skal heller ikke antall minutter skrives ut. Da er det altså bare det innleste antall sekunder som skal skrives ut. (For å få til denne forbedringen, er det greiest å opprette en egen String-variabel for utskriften.)

Oppgave 5

Lag en ny versjon av programmet du lagde for oppgave 2 ovenfor. I den nye versjonen skal innlesing av brevets vekt og utskrift av porto utføres gjentatte ganger inntil det leses inn en negativ verdi for vekten.

Oppgave 6

I kapittel 2, oppgave 5 skulle du lage et program som leste inn radien til en sirkel og skrev ut sirkelens areal. Lag en ny versjon av dette programmet ved at programmet også beregner og skriver ut sirkelens omkrets. (Formelen for omkrets er 2 * Π * r.) Dessuten skal innlesing av radius og utskrift av areal og omkrets foretas gjentatte ganger, inntil det leses inn en negativ verdi for radien. (Utskrift av areal og omkrets kan gjøres i en og samme dialogboks.)

Merknad

En vanlig situasjon er at vi på forhånd, det vil si allerede når vi skriver programmet, vet hvor mange ganger en løkke skal gjennomløpes. For å styre løkka er det da naturlig å bruke en tellevariabel som teller opp antall løkkegjennomløp. Det går greit å bruke en while-løkke i slike situasjoner, men et bedre alternativ er det å bruke en for-løkke, som nettopp er spesialtilpasset slike situasjoner. Den løkketypen tar vi for oss i begynnelsen av neste kapittel.

Formatering av utskrift

Som vi har sett eksempler på, blir desimaltall (det vil si verdier av type double) i utgangspunktet skrevet ut med maksimalt antall desimaler, som er inntil 16 desimaler. Unntaket er at eventuelle 0-er på slutten blir sløyfet i utskriften. Ofte vil det virke meningsløst å få skrevet ut desimaltall med 16 desimaler. Som regel ønsker vi selv å kunne kontrollere hvor mange desimaler som skal skrives ut. Til dette formål kan vi bruke klassen DecimalFormat fra javas klassebibliotek. Den ligger i pakken java.text. Vi må derfor importere den ved at vi forrest i java-fila skriver

import java.text.DecimalFormat;

når vi vil bruke denne klassen i et program.

Formatering dreier seg om hvordan vi ønsker at utskriften skal se ut. Formatering berører ikke verdien til den variabelen som skal skrives ut. Formatering dreier seg også om tilpasning til lokale forhold. I Norge for eksempel, bruker vi komma som desimaltegn. Ved gruppering av sifre i grupper på tre ved utskrift av tall med mange sifre, brukes i Norge mellomrom mellom gruppene, ikke komma som brukes i USA. Et eksempel på standard formatering hos oss i Norge er

  32 476,3789

Ved bruk av java-klassen DecimalFormat finnes det mange muligheter for å bestemme formateringen. Dersom vi ikke har angitt noe annet, vil DecimalFormat bruke den standard som gjelder i det land der java er installert. For eksempel vil hos oss i Norge gruppering og desimaltegn være som vist i eksemplet ovenfor. For angivelse av formatering for øvrig, finnes det noen spesialtegn. De viktigste er som følger:

0Indikerer et siffer. Sifferet vises alltid, også når det er 0.
#Indikerer et siffer. Sifferet vises ikke når det er ledende 0 eller en 0 på slutten.
.Indikerer plassering av desimaltegn. Desimaltegn er i Norge komma.
,Indikerer plassering av gruppeseparator. Gruppeseparator er i Norge mellomrom.
%Indikerer multiplikasjon med 100 og visning som en prosentstørrelse.

Når vi skal bruke klassen DecimalFormat til å formatere en double-variabel x (som på forhånd er blitt tilordnet en verdi), må vi skrive kode på følgende form (uttrykt i pseudo-kode):

  String formateringsstreng = < en streng som bestemmer ønsket formatering >;
  DecimalFormat formateringsobjekt = new DecimalFormat( formateringsstreng );
  String utskrift = formateringsobjekt.format( x );

String-variabelen utskrift vil nå inneholde verdien til x formatert slik vil ønsker og kan brukes til utskrift på vanlig måte, for eksempel i en meldingsboks.

Merknad

På grunnlag av det vi lærte i kapittel 3 om klasser, objekter og konstruktører, kan vi nå tolke instruksjonen

  DecimalFormat formateringsobjekt = new DecimalFormat( formateringsstreng );

på følgende måte: Vi oppretter et objekt av type DecimalFormat. Strengen formateringsstreng blir i form av konstruktørparameter overført som verdi til et av datafeltene i objektet. Variabelen formateringsobjekt blir satt til å referere til (peke på) objektet.

Eksempler på formateringsstrenger:

"0.00"  //gir to desimaler som alltid vises, selv om de er 0
"0.00#"  //gir inntil tre desimaler. Den siste vises ikke dersom den er 0
"##,##0.0000"  //vil blant annet gi 32 476,3789

Typekonvertering (casting)

Noen ganger ønsker vi for eksempel at et heltall skal oppfattes som et desimaltall.

Eksempel 5

Vi ønsker å regne ut snittet av noen heltall i form av en desimaltallsverdi. Vi tenker oss da at int-variabelen sum inneholder summen av n slike heltall. Dersom vi da skriver for eksempel

double snitt = sum / n;

for å beregne snittet, vil det på høyre side av tilordningsoperatoren bli foretatt en heltallsdivisjon, siden både sum og n er av type int. Dersom for eksempel sum er lik 17 og n er lik 3, vil resultatet av divisjonen bli 5. Ved tilordningsoperasjonen vil 5 automatisk bli konvertert til type double, slik at snitt vil få verdien 5.0.

For å få foretatt en divisjon mellom desimaltall ved beregning av snittet, kan vi før divisjonen foreta en konvertering av enten sum eller n til type double. Har vi skrevet kode for å konvertere den ene av disse, vil også den andre automatisk bli konvertert. Divisjonen blir da mellom desimaltall, slik at vi får beregnet snittet på den måten vi ønsker.

Typekonvertering får vi generelt utført ved at vi rett foran den variabel som ønskes konvertert skriver inni en parentes den datatypen vi ønsker konvertering til. I vårt tilfelle får vi derfor riktig snittberegning ved å skrive instruksjonen

double snitt = (double) sum / n;

Merknader Operatoren () for typekonvertering har høyere prioritet enn divisjon. Typekonverteringen vil derfor bli utført før divisjonen. (Dersom du er i tvil om prioriteten, er det tryggest å sette inn ekstra parenteser, slik at du sikrer deg at det som står mellom disse parentesene utføres først.) Variabelen sum vil ikke bli berørt av typekonverteringen. Den vil fortsatt være av type int og vil beholde den verdien den hadde før typekonverteringen. Typekonverteringen medfører bare at det blir opprettet en midlertidig verdi av den type vi har spesifisert. Denne midlertidige verdien brukes (i vårt eksempel) ved divisjonen. Det er selvsagt ikke tillatt å konvertere helt fritt mellom hvilke som helst datatyper. Dersom den typekonverteringen vi prøver å foreta ikke er tillatt, vil den resultere i en kompileringsfeil.

Flere tilordningsoperatorer

I programmer forekommer det forholdsvis ofte tilordningsinstruksjoner av formen

  variabel = variabel + verdi;

(variabel kan være av numerisk type eller String). En slik instruksjon kan forkortes til

  variabel += verdi;

På tilsvarende måte kan vi for numeriske variable x og y skrive

x -= y; istedenfor x = x - y;
x *= y; istedenfor x = x * y;
x /= y; istedenfor x = x / y;
x %= y; istedenfor x = x % y;
(de to siste forutsatt at y != 0).

I tilfelle tellevariable har vi ofte bruk for instruksjoner av formen

  antall = antall + 1;

eller

  antall = antall - 1;

Disse kan forkortes til henholdsvis

  antall++;
  antall--;

Det er også tillatt å skrive henholdsvis

  ++antall;
  --antall;

Dersom disse står som egne instruksjoner, slik de gjør ovenfor, vil det være likegyldig om vi bruker det første eller det andre av disse to alternativene. Men dersom vedkommende variabel med en av disse operatorene foran eller bak inngår som del av et uttrykk, vil det bli forskjellig virkning: Står operatoren bak variabelen, er det den gamle verdien til variabelen som brukes ved beregning av uttrykket. Økningen eller minkningen av variabelen foretas først etterpå. Dersom operatoren står foran variabelen, er det motsatt. Da økes eller minkes denne først. Deretter foretas beregning av uttrykket som den inngår i.

Eksempel 6

Anta at vi har to int-variable antall og variabel som på forhånd er blitt tilordnet verdier. Dersom vi da skriver instruksjonen

  variabel = antall++;

så vil dette gi samme resultat som de to instruksjonene

  variabel = antall;
  antall = antall + 1;

Skriver vi isteden instruksjonen

  variabel = ++antall;

så vil dette gi samme resultat som de to instruksjonene

  antall = antall + 1;
  variabel = antall;

altså motsatt rekkefølge.

Forrige kapittel Neste kapittel

Copyright © Kjetil Grønning og Eva Hadler Vihovde, revidert 2014