|
Bakgrunnen for dette notatet er at det er skrevet mer enn nok om
objektorientert programmering i java fra før. Det er også
et faktum at objektorientering bankes inn som svaret på alt, mens
andre områder blir forsømt når man skal
forsøke å lære
objektorientert programmering uten å ha et
skikkelig grunnlag innen generell programmering.
Dersom du føler deg som en nybegynner i programmering så kan
kanskje dette notatet være med på å demystifisere
javaprogrammering noe. For deg som har erfaring med objektorientert
programmering, kan det også være lurt å lese
igjennom for å se det hele fra en litt annen vinkel. Hvis du
synes det dukker opp mange nye ting underveis er det et tegn på
at du skal ta et skritt tilbake fra objektorientert programmering.
Slik kan du ta flere skritt fram igjen, når det grunnleggende er
forstått.
Hva er et godt javaprogram?
God objektorientert design er viktig i store systemer for å
holde orden på de ulike komponentene. Velskrevet kode er viktig,
slik at det er lett for andre å sette seg inn i og vedlikeholde
koden. I faget TDT4120 Algoritmer og datastrukturer vil de fleste
programmene bli små implementasjoner av kjente algoritmer. Det
betyr at velskrevet kode er langt viktigere enn inngående
kunnskap om- og utstrakt bruk av objektorientering. I mange tilfeller
vil det være beste være å avstå fra å
lage egne objekter i det hele tatt.
Java uten objektorientering
Det første man bør lære om java er hvordan man
skriver enkel kode uten egendefinerte objekter. Objektene lærer
man fort når det grunnleggende er forstått og det kan
føre til en åpenbaring av hvilke muligheter som
finnes. Nøkkelordet for ikkeobjektorientert javaprogrammering
er static. Et javaprogram i sin enkleste
form inneholder kun den statiske main()-metoden, mens javaprogrammer i sin
"nest enkleste form" i tillegg inneholder andre metoder og variabler som er
deklarert static.
public class MinKlasse {
static int demoDummy = 14;
/** returnerer i fakultet for 0 <= i <= 12 .*/
public static int fakultet(int i)
{
if( i > 1 )
return i * fakultet( i - 1 );
else
return 1;
}
public static void main(String[] args)
{
System.out.println("6 fakultet: " + fakultet(6) );
}
}
Variabler som er deklarert static kalles
klassevariabler. Tilsvarende kalles variabler som ikke er deklarert static for
medlemsvariabler. Medlemsvariablene er unike for hver objektinstans
(medlem) av klassen og når det opprettes en objektinstans, settes det
egentlig av plass til alle medlemsvariablene i minnet. Klassevariablene er
felles for hele klassen og trenger derfor ikke repeteres for hver instans.
Tilsvarende har vi klassemetoder og medlemsmetoder som deklareres
henholdsvis med og uten bruk av static. Dersom en utelater static på steder det skulle vært
brukt, er dette en av mange faktorer som kan bidra til å obfuskere
koden. Spesielt etter å ha blitt ekspert på
objektorientert programmering med java er det viktig å huske at:
- En variabel skal deklareres static med
mindre den skal være unik for hver instans av klassen.
- En metode skal være static med
mindre den skal operere på medlemsvariable.
Det er dårlig objektorientert design å tvinge brukere av
klasser til å opprette objekter uten mening. Eksempelet under
er hentet fra DinKlasse og viser hvordan gal design av MinKlasse kan
vanskeliggjøre kode i andre klasser. Den beste løsningen
oppnås kun dersom fakultetmetoden er deklarert static i MinKlasse.
//Bra
int i = MinKlasse.fakultet(6);
//Dårlig design av MinKlasse
MinKlasse m = new MinKlasse(6);
int i = m.fakultet();
//Dårlig design av MinKlasse
MinKlasse m = new MinKlasse();
int i = m.fakultet(6);
Programflyt
Før man begynner å ta for seg av mulighetene
objektorientering gir, bør en ha full kontroll på
programflyt. Med programflyt menes i hvilken rekkefølge de
forskjellige delene av programmet blir utført. Vi vet at et
javaprogram starter med at main()-metoden blir kalt. Derfra blir koden
utført linje for linje helt til og med siste linjen i
mainmetoden, der programmet slutter (med mindre vi har startet opp
andre tråder enn hovedtråden). Hva som skjer i hvilken
rekkefølge er imidlertid ikke alltid like intuitivt. Hva
skrives for eksempel ut av denne kodesnutten?
public class Program {
public static char f( char c ){
System.out.print( c++ );
return c--;
}
public static void main(String[] args)
{
if( f('j') == 'k' || f('f') == 'f'){
System.out.println( f('d') );
}
}
}
Dersom du ser dette med en gang kan du hoppe til neste avsnitt. Dersom
du ikke ser hva som vil bli skrevet ut, se litt nøyere. Dersom
du fremdeles ikke ser hva som vil bli skrevet ut, kompiler og
kjør programmet. Dersom du etter å ha kjørt
programmet ikke forstår nøyaktig hva som skjedde, kontakt
studass og sørg for å forstå denne biten. Det er
riktignok ikke så viktig at du forstår alle mulige obskure
programsnutter, men det er viktig for å at du skjønner
programflyt for at du skal kunne programmere skikkelig.
Programflyten styres i tillegg til metodekall av kontrollstrukturer;
if else,
for,
while,
switch og av nøkkelord for å
gjøre hopp;
return,
break,
continue.
Alt dette regnes som kjent stoff og dersom du skulle være i tvil
om noe av dette finner du svaret i javaboka di. Programflyten ved
metodekall regnes også som kjent stoff, men for sikkerhets
skyld repeterer vi hvordan det fungerer.
Dersom vi kaller en metode fra en gitt kontekst (blokk), må
denne metoden returnere før det neste metodekallet
gjøres fra den samme konteksten. For å kunne kalle en
metode må alle inputparametre (og en eventuell instans å
kalle metoden på) være kjent. Det vil si at at disse
må evalueres før selve metodekallet
gjøres. Rekkefølgen er definert slik at det eventuelle
objektet som har metoden evalueres før parameterne til
metoden. I eksempelet under evalueres først metoden a()
deretter b() og til slutt c(), fordi c blir kalt på objektet
returnert fra a() og skal ha b()'s returverdi som parameter.
a().c( b() );
I motsetning til C og enkelte andre C-style språk har java en
mer generell regel som sier at alle uttrykk blir evaluert fra venstre
mot høyre. Resultatene av uttrykkene under er derfor veldefinert
i java, mens C ikke sier noe om hvordan slike uttrykk skal
håndteres.
int i = (i = 3) + i; // i = 3 + 3;
minus( i = 5, i); // minus( 5, 5);
På samme måte blir logiske uttrykk
evaluert fra venstre mot høyre i java, såvel som i de
fleste andre språk. Ofte vil
resultatet av hele uttrykket være kjent før alle
deluttrykkene er evaluert fullstendig. I slike tilfeller er det
unødvendig å evaluere den siste delen av uttrykket, og de
gjenstående delene vil derfor ikke bli evaluert. I eksemplene under vil
bare de fire første uttrykkene bli evaluert i hver "if", fordi det fjerde
deluttrykket i begge tilfeller avgjør utfallet av hele uttrykket.
boolean T = true, F = false;
if( F || F || F || T || F || F || T || F || T );
if( T && T && T && F && T && F && F && T && F );
Selv om reglene klart sier i hvilken rekkefølge de forskjellige
delene av koden skal kjøre er det dumt å skrive kode som
i stor grad baserer seg på slike regler. Dersom du skriver kode
som er avhengig av slike regler må du kunne reglene
ordentlig og du bør uansett begrense bruken. Kjenner du ikke
reglene godt nok risikerer du å
skrive gal kode til tross for at den kan fungere når du tester
den på din maskin.
Presedens
Presedensregler er det som bestemmer i hvilken rekkefølge operatorer i
uttrykk skal evalueres. Disse reglene sørger for at
betydningen av uttrykk er entydig definert - selv uten
parenteser. Parenteser er likevel
klargjørende og kan brukes selv om språket ikke krever
det. De operatorene som binder
operandene tettest til seg har høyest presedens. Fra
matematikken vet vi for eksempel at multiplikasjon og divisjon har høyere
presedens enn addisjon og subtraksjon og at parenteser brukes til å
overstyre disse reglene. Under følger presedensreglene for
operatorer i java.
| postfix operators | [] . (params) expr++ expr-- |
| unary operators | ++expr --expr +expr -expr ~ ! |
| creation or cast | new (type)expr |
| multiplicative | * / % |
| additive | + - |
| shift | << >> >>> |
| relational | < > <= >= instanceof |
| equality | == != |
| bitwise AND | & |
| bitwise exclusive OR | ^ |
| bitwise inclusive OR | | |
| logical AND | && |
| logical OR | || |
| conditional | ? : |
| assignment | = += -= *= /= %= &= ^= |= <<= >>= >>>= |
Bruk tabellen over til å forstå hva som skjer i dette
utrykket:
i+=i+++i;
Presedensregler kommer ikke i konflikt
med regelen om at uttrykk skal evalueres fra venstre mot
høyre. I uttrykket under evalueres a først, så
b og deretter multipliseres disse. Tilsvarende skjer med c og d
før hele utrykket adderes. Begrepet evaluere gir større
mening dersom vi ser på a, b, c og d som mindre delutrykk, for
eksempel metodekall. Operandene evalueres altså fra
venstre mot høyre, mens operatorene følger
presedensreglene.
a * b + c * d
Parameteroverføring ved metodekall
Grunnen til at det er satt av et eget avsnitt til
parameteroverføring til metoder, er at java behandler dette
på to fundamentalt forskjellig måter ut i fra hva slags
datatype som blir overført. De primitive datatypene som finnes
i java er boolean, char, byte, short, int, long, double, float. Som parametre
til metodekall blir disse overført som rene verdier. Det vil si
at metoden som blir kalt ikke vil kunne modifisere parameterne slik at
det virker inn på koden den kalles fra. Når vi sender
objekter som parametre til metodekall
sender vi derimot kun referansene til objektene. Dette betyr at
metoden som blir kalt kan modifisere det opprinnelige objektet. Dette
illustreres best ved et eksempel. I program A under vil
modifiser() kun motta en verdi og kallet
til modifiser() vil
ikke ha noen effekt fra main(). I program
B opprettes ett tabellobjekt og det er referansen til
dette objektet
som blir overført. Fordi det er en referanse til den opprinnelige
tabellen som sendes, kan modifiser() endre
verdiene som ligger i tabellen.
|