Løsningsforslag - oppgaver i Avsnitt 1.4.4


Oppgave 1 b)

  public int compareTo(Heltall h)
  {
    if (verdi < h.verdi) return -1;
    else if (verdi == h.verdi) return 0;
    else return 1;
  }

Oppgave 1 c)

Hvis verdi1 og verdi2 er to heltall, vil differansen verdi1verdi2 bli negativ hvis verdi1 er mindre enn verdi2, bli lik 0 hvis verdi1 og verdi2 er like og bli positiv hvis verdi1 er større enn verdi2. Matematisk sett vil derfor verdi1verdi2 gi rett fortegn.

Men hvis de to heltallene verdi1 og verdi2 er representert ved hjelp av datatypen int, er situasjonen litt mer komplisert. Det kommer av at en int-variabel har 32 binære siffer. Hvis verdien til differansen verdi1verdi2 er for stor (eller for liten, dvs. for stor negativ) til å kunne bli representert med bare 32 binære siffer, vil systemet kun ta med de 32 siste binære sifferne i differansen. Dette kan gi fortegnsfeil fordi det brukes 2-komplement for negative tall. Dette skal vi se mer på i Delkapittel 1.7. Her nøyer vi oss med et eksempel:

  int verdi1 = -1234567890;  // verdi1 = 10110110011010011111110100101110
  int verdi2 = 1234567890;   // verdi2 = 01001001100101100000001011010010

  int d = verdi1 - verdi2;  // matematisk sett lik -2469135780

  System.out.print(d);

  // utskrift: 1825831516

I dette eksemplet er verdi1 negativ og verdi2 positiv. Dermed blir d = verdi1verdi2 matematisk sett negativ. Men utskriften viser at d er positiv. Det er lettere å se hva som skjer hvis vi bruker datatypen long som har 64 binære siffer:

  long verdi1 = -1234567890;
  long verdi2 = 1234567890;

  long d = verdi1 - verdi2;  // matematisk sett lik -2469135780

  System.out.println(d);  // utskrift: -2469135780

  System.out.println(Long.toBinaryString(d));
  // Utskrift:
  // 1111111111111111111111111111111101101100110100111111101001011100

  int k = (int)d;
  System.out.println(k);  // Utskrift: 1825831516

Her viser utskriften at d har den verdien som vi forventer, dvs. d = -2469135780. Videre har vi de 64 binære sifferne til d. Setningen int k = (int)d gjør at d blir konvertert til en int og det skjer ved at de 32 første binære sifferne fjernes og da står vi igjen med 01101100110100111111101001011100. Dette representerer et positivt tall siden den første biten er 0. Det er rett og slett binærkoden til tallet 1825831516 i int-format.

Konklusjonen er at vi må være meget forsiktige med å utføre regnestykker med store tall i int-format. Det kan gi feil resultat både med hensyn på fortegn og størrelse. Men hvis f.eks. både verdi1 og verdi2 er innenfor intervallet [-1073741824,1073741823], så vil verdi1verdi2 alltid gi det riktige svaret når vi bruker int som tallformat. Vi skal se nærmere på det binære tallformatet i Delkapittel 1.7.

Oppgave 1 d)

Her vil x.compareTo(y) gi 0 fordi begge inneholder verdien 3 og er dermed like. Men x.equals(y) vil gi false hvis vi bruker den versjonen av equals som vi arver fra class Object. Den versjonen av equals gir false hvis vi sammenligner to forskjellige objekter. Poenget er at x og y er to forskjellige objekter, men de inneholder samme verdi. Metoden equals i class Object er kodet slik at x.equals(y) og x == y gir samme resultat.

Man bruker av og til utrykkene logisk likhet og fysisk likhet. Poenget er at x og y er fysisk ulike fordi det er to forskjellige objekter, men de er logisk like fordi de har samme innhold. Setningen x == y tester alltid på fysisk likhet. Hvis vi lager en klasse, så er det vi som bestemmer om to forskjellige instanser av klassen kan ses på som logisk like eller ikke. Vi må da kode equals-metoden i vår klasse slik at x.equals(y) returnerer true hvis x og y er logisk like, og false ellers.

Oppgave 1 e)

