TDDE10 Objektorienterad programmering i Java
Tentatips
Innehåll
Nedan följer en kort lista med koncept som alltid ingår i tentan:- Grundläggande java-syntax. D.v.s variabler, konstanter, datatyper, deklarationer, uttryck, in- och utmatning, if-satser, loopar, underprogram o.s.v.
- Grundläggande objektorientering. D.v.s. Objekt/klasser, ansvar, konstruktorer, arv, klasshierarkier, överskuggning, polymorfism, inkapsling/synlighet, instans- resp. klassvariabler/-metoder, abstrakta metoder/klasser.
- Abstrakta datastrukturer. D.v.s. att använda javas inbyggda ADT:er (t.ex. listor, stackar, köer, mappar, mängder m.fl.) eller att implementera egna.
- UML-diagram (klassdiagram) D.v.s. hur man ritar upp vilka klasser systemet har, vilka attribut och operationer de har och vilka relationer de har sins emellan. Ofta kan man tjäna mycket tid på att läsa UML-diagrammen noga, för att få tips om hur man bör strukturera sin lösning.
- Generiska klasser
- Undantagshantering
- Interface
- Förbjuda arv/överskuggning (final)
- Typkontroller och konvertering
- Jokertecken
- Nästlade klasser
- Objektorienterad analys
- Strömmar/filer
- Slumptal
- Rekursion
- Javadoc
Vad får jag använda?
Java har många inbyggda paket och klasser. Vi tillåter att man använder sådana klasser. Generellt sett är det fritt fram att använda det så länge det inte explicit står i uppgiften att man inte får använda en viss sak eller att det uppenbarligen är så att det inbyggda man använder överlappar helt eller till mycket stor del med vad man som tentand skall visa att man kan. Detta innebär alltså att man oftast har full rätt att nyttja de saker som finns, t.ex. det som finns i paketet java.util. Här finns det mycket smått och gott som kan göra livet för tentanden lite lättare:- ArrayList, en implementation av ADT:n lista.
- Arrays, en klass med statiska metoder för array-manipulation (t.ex. sortering och sökning).
- Collections, en klass med statiska metoder för manipulation på samlingar (t.ex. listor, mängder, o.s.v.).
- HashMap, en implementation av ADT:n ordbok. (Finns även TreeMap).
- Random, en klass för att slumpa tal.
- Scanner, en klass för att läsa ut data från en fil/ström/sträng.
- Stack, en implementation av ADT:n stack.
- TreeMap, en implementation av ADT:n mängd. (Finns även HashSet).
Vanliga missar
Nedan har vi samlat ihop några av de vanligaste felen som tentander gör som leder till poängavdrag på uppgifter på tentan.- Duplicering av kod i sub/superklass. Om t.ex. superklassen lagrar ett visst data i en instansvariabel så skall inte subklassen också behöva lagra detta data, om det inte finns särskilda goda skäl. Om det finns generella beteenden för klasserna så bör detta ligga i superklassen. Om precis samma (eller nästan exakt samma) kod finns i sub- som superklass så låter man antagligen inte superklassen ta det fulla ansvar som den bör. Tänk på att man kan anropa super.metodAnrop() för att anropa superklassen!
- Felaktiga konstruktorer. Den som anropar en konstruktor skall
behöva ange (med parametrar) de data som klassen behöver få reda
på utifrån för att initieras. Den som anropar konstruktorn
skall inte behöva ange t.ex. konstant data, eftersom det då finns en
risk för att den som anropar gör fel. Ett exempel är klassen Cat som
lagrar namn och antal liv. Namnet är något som den som anropar skall
ange, men antal liv skall alltid börja på 9. Anropet till konstruktorn bör alltså vara:
new Cat("Isaac");
Inte på följande sätt:new Cat("Isaac", 9); // Det var givet att katter alltid har 9 liv från början! // Varför låta anroparen få möjligheten till att göra fel?
Internt blir då konstruktorn (på det korrekta sättet):public Cat(String name) { this.name = name; this.lives = 9; }
Detsamma gäller när det kommer till att anropa superkonstruktorn. Alla parametrar som kommer in till subklassens konstruktor kanske inte nödvändigtvis skall skickas till superkonstruktorn! Detta beror ju på. Vissa subklasser kanske fixerar en eller flera av superklassens attribut. I slutänden måste man noga tänka över: vad som den som instansierar objektet skall ange, vad som skall sparas i subklassen och vad som skall skickas vidare till en eventuell superklass. Svaret finns nästan alltid i uppgiftstexten, läs noga! - Variabeldeklarationer på fel ställen. Ett mycket vanligt fel är att man inte deklarerar sina variabler rätt. Tänk på att deklarationsstället helt och hållet avgör livslängden på variabeln. En variabel lokalt deklarerad i en metod, samt parametrar, överlever bara så länge man är i den metoden. En variabel deklarerad i klassen är en instansvariabel (såvida den inte är static) och överlever så länge som objektet finns. Variabler som bara används lokalt men som har deklarerats som instansvariabler och vice versa kan ge poängavdrag.
- Implementationen följer inte uppgiften. Ofta får man ganska mycket text och ofta får man även ett UML-diagram i uppgiften. En del av texten är ofta av inledande/tematisk natur, men större delen av texten (och diagrammet) beskriver ofta icke-funktionella krav på din implementation. Dessa skall följas så läs dem noga.
- Fullständiga uppräkningar. Många sådana missar beror helt
och hållet på att man inte har ett generellt lösningssätt. Vad händer
när problemet blir "1" större, eller "3" större, eller "100" större?
Använd loopar för att få bort fullständiga uppräkningar. Ibland kan
vanliga fält hjälpa oss här om uppräkningen inte har något generellt
mönster. T.ex.:
String[] weekDays = new String[]{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"}; for (int i = 0; i < weekDays.length; ++i) { if (userInput.equals(weekDays[i])) { System.out.println(userInput + " is a weekday."); break; } }
Ofta beror dock fullständiga uppräkningar på att man inte har utnyttjat polymorfi för att delegera utförandet av metoder till rätt subklass. Sådana fullständiga uppräkningar är allra viktigast att få bort och inse varför de inte är bra för koden. - Felaktig användning av static. Detta är något som vi vet
att många har svårt för så det gäller att vara extra noga med att lära
sig det väl. När en variabel deklareras static så existerar
bara en sådan för hela klassen (alla objekt har en
gemensamt). Detta har enorm påverkan på klassens beteende. När en
metod deklareras static så betyder det att man inte behöver ett objekt
av klassen för att anropa den. Och när man väl har anropat den så är
man inte inne i ett objekt, d.v.s. man har inte tillgång till
instansvariabler eller instansmetoder. Klassvariabler och klassmetoder
är mindre vanliga än instansvariabler och instansmetoder när man
jobbar objektorienterat men de dyker ändå upp här och där. I
UML-diagram markeras static med understruken stil. Om man vill
initiera en klassvariabel gör man det rimligtvis precis vid
deklarationen, t.ex:
public class SomeClass { private static int someClassVariable = 42; }
En vanlig miss här är t.ex. att man istället initierar klassvariabler i klassens konstruktor. Detta leder till problem eftersom klassvariabeln då alltid återställs varje gång ett objekt av klassen skapas. - Felaktig inkapsling (saknar t.ex. private). Denna är mycket vanlig. Man har helt enkelt glömt att sätta private på sina instansvariabler, eller public på sina metoder. Tänk på att hjälp-metoder i klasserna kan vara private. Om man inte sätter någon synlighet i java så får man paketsynlighet, detta vill man bara ha någon gång ibland. I UML markeras private med ett minustecken, public med ett plustecken och protected med en brädgård (#).
- Felaktig överskuggning. När en subklass skall överskugga en metod så måste metodnamnet och parametrarna vara precis som i superklassen. Missar man detta så får man ju istället en ny metod i subklassen. Detta kan leda till konstiga fel där man tycker att polymorfiskt anrop borde ske, men det inte blir det. Ett hett tips är att använda taggen @Override ovanför de metoder som man tänker sig skall överskugga. Då gör java-kompilatorn en automatiskt check att detta faktiskt är en metod i någon superklass och att överskuggningen sker korrekt. Har man då gjort något fel (t.ex. stavat fel på metodnamnet) så får man ett kompileringsfel istället för en svårhittad bug.
- Grova övertramp mot kodkonventionerna. När det är tenta är
vi medvetna om att studenterna är lite mer stressade än vanligt och vi
har lite överseende med att koden inte alltid kanske blir den
vackraste. Det finns dock ingen anledning till att tillåta totalt
kaos. Då blir det bara ett försvårat arbete både för den som skriver
koden och den som skall rätta. Vi kan ha överseende med att en
krull-parentes sitter fel på något enstaka ställe, eller att man
kanske har indenterat en eller två rader fel. Men om vi får kod som
ser ut på detta sätt:
private void Print_array() { for (int r = 1; r <= mapArray[0].length; ++r) { for (int c = 1; c <= mapArray.length; ++c) { System.out.print (mapArray[c-1][r-1].get_symbol()+" "); } System.out.println(" ") ; } }
Så får man poängavdrag för: "Oläslig kod". Lyckligtvis så finns det verktyg direkt i eclipse för att auto-formatera kod. Ctrl-shift-f. Tänk på att konventioner för namngivning också är mycket viktigt för läsbarhet. - "Pekarfel". Detta resulterar vanligen i att programmet
kraschar med NullPointerException, men kan även ha andra tråkiga
effekter. Om du får ett NullPointerException, gå till den rad där
felet har kastats och titta efter punkter. Operatorn punkt är
avreferering, d.v.s. att man följer en referens. NullPointerException
får man i java när man försöker följa en referens som är null. Alltså,
om det på den raden står så här:
house.getOwner().printYourself(); // här kastas NullPointerException
Så är det troligast att antingen variabeln house är null, eller att det som metoden getOwner() returnerar är null, eftersom det är dessa två referenser som avrefereras på den raden.
En annan vanlig miss som har med referenser att göra är hur objekt hanteras. Tänk t.ex. på att operatorn "==" jämför två referenser, d.v.s. om de är samma objekt, inte om de råkar ha lika innehåll. Särskilt för strängar innebär detta att man istället skall använda metoden equals() för att jämföra deras innehåll. - Felaktig ansvarsfördelning mellan klasser. Detta kan arta
sig på olika sätt. Ofta ser man kanske att en klass har blivit enorm
medans andra jättesmå. Detta behöver inte vara fel men är ofta ett
gott tecken på att något är galet. En klass bör ha så få
ansvarsområden som möjligt. När en klass börjar få för mycket ansvar
blir den för står, och jobbig att tampas med. Då bör den brytas upp i
mindre delar eller så skall ansvar flyttas över på andra klasser.
Ett annat symptom är att det behövs typkontroller (operatorn instanceof) och castningar för att lösa problem som uppstår när klassen skall interagera med andra klasser. Detta beror inte sällan på att ansvaret har hamnat på fel ställe. Självklart kan det vara så att det behövs typkontroller och castningar ibland (annars skulle de ju inte finnas i språket), men det sker ganska sällan och uppstår oftast när det är någon form av delat ansvar mellan klasser som kanske varken hör hemma i endera klassen, eller känns som att man skulle vilja lägga det i båda. Där typkontroller och castning används, men man kan undvika det om man istället korrekt använder sig av arv och polymorfi så får man poängavdrag. - Avsaknad eller felaktig användning av generiska parametrar.
När man har en generisk klass så måste man komma ihåg den där
generiska parametern när man använder den! Om man inte gör det så
reverterar den generiska typen inuti till klassen Objekt och vi får
inga vettiga typkontroller. Ta t.ex. följande exempel:
ArrayList kittenList = new ArrayList(); kittenList.add(new Cat("Isaac")); kittenList.get(0).getName(); // Kompileringsfel. Här måste vi nu konvertera!
Detta ger kompileringsfel, trots att klassen Cat har metoden getName()! Dessvärre ser ArrayList endast sina lagrade element som "Objekt", eftersom vi inte angav någon generisk parameter när vi deklarerade den! Det korrekta sättet att göra deklarationen är:ArrayList<Cat> kittenList = new ArrayList<Cat>();
Då får vi faktiskt ett objekt av typen Cat när vi anropar metoden get() och vi kan göra katt-specifika saker på det!
Detta blir extra viktigt när man gör och använder egna generiska klasser. Ett tips är att om kompilatorn börjar prata om "Raw Type" så har du nog glömt att ange generisk parameter med <>-parenteser någonstans.
Sidansvarig: Magnus Nielsen
Senast uppdaterad: 2024-03-19