Lab 0 -- Introduktion till Objektorientering i Java

Inledning

Laborationen går ut på att lära sig grunderna för objektorienterad programmering, samt motsvarande språkkonstruktioner i Java.

Laborationen består av olika steg där du får öva på bl.a. följande begrepp inom OOP:

  • Klasser och instanser
  • Meddelandesändning
  • Polymorfism
  • Arv
  • Instansvariabler och klassvariabler (static-variabler)
  • Metoder och klassmetoder (static-metoder)
  • Inkapsling och "information hiding" (skydda implementationen)
  • Skyddsnivåerna private, protected, public

Under labben kommer du att modifiera och bygga på klasserna i programmet på olika sätt. Det kan vara klokt att spara undan koden från de tidigare stegen genom att skapa kopior av filerna.

Laborationshandledningens struktur

Laborationen är indelad i 4 steg. Varje steg exemplifierar olika begrepp.

I varje steg finns lite kodexempel. Det kan vara exempel på hur er kod ska se ut eller annan kod som ska kunna använda er kod.

Steg 1 och 2 följs i sin helhet för att fullborda respektive steg.
Steg 3 inleds med en diskussion om olika företeeleser som ni kan testa på er egen kod. Själva uppgiften kommer litet längre fram under rubriken "Uppgift".
Steg 4 består i sin helhet av en uppgiftsbeskrivning.

Varje steg avslutas med ett antal frågor. Frågorna kan ni själva använda som kontrollpunkter att ni förstår vad som händer.

Steg 1 - polymorfism

Skapa klasser för att representera djur i allmänhet, hundar och katter. Klasserna Dog och Cat ska ärva från klassen Animal. Klasserna ska ha metoder enligt följande:
Animal
  introduceYourself() - ska skriva ut texten: 
                    "Morr. Jag är ett djur."

Cat (ärver Animal)
  introduceYourself() - ska skriva ut texten: 
                    "Mjau. Jag är en katt som heter X"
                  
Dog (ärver Animal)
  introduceYourself() - ska skriva ut texten: 
                    "Vov. Jag är en hund som heter X"

Skriv kod för de tre klasserna. Lägg varje klass i en egen fil med samma namn som klassen, till exempel Dog.java.

Använd klassen nedan för att testa dina klasser. För några av klasserna behöver du även skapa konstruktorer för att detta ska fungera.

public class Lab0Steg1{

    public static void main(String[] args) {
        Animal[] allAnimals;
        int i;
        
        allAnimals = new Animal[3];
        
        allAnimals[0] = new Cat("Kurre");
        allAnimals[1] = new Dog("Vilma");
        allAnimals[2] = new Cat("Bamse");
        
        i = 0;
        while (i < allAnimals.length) {
            allAnimals[i].introduceYourself();
            i = i + 1;
        }
    }
}

Lägg även denna klass i en egen fil, som alltså ska heta Lab0Steg1.java

Frågor:

  • Vad blir det för utskrift när man kör ovanstående kod?
  • Vad menas med polymorfism?
  • Hur fungerar polymorfismen i ovanstående program?
  • Metoden introduceYourself i Animal verkar ju aldrig anropas? Varför inte?
  • Kommentera bort metoden introduceYourself i Dog. Vad händer nu när du kör programmet?
  • Var sparas namnet för instanserna av Cat och Dog? (I vilken/vilka klasser har du lagt instansvariabeln som refererar till namnet på djuret? I både Cat och Dog, eller bara i Animal?)
  • Hur fungerar koden i testprogrammet?
  • Hur fungerar en array?
  • I ovanstående program har en while-loop använts för att stega igenom fältet och skriva ut information om djuren, men det finns en bättre lämpad loop-sats här. Vilken är det?

Steg 2 - instansvariabler och klassvariabler

Lägg till en publik instansvariabel i klassen Animal av typen int som heter age. Lägg även till age i konstruktorerna för Cat och Dog, så att djurets ålder sätts där.

Modifiera metoderna introduceYourself i klasserna Animal, Cat och Dog så att de också skriver ut hur gammalt djuret är. Här är ett exempel på kod för klassen Cat:

public void introduceYourself()
{
    System.out.println("Mjau. Jag är en katt som heter " + this.name);
    System.out.println("och jag är " + this.age + " år gammal.");
}
Testkör sedan med följande kod:
public class Lab0Steg2{

