Løsningsforslag ukeoppg. 8:  10. - 16. okt (INF1000 - Høst 2012)

Mer om tekster, metoder, og objekter  (kap. 1-8 og 12 i "Rett på Java" 3. utg.)

NB! Legg merke til at disse er løsningsforslag.  Løsningene dine trenger ikke å være like med disse forslag for å være riktige.  Det er vanlig i programmering at samme oppgave kan løses på mange vidt forskjellige måter, og alle fremgangsmåter er ok i INF1000 så lenge de leder fram til riktig resultat og oppfyller kravene som står i oppgaveteksten.

Mål
Få øvelse i teorien du trenger til å løse Oblig 3, og litt repetisjon.

[Nøkkeloppg.]: Oppgave merket med nøkkelsymbol er plukket ut som spesielt representativ for de viktigste temaene fra ukens forelesning, og alle bør ha som minimumsmål å løse denne selvstendig.

Oppgaver

For de som ønsker å lese en annerledes og fin forklaring av klasser og objekter anbefaler vi "objekter.pdf" i Marit Nybakkens notater, skrevet av en populær tidligere gruppelærerinne i kurset.

  1. [Nøkkeloppg.]: Vanlige feilmeldinger i Oblig 3:
    Finn feilene i følgende program og foreslå hvordan de kan rettes. Programmet består av to klasser: Student, som bare har én objektvariabel: navn; og Gruppe, som har en array med opptil 40 studenter og en konstruktør med programkode som illustrerer bruk av klasser og objekter.
    class Student {
       String navn;
    }
    
    class Gruppe {
       Student[] studenter = new Student[40]; // Array med Student-objekter
       int antStudenter; // Antall plasser i arrayen over som er i bruk
    
       public static void main(String[] args) {
          Gruppe g = new Gruppe();
    
          skrivAntall();
          /* Spørsmål:
           * a) Hvorfor gir linjen over følgende feilmelding?  Hvordan unngå det?
           *        Gruppe.java:12: non-static method skrivAntall() cannot
           *                        be referenced from a static context
           *            skrivAntall();
           *            ^
           *  Tips: Du kan lese mer om dette på side 201 (og 157) i læreboka.
           *    Problemet skyldes at main-metoden er en "klassemetode", dvs. deklarert
           *    med "static", og derfor kan man ikke referere til "objektvariabler"
           *    eller "objektmetoder" (dvs. variabler og metoder som ikke er deklarert
           *    me nøkkelordet static) direkte fra en static metode uten å gå
           *    via en peker, f.eks. pekeren g ovenfor.  Dette illustrerer
           *    hvorfor vi vanligvis bare har noen få programsetninger i
           *    main-metoden som bare setter programmet i gang, ved å kalle
           *    på andre metoder i programmet.
           */
    
    Svar:
    a) Globale variabler og metoder deklarert uten nøkkelordet static
       kalles for "objektvariabler" og "objektmetoder".  Slike variabler
       og metoder kan ikke aksesseres direkte fra main (uten å gå via en
       objektpeker) fordi main-metoden er static - den er en såkalt
       "klassemetode" og ikke "objektmetode".
    
       I dette tilfellet skulle kallet på objektmetoden skrivAntall()
       ha gått via en objektpeker, f.eks. slik: g.skrivAntall(),
       fordi kallet på metoden er gjort i static-metoden main.
       }
    
       Gruppe() {
          // b) Den vanligste feilmeldingen i Java er "cannot find symbol".
          //    Følgende linje gir feilmeldingen vist under.  Hva er feil i dette
          //    tilfellet, og hvordan kan vi rette det?
          antallStudenter = 0;
          /*                 Gruppe.java:32: cannot find symbol
           *                 symbol  : variable antallStudenter
           *                 location: class Gruppe
           *                         antallStudenter = 0;
           *                         ^
           *  Tips: Årsaken til feilmeldingen "cannot find symbol" er alltid
           *    at noe ikke er deklarert riktig (f.eks. at det ikke er deklarert
           *    på riktig sted eller er stavet feil).  Feilen er heldigvis
           *    lett å fikse: se i feilmeldingen hva det var som ikke var
           *    deklarert, og sjekk at du har deklarert det riktig (på riktig
           *    sted i programmet, og stavet riktig).  Du kan finne hva det er
           *    som ikke var riktig deklarert på 2. linje i feilmeldingen, der
           *    det står "symbol".  I eksemplet over er problem-symbolet
           *    "variable antallStudenter".  Legg også merke til linjenummeret
           *    og posisjonen på linja som Java-kompilatoren også gir deg.
           */
    
    b) antStudenter er stavet feil.
          // c) Følgende linje gir "NullPointerException" her.  Hvilken setning
          //    har vi glemt å utføre før dette?  (Husk at studenter[] er en array)
          studenter[antStudenter].navn = "Trine";
          antStudenter++;
          /*  Tips: Feilmeldingen NullPointerException skyldes oftest at man har
           *    forsøkt å bruke prikk-notasjon på en peker som hadde verdien null.
           *    Det er ikke lov (det gir ingen mening å si f.eks. null.navn).
           *
           *  Slik fikser du NullPointerException-feil:
           *    1. Se i feilmeldingen hvilket linjenummer Java fant feilen i.
           *    2. Se hvilke pekere i den angitte linjen som kan ha vært null
           *       under kjøring av programmet, særlig blandt de som har prikk-
           *       notasjon etter seg, f.eks. hvis linjen inneholder uttrykket:
           *         hyblene[rad][kol].leietager.navn  ...så kan null-pekeren
           *       være hyblene[rad][kol] eller hyblene[rad][kol].leietager
           *    3. Endre programmet slik at pekeren som forårsaket feilen ikke
           *       kan være null når programmet kommer til den aktuelle linjen.
           *       Hvis problemet var en peker med prikknotasjon kan du legge
           *       til en if-test på at pekeren != null før den aktuelle linjen.
           *    Det som står foran prikken må altså være noe som peker på
           *    et allerede-oppretet objekt av riktig klasse (og ikke null), på
           *    det tidspunktet under programutførelsen når setningen blir utført.
           *
           *    4. Hvis linjen har flere pekere og du ikke finner hvilken som ga
           *       NullPointerException kan du legge inn en testutskrift rett før
           *       linja, og skrive ut én av dem, f.eks.
           *         System.out.println("test1:" + hyblene[rad][kol].leietager);
           *       Med denne fremgangsmåten vil du alltid kunne finne nullpekeren.
           */
    
    c) Her mangler å opprette studentobjekt, f.eks. slik:
        studenter[antStudenter] = new Student();
          // d) Skriv det som mangler for at studenter[1].navn skal bli "Martin".
          Student s1 = new Student();
          s1 _________ = "Martin";
          studenter[antStudenter++] = _________ ;
    
    d) s1.navn = "Martin";
    studenter[antStudenter++] = s1 ;
          // e) Hva mangler her for at if-testen skal kunne gi true?
          //    Tips: Husk at tekster må sammenlignes med andre tekster.
          if (studenter[1].equals("Martin")) {
              System.out.println(true);
          }
    
    e) Det mangler .navn:
    if (studenter[1].navn.equals("Martin")) {
          // f) Hvorfor gir følgende løkke NullPointerException på linjen med
          //    System.out..? (anta at eneste kode som er blitt utført når
          //    programmet kommer hit er det som står i linjene over, med feilene
          //    rettet).  Også, hvordan kan betingelsen i første linje endres for å
          //    unngå NullPointerException?  Tips: Tenk arrayplasser og != null.
          for (int i = 0; i < studenter.length; i++) {
              Student s2 = studenter[i];
              System.out.println(s2.navn);
          }
    
    f) Her blir det NullPointerException fordi det bare er noen få elementer
       i arrayen som inneholder Student-objekt, og resten har null-peker.
       Problemet er at "i" går for langt, og forbi plassene i arrayen der det
       ligger data.  En løsning er å endre betingelsen i for-løkka slik at "i"
       bare teller opp til antStudenter:
          for (int i = 0; i < antStudenter; i++) {
    
       En annen mulig løsning er å sjekke at elementet i arrayen
       studenter[] på plass "i" ikke er null:
          for (int i = 0; studenter[i] != null && i <
       studenter.length; i++) {
          // g) Finn en annen måte å unngå NullPointerException her uten å endre
          //    første linje under, men ved å legge til en if-setning inne i løkka:
          for (int i = 0; i < studenter.length; i++) {
              Student s3 = studenter[i];
    
    g)  if (s3 != null) {
            System.out.println(s3.navn);
        }
          }
    
          // h) Hvorfor gir følgende linje i programkoden denne feilmeldingen:
          //    ArrayIndexOutOfBoundsException: 40
          studenter[40] = new Student();
    
    h) Fordi indeksene i arrayen går fra 0 til 39.
          // i) Hva vet vi om uttrykket merkert med "___" på neste linje hvis
          //    linjen gir feilmeldingen "ArrayIndexOutOfBoundsException: -1":
          s1 = studenter[ ___ ];
    
    i) Uttrykket har verdi -1, så det kan f.eks. ha vært:
         s1 = studenter[j];   ...der j har verdien -1 på dette tidspunktet.
          // j) Hva er feil her?  Feilmeldingen dette gir er:
          //                  Gruppe.java:103: incompatible types
          //                  found   : java.lang.String
          //                  required: Student
          studenter[2] = "Eva";
    
    j) Dette er et forsøk på å lagre en String-verdi inne i en Student-peker.
       Det går ikke.  Tekster må lagres i tekst-variabler, f.eks.:
             studenter[2].navn = "Eva";
          // k) Hva er feil her?  Feilmeldingen er: cannot find symbol
          //                  symbol  : constructor Student(java.lang.String)
          Student lars = new Student("Lars");
    
    k) Som du ser av feilmeldingen, så finner ikke Java-kompilatoren noen
       konstruktør i klassen Student med en String som parameter.  Løsningen er
       enten å lage en slik konstruktør i klassen Student (med én String som
       parameter), eller bruke den vanlige automatisk-definerte konstruktøren
       (som ikke har parametre), og lagre navnet i en egen programsetning:
           Student lars = new Student();
           lars.navn = "Lars";
          // l) Hva er feil her?  Feilmeldingen dette gir er:
          //                  Gruppe.java:114: incompatible types
          //                  found   : int
          //                  required: boolean
          if (antStudenter = 0) {
              system.out.println("Ingen student registrert!");
          }
    
    l) Det skulle stått to lik-tegn: "==".  Når det bare står ett lik-tegn
       er hele uttrykket en tilordning, som gir 0 som verdi, og det passer
       ikke som betingelse i en if-setning (betingelser må ha boolsk verdi).
          // m) Hvorfor klager Java også med: "package system does not exist"?
    
    m) System er stavet feil.
          // n) Når vi har rettet alle feilene i a) til m) ovenfor, så gir fortsatt
          //    if-setningen under NullPointerException.  Hvordan unngår vi det?
          boolean funnet;
          for (int i = 0; i < studenter.length; i++) {
              Student stud = studenter[i];
              if (stud.navn.equals("Eva")) {
                   funnet = true;
                   System.out.println(stud.navn);
              }
          }
    
    n) Ved f.eks. å legge til en ekstra-betingelse i if-setningen, f.eks.:
        if (stud != null && stud.navn.equals("Eva")) {
          // o) Løkken ovenfor stopper ikke når navnet "Eva" blir funnet.
          //    Hvordan kan vi få løkken til å stoppe når navnet blir funnet?
    
    o) Ved å legge til "! funnet" som en ekstra-betingelse i selve for-løkken:
           for (int i = 0; i < studenter.length && !funnet; i++) {
          // p) Løkken ovenfor gir ingen melding til bruker når navnet ikke blir
          //    funnet.  Hvordan kan vi programmere utskrift av en slik melding?
          //    Og hvordan kan vi unngå at Java da skal klage om at "variable
          //    funnet might not have been initialized"?
    
    p) Ved å sette startverdien "false" i funnet, og legge til rett etter
        løkken en if-setning som skriver ut en melding om "ikke funnet"
        hvis !funnet.
          // q) Anta at navnet til studenter[2] er "Eva".  Hvorfor endrer ikke
          //    følgende kode navnet til studenter[2]?  Hvordan kan det ordnes?
          //    (slik at studentobjektet i "ny" overføres til studenter[2]).
          Student ny = new Student();
          Student studPeker;
          ny.navn = "Heidi";
          studPeker = studenter[2];
          studPeker = ny;
       }
    
    q) Vi har 2 student-objekter i minnet: Eva som er pekt på av studenter[2],
       og Heidi som er pekt på av ny.  Det som kodebiten gjør er å sette
       studPeker til å peke på det første, og deretter flyttes (bare) denne
       studPeker til å peke på ny i stedet.  Det endrer ikke på hva pekeren
       studenter[2] peker på.  For å ordne det må man bruke en tilordning
       hvor studenter[2] står på venstre side av "="-tegnet, f.eks. ved
       å legge til følgende som en ny linje rett etter kodebiten i oppgaven:
           studenter[2] = studPeker;
       void skrivAntall() {
          System.out.println("Antall studenter: " + antStudenter);
       }
    
       // r) Hvorfor får vi feilmeldingen: "<identifier> expected" på denne linjen:
       System.out.println("Slutt");
    
    r) Det er bare deklarasjoner (av variabler og metoder) som
       kan plasseres her, i dette området utenfor metoder.  Andre typer
       setninger, som "System.out.." er bare lov å plassere inne i metoder.
    }
    
    Flere debuggings-tips:
    Både feilmeldingene fra kompilatoren (javac) og kjøresystemet til Java (java) gir deg vanligvis linjenummeret der feilen oppsto, og navnet til feilen.  Disse opplysningene er nyttige for å finne og rette feilen.  Start alltid med å rette den første feilen som Java fant.

    Tips til krøllparentes-feil:
    En annen type feil som er lett å gjøre i Java er krøllparentes-feil, f.eks. å glemme en krøllparentes et sted, ha en for mye, en som "vender" feil vei, o.l. Disse problemene gir opphav til mange typer feilmeldinger, bl.a.:
    • «class, interface, or enum expected», «<identifier> expected»
    • «reached end of file while parsing», «'}' expected»,
    • «illegal start of type», «illegal start of expression», «'else' without 'if'», m.fl...
    To gode tips for å unngå og rette slike feil er:
    1. Alltid holde programmet riktig formatert med "innrykk" underveis mens du skriver det: Start et nytt "nivå" med innrykk rett etter hver "{" (åpnings-krøllparentes) du skriver, dvs. at alle linjene etter en slik krøllparentes får f.eks. 4 flere mellomrom foran enn linjene over ...frem til første "}", da du forminsker ett innrykk-nivå, osv.  Alle programmer i disse ukeoppgavene er formatert på denne måten.
    2. Når et av de ovennevnte feilene dukker opp, bruk funksjonen i Emacs (eller Eclipse, e.l.) som setter inn riktig innrykk i hele programmet. I Emacs kan det gjøres ved å markere hele programmet (Ctrl-x h) og velge "Java > Indent Line or Region" fra menyen øverst i Emacs-vinduet.  Da blir det satt riktig innrykk i hele programmet og det blir lett å se hvor feilen er, ved å starte fra toppen og fortsette nedover til du finner noe som har havnet på feil innrykksnivå: Sjekke at alle klasser havnet i "første innrykks-nivå" (dvs. at det ikke står noen mellomrom før nøkkelordet class), og at første linje i alle metoder havnet i 2. nivå av innrykk (dvs. at det er 4 mellomrom rett før nøkkelordet void), og at selve innmaten i metodene er i 3. nivå (har 8 mellomrom foran), osv.

    Flere tips til feilsøking og -retting finner du i Marit Nybakkens feilmeldinger.pdf.



  2. HashMap med akronymer: kap. 9, oppg. 2 (noe modifisert tekst) (side 195)

    (a) Et akronym er et initialord (forkortelse) som blir lest og uttalt som et vanlig ord, altså ikke bokstav for bokstav.Lag et program som leser inn en rekke akronymer og deres tolkning fra fil (du finner en fil med akronymer (og andre forkortelser) på bokas hjemmeside: akronymer.txt ). Legg akronymene og tolkningene i en HashMap med akronymet som nøkkel.

    Brukeren skal oppgi et akronym til programmet og få tilbake en eller flere tolkninger av akronymet. Det kan maksimalt være 10 tolkninger til hvert akronym. Denne brukerdialogen skal gå i løkke til brukeren svarer "-".

    Tips: Lag en klasse der objekter har følgende attributter: akronymet og en String-array med tolkninger, samt antall tolkninger. Pekere til objekter av denne klassen legges i HashMap med akronymet som nøkkel. Det kan være lurt å lage en metode i denne klassen som legger til en ny tolkning, og som holder orden på hvor mange tolkninger det er i øyeblikket.

    
    API 	Application Programming Interface
    AV 	Audio/Video
    AV 	Authenticity Verification
    BASH 	Bourne Again Shell [Unix]
    EU 	Europaunionen
    FN 	Forente nasjoner
    ...osv...
    
    // Basert på løsningsforslaget i lærebokens hjemmeside.
    /*
    * Løsning på oppgave 2 i kapittel 9 : Akronymer
    * Programmet legges i en fil med navnet : AkronymerMain.java
    *
    * Løsningen bygger på to klasser som begge fins i denne filen
    * Programmet forutsetter at alle akronymene står med store bokstaver
    * i datafilen, men brukeren trenger ikke taste de inn med store bokstaver.
    */
    
    import java.util.*; // for å få med HashMap
    import easyIO.*;    // for å få med fil- og brukerkommunikasjon
    
    class AkronymerMain {
       
        // initialiserer akronym-registeret 
        HashMap<String, Akronym> akronymer = new HashMap<String, Akronym>();
    
        public static void main(String[] args) {
     
            // Oppretter et objekt av denne samme klassen for å kunne kalle
            // objektmetoder fra denne klassemetoden vha. pekeren "am".
            AkronymerMain am = new AkronymerMain();
    
    // leser inn data fra fil og bygger opp register
    am.byggRegister();
    //går i dialog med bruker
    am.brukerdialog(); } void byggRegister() { In fil = new In("akronymer.txt"); while(fil.hasNext()) { // leser akronymet og tolkningen String akro = fil.inWord(); String tolkning = fil.inLine().trim(); // sjekker om akronymet er registrert tidligere if (akronymer.containsKey(akro)){ // vi skal nå legge til en ny tolkning Akronym a = akronymer.get(akro); a.add(tolkning); } else { // vi lager et nytt Akronym-objekt Akronym ny = new Akronym(akro,tolkning); // vi legger det inn i akronym-registeret akronymer.put(akro, ny); } } fil.close(); } void brukerdialog() { In tastatur = new In(); String svar; do { System.out.print("Skriv inn et akronym (avslutt med -): "); svar = tastatur.inLine(); svar = svar.trim(); svar = svar.toUpperCase(); if (akronymer.containsKey(svar)) { Akronym a = akronymer.get(svar); a.skrivUt(); } else if (!svar.equals("-")){ System.out.println(svar + " fins ikke i akronymregisteret."); } } while (!svar.equals("-")); } } class Akronym { int antall; String akronym; String[] tolkninger = new String[10]; Akronym (String a, String t) { akronym = a; tolkninger[antall] = t; antall = 1; } void add(String t) { if (antall < 10) { tolkninger[antall] = t; antall++; } else { System.out.println("For mange tolkninger av " + akronym); } } void skrivUt() { System.out.println(akronym + " kan bety:"); for (int i = 0; i < antall; i++) { System.out.println(" " + tolkninger[i]); } } }



Tibakemelding om dette oppgavesettet kan duskrive i bloggen eller sende på mail til siriamj [a] ifi.uio.no