Her vil både x.hashCode() og y.hashCode() gi verdien 3. Hvis vår hashCode() kommenteres vekk slik at det er den versjonen vi arver fra class Object som vil bli brukt, så blir utskriften helt annerledes. Den kan f.eks. bli 15655788 26533766, men også noe helt annet. I kildekoden for class Object er det ingen kode for metoden. Der står det bare public native int hashCode(). Det betyr at metoden må lages som en del av den virtuelle Java-maskinen (JVM). En vanlig teknikk er å konvertere objektets minneadresse til et heltall og bruke det som returverdi for metoden.

Oppgave 2 d)

  public Person(String fornavn, String etternavn)   // konstruktør
  {
    Objects.requireNonNull(fornavn, "fornavn er null");
    Objects.requireNonNull(etternavn, "etternavn er null");

    this.fornavn = fornavn;
    this.etternavn = etternavn;
  }

Oppgave 2 e)

  public boolean equals(Object o)         // ny versjon av equals
  {
    if (o == this) return true;           // er det samme objekt?
    if (o == null) return false;          // null-argument
    if (getClass() != o.getClass()) return false;  // er det rett klasse?
    final Person p = (Person)o;           // typekonvertering
    return etternavn.equals(p.etternavn) && fornavn.equals(p.fornavn);
  }

Oppgave 2 f)

  public boolean equals(Person p)         // Person som parametertype
  {
    if (p == this) return true;           // er det samme objekt?
    if (p == null) return false;          // null-argument
    return etternavn.equals(p.etternavn) && fornavn.equals(p.fornavn);
  }

Oppgave 2 g)

NetBeans

Hvis metoden equals() er laget (men ikke hashCode()), vil NetBeans tilby flg. kode for hashCode() (konstantene varierer fra gang til gang):

  public int hashCode()
  {
    int hash = 5;
    hash = 71 * hash + Objects.hashCode(this.fornavn);
    hash = 71 * hash + Objects.hashCode(this.etternavn);
    return hash;
  }

Hvis metoden hashCode() er laget (men ikke equals()), vil NetBeans tilby flg. kode for equals():

  public boolean equals(Object o)
  {
    if (o == null) return false;
    if (getClass() != o.getClass()) return false;
    final Person other = (Person) o;
    if (!Objects.equals(this.fornavn, other.fornavn)) return false;
    return Objects.equals(this.etternavn, other.etternavn);
  }

Eclipse

Hvis ingen av dem er kodet, vil Eclipse lage flg. forslag:

  @Override
  public int hashCode()
  {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((etternavn == null) ? 0 : etternavn.hashCode());
    result = prime * result + ((fornavn == null) ? 0 : fornavn.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj)
  {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    Person other = (Person) obj;
    if (etternavn == null)
    {
      if (other.etternavn != null) return false;
    }
    else if (!etternavn.equals(other.etternavn)) return false;
    if (fornavn == null)
    {
      if (other.fornavn != null) return false;
    }
    else if (!fornavn.equals(other.fornavn)) return false;
    return true;
  }

Oppgave 2 h)

I flg. versjon av toString() brukes metoden join:

  public String toString()
  {
    return String.join(" ", fornavn, etternavn);
  }

Oppgave 2 i)

import hjelpeklasser.*;
import java.util.*;
import java.util.stream.*;

public class Program
{
  public static void main(String... args)
  {
    Person[] p = new Person[5];                   // en persontabell

    p[0] = new Person("Kari","Svendsen");         // Kari Svendsen
    p[1] = new Person("Boris","Zukanovic");       // Boris Zukanovic
    p[2] = new Person("Ali","Kahn");              // Ali Kahn
    p[3] = new Person("Azra","Zukanovic");        // Azra Zukanovic
    p[4] = new Person("Kari","Pettersen");        // Kari Pettersen

    int m = Tabell.maks(p);                       // posisjonen til den største
    System.out.println(p[m] + " er størst");      // skriver ut den største

    Tabell.innsettingssortering(p);               // generisk sortering
    System.out.println(Arrays.toString(p));       // skriver ut sortert

    Stream s = Arrays.stream(p);
    Optional<Person> resultat = s.max(Comparator.naturalOrder());
    resultat.ifPresent(System.out::println);

  } // main  
}

Oppgave 2 j)

  Arrays.stream(p).max(Comparator.naturalOrder()).ifPresent(System.out::println);