    public static void main(String[] args) {

        Animal kurre = new Cat("Kurre", 6);
        Animal vilma = new Dog("Vilma", 3);

        kurre.introduceYourself();
        vilma.introduceYourself();
    }
}
Frågor:
  • Vad blir utskriften?
  • Förklara hur det kommer sig att instansvariabeln age i Animal kan användas i Cat och Dog när den är deklarerad i Animal.
  • Vad består egentligen en instans av?
  • Och vad består en klass av?
  • Vad är skillnaden mellan en klass och en instans?
  • Vad refererar variabeln this till?

Steg 3 - publika vs. privata variabler

Här får du lära dig varför man ska skydda representationen (instansvariablerna) och du får också lära dig en del om konstruktorer, och prova att skriva en klassmetod (static-metod).

Själva uppgiften för Steg 3 kommer i slutet av steget.

Det är mycket osnyggt att använda publika variabler på det sätt vi gjorde i Steg 2. Anledningen är att koden blir känslig för ändringar.

Antag att vi vill ändra representerationen av hur gammalt ett djur är från age till birthYear. Så här skulle det kunna se ut i klassen Animal i så fall:

public class Animal{

    public int birthYear;
    // variabeln age är borttagen
    ...
}
Plötsligt fungerar inte koden som använder variabeln age längre! Vi måste gå in och ändra i alla klasser där age används och skriva om koden så att den använder variabeln birthYear istället, och vi måste dessutom räkna ut åldern varje gång vi vill veta denna genom att ta nuvarande årtal minus födelseåret.

I t.ex klassen Cat hade vi fått ändra till följande kod:

public void introduceYourself() {

    System.out.println("Mjau. Jag är en katt som heter " + this.name);
    System.out.println(
            "och jag är " + 
            (2013 - this.birthYear) + 
            " år gammal.");
}
Hade vi haft en metod som hette getAge istället hade vi däremot bara behövt ändra på ett enda ställe i koden - i klassen Animal.

Så här skulle det kunnat ha sett ut i klassen Animal om vi hade gömt variabeln age från första början och kommit åt den via en metod istället:

public class Animal{

    private int age;
    
    public Animal(int age) {
        this.age = age;
    }
    
    public int getAge() {
        return age;
    }
}
Koden i Cat hade då t.ex kunnat se ut så här:
public class Cat extends Animal{

    private String name;
    
    public Cat(String name, int age) {
        super(age); // Observera anropet till superklassens, dvs. Animals konstruktor.
        this.name = name;
    }
    
    public String getName() {
        return this.name;
    }
    
    public void introduceYourself() {
        System.out.println(
            "Mjau. Jag är en katt som heter " + this.getName());
        System.out.println(
            "och jag är " + this.getAge() + " år gammal.");
    }
}

Notera att i ovanstående kod gömmer vi också instansvariabeln name med private, för att den ska vara skyddad mot åtkomst av kod utanför klassen. Om man vill gå ett steg längre i förbättringarna kan man lägga name i Animal, och flytta metoden getName dit. Då slipper man ha variabeln name i både Cat och Dog, men man måste i så fall ge konstruktorn till Animal ett extra argument för namnet. Kanske har du redan gjort på det sättet i de tidigare stegen.

Nu när vi vill ändra representationen av age till birthYear, så behöver vi endast ändra i koden för Animal. Koden i övriga klasser fungerar utan ändringar eftersom vi kommer åt age via metoden getAge i Animal.

Koden i klassen Animal kan då se ut så här:

public class Animal{
    private static int currentYear = 2013;
    private int birthYear;
    
    public Animal(int age) {
        this.birthYear = Animal.currentYear - age;
    }
    
    public int getAge() {
        return Animal.currentYear - this.birthYear;
    }
}
I ovanstående kod används en klassvariabel för att hålla reda på vilket år som är det nuvarande året. Årtalet är ju rimligtvis det samma för alla djur. (Såvida de inte börjar hålla på med tidsresor...)

Uppgift.

Skriv om ditt program enligt de riktlinjer vi har gått igenom så att alla instansvariabler skyddas med private. Byt representationen av age så att födelsedatum sparas istället för age. Lägg alla instansvariabler i klassen Animal.

Koden för klasserna ska fungera med följande testprogram:

public class Lab0Steg3{

    public static void main(String[] args) {
        Animal.setYear(2013); // Så här anropar man en klassmetod,
                              // dvs en statisk metod.
                              // Hur ska koden för setYear se ut?
        
        Animal kurre = new Cat("Kurre", 6);
        Animal vilma = new Dog("Vilma", 3);
        
        kurre.introduceYourself();
        vilma.introduceYourself();

        Animal.setYear(2014); // Nu blir är ett nytt år för alla djur.

        kurre.introduceYourself();
        vilma.introduceYourself();
    }
}

Det kan hända att kompilatorn klagar på koden i de tidigare testprogrammen (Lab0Steg1 och Lab0Steg2). Du kan i så fall ignorera det eller kommentera bort koden inuti dessa klasser så försvinner problemen.

Frågor:

  • Vad är skillnaden mellan private och public?
  • Hur kommer man åt instansvariabler som är deklarerade som private från andra klasser?
  • Kan man komma åt private-variabler i subklasser?
  • Vi har inte använt protected ännu, men vad innebär det?
  • Vad innebär det att använda private, public och protected på metoder?
  • Vad betyder this nu igen? När kan man utelämna this och när måste man ha med this?
  • Vad betyder super(age); i konstruktorn för klassen Cat i kodexemplet ovan?
  • Hur skriver man en klassmetod (en static-metod)?
  • Vad skiljer en klassmetod från en vanlig metod?
  • Kan man komma åt instansvariabler från en klassmetod?
  • Slutligen: Varför är det känsligt att accessa en instansvariabel, men mycket mindre känsligt att anropa en metod?

Sammanfattningsvis: Att gömma implementationen (t.ex genom att göra instansvariablerna privata, men även metoder kan göras privata) är en av grundstenarna i OOP. Det är en del extra arbete att skriva metoder för att komma åt variablerna, men det är väl värt detta i lite större program, och i yrkesmässig programmering är det en självklarhet.

Steg 4 - relationer mellan objekt

Det här är det sista steget i lab 0 och här får du lära dig mer om relationer mellan objekt.

Objekt kan hänga ihop med varandra. Det kallas för relationer. Ett hus kan t.ex innehålla ett antal olika djur. För att hålla reda på vilka djur som finns i huset, kan huset ha en lista över djuren. Ett djur i sin tur kanske kan ha en leksak, och det kanske kan ha en kompis, en relation till ett annat djurobjekt.

Uppgift

Din uppgift är att skriva ett program som har följande struktur:

  • Du ska skapa två nya klasser, House och Toy.
  • Instanser av typen House kan innehålla godtyckligt många djur.
  • Klassen Toy har en sträng som är namnet på leksaken. Namnet sätts i konstruktorn, och kan kommas åt med den publika metoden getName().
  • Instanser av kassen Animal (eller instanser av dess subklasser Dog och Cat) kan ha en kompis, som är ett annat djur. Animal kan även ha godtyckligt många leksaker.
  • Klassen House ska ha en metod print() som skriver ut information om alla djur, deras vänner och leksaker.

Din kod för klasserna House, Animal, Dog, Cat, och Toy, ska fungera med följande testprogram:

public class Lab0Steg4{

    public static void main(String[] args) {

        // Sätt årtalet för djuren.
        
        Animal.setYear(2013);

        // Skapa några djur.
        
        Animal kurre  = new Cat("Kurre", 6);
        Animal vilma  = new Dog("Vilma", 3);
        Animal bamse  = new Cat("Bamse", 12);
        Animal smilla = new Dog("Smilla", 1);

        // Skapa leksaker.
        
        Toy ball = new Toy ("Boll");
        Toy shoe  = new Toy ("Tuggsko");
        Toy mouse  = new Toy ("Platsmus");

        // Skapa huset.
        
        House house = new House();

        // Skapa relationer mellan objekten.
        
        house.addAnimal(kurre);
        house.addAnimal(vilma);
        house.addAnimal(bamse);
        house.addAnimal(smilla);

        kurre.setFriend(vilma);
        vilma.setFriend(smilla);
        bamse.setFriend(kurre);

        kurre.addToy(ball);
        kurre.addToy(mouse);
        vilma.addToy(shoe);
        vilma.addToy(ball);
        // Skriv ut vad som finns i huset.
        
        house.print();
    }
}
Tips
Lite hjälp på vägen: så här kan metoden print i House se ut:
public void print() {

    System.out.println("Följande djur finns i huset:");
    
    for (Animal animal: animalList) {
        animal.print();
    }
}
Ovanstående kod fungerar om instansvariabeln animalList i House till exempel är av typen ArrayList<Animal>. Det är en klass i Javas klassbibliotek och för att kunna använda den måste du importera den med:
import java.util.ArrayList; 

Lägg import-satserna först i källkodsfilen, innan klassen.

Nu fortsätter vi med en annan sak. Observera att man måste kolla att en variabel inte är null innan den används. Om man t.ex har en variabel friend i Animal som kan vara null måste man testa att den har ett värde innan man kan använda den.

Här följer ett kodexempel som illustrerar detta:

class Animal extends Object{

    private Animal friend;

    public Animal() {
        // Till att börja med har ett nytt djur ingen kompis.
        
        this.friend = null;
    }

    public void setFriend(Animal animal) {
        this.friend = animal;
    }

    public void print() {
        // Anropa introduceYourself för att skriva ut
        // uppgifter om mig själv. Om this t.ex
        // är en Cat-instans, kommer då metoden
        // introduceYourself i Animal eller i Cat att
        // anropas?
        
        this.introduceYourself();

        // Kolla att friend är skild från null innan
        // meddelandet introduceYourself skickas.
        
        if (friend != null) {
            System.out.println("Här är uppgifter om min kompis:");
            friend.introduceYourself();
        } else {
            System.out.println("Jag har ingen kompis.");
        }
    }

    public void introduceYourself() {
        System.out.println("Morr. Jag är ett djur.");
    }
}
Om du utgår från ovanstående kodexempel för klassen Animal i ditt program, behövs i så fall metoderna setFriend och print även i subklasserna Dog och Cat?

Hur kan du bygga ut ovanstående kod för klassen Animal så att djurobjekt kan referera till leksaker? Vilka tillägg behövs i print för att namnet på leksakerna i djurets lista med leksaker ska skrivas ut?

UML-diagram
Försöka att rita ett översiktlig klassdiagram enligt UML som visar alla dina klasser, och arv samt associationer emellan dem.
Frågor:
  • Hur skapar man en ArrayList?
  • Hur lägger man in ett objekt i en ArrayList?
  • Hur itererar man över elementen i en ArrayList? Finns det flera sätt?
  • Kan du beskriva vilka meddelanden som skickas till vilka instanser och vilka metoder som anropas när print i House utförs?
Kodkonventioner
När man skriver kod är det lämpligt att använda sig av kodkonventioner. I den här kursen utgår vi ifrån Oracles kodkonventioner. Läs igenom kodkonventionerna, speciellt avsnitt 6-9, och kontrollera att din kod följer dessa konventioner.

Redovisning

Lab 0 har ingen redovisning. Du bör dock ha förstått begreppen som gås igenom labben, och kunna svara på frågorna ovan, samt på nedanstående frågor. Att förstå de objektorienterade koncepten är nödvändigt för att klara av kursen. Om något är oklart, fråga gärna Sara.

  • Ligger bara den kod som behövs i Cat och Dog, och den kod som blir likadan för både Cat och Dog i Animal? Vad är det som skiljer kod som måste ligga i Cat respektive Dog från den kod som kan delas via Animal?
  • Vad är en klass?
  • Kan du berätta vilka delar en klassbeskrivning i Java innehåller? Ge exempel med hjälp av en klass i ert program.
  • Kan du förklara skillnaden mellan en klass och en instans?
  • Kan du förklara vad instansvariabler är?
  • Kan du berätta vad synlighet innebär?
  • Kan du ge exempel på hur "information hiding" används och vad det är bra för?
  • Kan du förklara vad polymorfism innebär? Ge exempel från koden!
  • Kan du förklara syntaxen för de olika kontrollstrukturer du har använt i ert program?
  • Kan du visa ett exempel på en loop i ert program som itererar över en lista med objekt och förklara vad koden gör?

Extra övning

Efter lab 0 kan det vara lämpligt att fortsätta öva genom att göra övningar som finns att hitta i kurslitteraturen. Detta kan speciellt vara bra om man tyckte att lab 0 var svår.

Framförallt rekommenderas övningar inom följande kategorier:

  • Grundläggande Java
  • Objektorientering
  • Problemlösning/allmäna övningar
Detta motsvaras i princip av de övningar som finns till kapitel 1-5 i kursboken. Liknande övningar finns i de flesta Javaböcker.

Ursprungligen skapad för kurserna HKGBB7 och TDDC30 vid Linköpings universitet av Mikael Kindborg, Sara Stymne, Johan Janzén, Jonas Lindgren m.fl 2005-2012. Modifierad för 5LN446 av Sara Stymne 2013.