1
|
class Billet {
private String fra, til;
private int pris;
public Billet( String f, String t, int p ) {
fra = f;
til = t;
setPris( p );
}
public void setPris( int p ) {
pris = p;
}
public String toString() {
return "[Billet: " + fra + "-" + til + ", " + pris + " kr.]";
}
} |
|
|
|
Billet b = new Billet( "Ikast", "Herning", 25 );
println( b.toString() );
b.setPris( 30 );
println( b.toString() ); |
[Billet: Ikast-Herning, 25 kr.]
[Billet: Ikast-Herning, 30 kr.] |
|
2
|
class Punkt {
private int x, y;
public Punkt() {
x = 0;
y = 0;
}
public Punkt( int x, int y ) {
this.x = x;
this.y = y;
}
public void moveX( int dx ) {
x += dx;
}
public void moveY( int dy ) {
y += dy;
}
public String toString() {
return "(" + x + "," + y + ")";
}
} |
|
|
|
Punkt p = new Punkt( 3, 5 );
println( p.toString() );
p.moveX( 3 );
p.moveY( -1 );
println( p.toString() ); |
|
3
|
class Farve {
private int r, g, b;
public Farve( int r, int g, int b ) {
set( r, g, b );
}
private int normalize( int styrke ) {
if ( styrke < 0 )
return 0;
else if ( styrke > 255 )
return 255;
else
return styrke;
}
public void set( int r, int g, int b ) {
this.r = normalize( r );
this.g = normalize( g );
this.b = normalize( b );
} |
|
|
| Vi har her datakernen som de tre tal der beskriver farven. Man bemærker
det nære slægsskab mellem set-konstruktoren og set-metoden.
|
|
|
I set-metoden
anvender vi this-referencen,
da parametrenes navne er de samme som instans-variablene. Vi har lavet
en service-metode normalize,
til at forenkle set-metodens
kontrol af værdierne, før de placeres i instans-variablene.
|
|
| De to andre konstruktorer er som følger:
|
|
|
public Farve() {
this( 0, 0, 0 );
}
public Farve( Farve other ) {
this( other.r, other.g, other.b );
} |
|
|
| Man bemærker, at de begge anvender set-konstruktoren med et this-kald.
|
|
| toString-metoden
bliver en ikke alt for køn samling if-sætninger,
men det virker!
|
|
|
public String toString() {
if ( r == 255 && g == 0 && b == 0 )
return "[Rød]";
else if ( r == 0 && g == 255 && b == 0 )
return "[Grøn]";
else if ( r == 0 && g == 0 && b == 255 )
return "[Blå]";
else if ( r == 0 && g == 0 && b == 0 )
return "[Sort]";
else
return "[" + r + ", " + g + ", " + b + "]";
}
|
|
|
| equals-metoden
er derimod meget enkel:
|
|
|
public boolean equals( Farve other ) {
return r == other.r &&
g == other.g &&
b == other.b;
}
}
|
|
|
| Man kunne også vælge at tilgå instans-variablene med
this-referencen
for at understrege, at det er vores variable i modsætning til dem
vi får adgang til via parameteren:
|
|
|
public boolean equals( Farve other ) {
return this.r == other.r &&
this.g == other.g &&
this.b == other.b;
}
}
|
|
|
| men det er i første række en smagssag.
|
|
|
Lad os til slut se en testanvendelse:
|
|
|
Farve rød = new Farve( 257, 0, -2 );
Farve lilla = new Farve( 153, 0, 153 );
Farve osseRød = new Farve( rød );
Farve sort = new Farve();
println( "rød.toString(): " + rød.toString() );
println( "rød.equals( osseRød ): " + rød.equals( osseRød ) );
println( "sort.equals( lilla ): " + sort.equals( lilla ) ); |
rød.toString(): [Rød]
rød.equals( osseRød ): true
sort.equals( lilla ): false |
|
4
|
class PengeKasse {
private int tyvere, tiere, femmere, toere, enere;
public PengeKasse() {
reset();
}
private void reset() {
tyvere = 0;
tiere = 0;
femmere = 0;
toere = 0;
enere = 0;
} |
|
|
|
Her lægger vi ud med at erklære de fem instansvariable
og laver default-konstruktoren.
|
|
|
Default-konstruktoren anvender en servicemetode der sætter alle
variable til 0. Det er lavet som en servicemetode fordi add
også får brug for denne funktionalitet.
|
|
|
public PengeKasse( PengeKasse other ) {
tyvere = other.get( 20 );
tiere = other.get( 10 );
femmere = other.get( 5 );
toere = other.get( 2 );
enere = other.get( 1 );
} |
|
|
|
Copy-konstruktoren kunne også have valgt at tilgå variablene
direkte, men da vi alligevel har get-metoden, er det pænere at
bruge den i stedet. Man bemærker, at der kun er tale om kopiering
- vi fjerner ikke mønterne fra den PengeKasse
vi kopierer fra.
|
|
|
public int sum() {
return 20 * tyvere + 10 * tiere + 5 * femmere + 2 * toere + enere;
} |
|
|
|
Metoden bygger på en simpel sammenregning af værdien af
de forskellige mønter ud fra deres pålydende og antal.
|
|
|
public String toString() {
String res;
// "[...]" + "[...]" + "[...]" + ...
res = kantede( 20, tyvere ) +
kantede( 10, tiere ) +
kantede( 5, femmere ) +
kantede( 2, toere ) +
kantede( 1, enere );
return res;
}
private String kantede( int pålydende, int antal ) {
return "[" + pålydende + ":" + antal + "]"; // f.eks. "[20:1]"
} |
|
|
|
toString benytter en servicemetode:
kantede, der laver den tekststreng
der indeholder oplysningerne om én af mønttyperne. Metoden
samler disse for de fem forskellige typer, og returnerer den samlede
tekststreng.
|
|
|
public boolean equals( PengeKasse other ) {
boolean res;
res = this.tyvere==other.tyvere &&
this.tiere==other.tiere &&
this.femmere==other.femmere &&
this.toere==other.toere &&
this.enere==other.enere;
return res;
} |
|
|
|
equals er en simpel sammenligning
af antallet af de forskellige mønttyper.
|
|
|
public void set( int pålydende, int antal ) {
switch ( pålydende ) {
case 20: tyvere = antal;
break;
case 10: tiere = antal;
break;
case 5: femmere = antal;
break;
case 2: toere = antal;
break;
case 1: enere = antal;
break;
default:
println( "Forkert pålydende: " + pålydende );
}
} |
|
|
|
En simpel switch der går
på mønttypen (pålydende)
og sætter den korresponderende instansvariabel til antal.
|
|
|
public int get( int pålydende ) {
switch ( pålydende ) {
case 20: return tyvere;
case 10: return tiere;
case 5: return femmere;
case 2: return toere;
case 1: return enere;
default:
println( "Forkert pålydende: " + pålydende );
return 0; // indikation af fejl
}
} |
|
|
|
Ligeledes en simpel switch,
der denne gang ikke bruger breaks,
da der returneres direkte. Det er specielt, at 0 returnes hvis mønttypen
ikke eksisterer. Det gøres i første række for at
metoden returnerer noget og i anden række, fordi det på
sin vis er rigtigt at returnere 0, f.eks.: "Der er ingen treere,
altså: ingen = 0"
|
|
|
public void add( PengeKasse other ) {
// læg den andens mønter til vores
this.tyvere += other.get( 20 );
this.tiere += other.get( 10 );
this.femmere += other.get( 5 );
this.toere += other.get( 2 );
this.enere += other.get( 1 );
// tøm den anden PengeKasse
other.reset();
} |
|
|
|
Metoden er rimelig enkel fordi vi ikke har noget normaliseringsproblem.
Har vi fem en-kroner skal vi ikke udskifte dem med en femmer, da vi
har en fysisk opfattelse af, at der ér fem en-kroner. Man bemærker
at vi afslutter med at "tømme" den PengeKasse
vi har taget mønterne fra.
|
|
|
public PengeKasse givTilbage( int beløb ) {
PengeKasse tilbage = new PengeKasse();
while ( beløb>=20 && tyvere>0 ) {
tilbage.tyvere++;
tyvere--;
beløb -= 20;
}
while ( beløb>=10 && tiere>0 ) {
tilbage.tiere++;
tiere--;
beløb -= 10;
}
while ( beløb>=5 && femmere>0 ) {
tilbage.femmere++;
femmere--;
beløb -= 5;
}
while ( beløb>=2 && toere>0 ) {
tilbage.toere++;
toere--;
beløb -= 2;
}
if ( beløb > enere ) {
add( tilbage ); // lægger mønterne tilbage
return null;
} else {
tilbage.enere = beløb;
enere -= beløb;
return tilbage;
}
}
} |
|
|
|
En temlig redundant udseende implementation, men med de fem instansvariable
er det ikke umiddelbart muligt at fjerne redundansen. For hver mønttype
gives der tilbage så længe "Vi mangler at give et beløb
tilbage der er større mønttypens pålydende og der
er flere tilbage af den pågældende type".
|
|
| Bemærk ligeledes, at vi lægger de mønter tilbage
som vi har taget (med et kald af add-metoden);
hvis det viser sig umuligt at give det ønskede beløb
tilbage.
|
|
|
Det skal bemærkes, at der er enkelt situationer hvor metoden
ikke virker. F.eks. hvis PengeKassen
indeholder to femmer og fire toere, og vi skal give otte tilbage. I
den situation vil den stædigt forsøge at give tilbage med
en femmer plus noget mere, i stedet for at give de fire toere tilbage.
|
|
|
Lad os til slut se en testanvendelse:
|
|
|
PengeKasse kasse1 = new PengeKasse();
kasse1.set( 20, 1 );
kasse1.set( 10, 2 );
kasse1.set( 5, 0 );
kasse1.set( 2, 1 );
kasse1.set( 1, 0 );
print( "20:" + kasse1.get( 20 ) + ", " );
print( "10:" + kasse1.get( 10 ) + ", " );
print( "5:" + kasse1.get( 5 ) + ", " );
print( "2:" + kasse1.get( 2 ) + ", " );
print( "1:" + kasse1.get( 1 ) );
println();
println( "[kasse1] indhold: " + kasse1 );
println( "[kasse1] sum: " + kasse1.sum() );
PengeKasse kasse2 = new PengeKasse( kasse1 );
println( "[kasse1] lig med [kasse2]: " + kasse1.equals( kasse2 ) );
kasse1.add( kasse2 );
println( "[kasse1] indhold: " + kasse1 );
println( "[kasse2] indhold: " + kasse2 );
PengeKasse tilbage = kasse1.givTilbage( 14 );
println( "[tilbage] indhold: " + tilbage ); |
20:1, 10:2, 5:0, 2:1, 1:0
[kasse1] indhold: [20:1][10:2][5:0][2:1][1:0]
[kasse1] sum: 42
[kasse1] lig med [kasse2]: true
[kasse1] indhold: [20:2][10:4][5:0][2:2][1:0]
[kasse2] indhold: [20:0][10:0][5:0][2:0][1:0]
[tilbage] indhold: [20:0][10:1][5:0][2:2][1:0] |
|
| 5
| Det første vi skal fastlægge er den interne datarepræsentation.
Vi skal kunne give det indtryk at temperaturen er både en Celsius-
og en Fahrenheit-temperatur, men internt er det uhensigtsmæssigt
med denne redundans. Derfor vælger vi kun at opbevare temperaturen
i en af de to skalaer. Vi vælger Celsius-skalaen og skal derfor
foretage omregninger i forbindelse med Fahrenheit.
|
|
| Konstruktorerne og set-/get-metoderne for Celsius er ukomplicerede:
|
|
|
class Temperatur {
// temperatur i celsius
private double temp;
public Temperatur() {
temp = 0;
}
public Temperatur( Temperatur other ) {
temp = other.celsius();
}
public double getCelsius() {
return temp;
}
public void setCelsius( double c ) {
temp = c;
}
|
|
|
| I forbindelse med set-/get-metoderne for Fahrenheit anvender vi omregning
til og fra Celsius for at harmonere med den interne repræsentation
i Celsius:
|
|
|
public double getFahrenheit() {
return 9.0 / 5.0 * temp + 32.0;
}
public void setFahrenheit( double f ) {
temp = 5.0 / 9.0 * f - ( 17.0 + 7.0 / 9.0 );
}
|
|
|
| Man bemærker, at vi vælger helt igennem at anvende kommatals-literaler
(med angivelse af ".0"), da det øger
læsbarheden i forhold til en blanding af direkte angivne kommatals-literaler
og implicit konverterede kommatals-literaler fra heltal (hvis du ikke
forstod den sidste kommentar, så er det netop derfor vi kun bruger
kommatal i beregningen!).
|
|
| equals er lige til, mens compareTo
er mere interssant:
|
|
|
public boolean equals( Temperatur other ) {
return temp == other.getCelsius();
}
private int signum( double tal ) {
if ( tal < 0 )
return -1;
else if ( tal > 0 )
return 1;
else
return 0;
}
public int compareTo( Temperatur t ) {
return signum( temp - t.getCelsius() );
}
|
|
|
|
Her har vi, som i kapitlet, valgt at lave en service-metode til fortegnsangivelsen.
|
|
|
Endelig er der toString. Vi
vælger at lade den returnere en teksstreng med temperaturen angivet
i både Celsius og Fahrenheit. Det gør vi blandt andet for
ikke at afsløre, at den interne repræsentation er i Celsius,
men også for at afspejle det lige forhold, der i alle andre sammenhænge
er mellem de to skalaer udad til.
|
|
|
public String toString() {
return "[" + temp + "C, " + getFahrenheit() + "F]";
}
} |
|
|
| Lad os til slut se en testanvendelse:
|
|
|
Temperatur t1 = new Temperatur();
t1.setCelsius( 100 );
println( t1 );
t1.setFahrenheit( 0 );
println( t1 );
Temperatur t2 = new Temperatur( t1 );
println( "t1 og t2 ens: " + t1.equals( t2 ) );
t1.setCelsius( 0 );
println( t1 );
println( "t1 compare to t2: " + t1.compareTo( t2 ) ); |
[100C, 212F]
[-17.7777777777778C, 0F]
t1 og t2 ens: true
[0C, 32F]
t1 compare to t2: 1 |
|
| 6
|
class Rektangel {
private int minX, maxX, minY, maxY;
// PRE: For parametrene gælder: minX <= maxX && minY <= maxY
public Rektangel( int minX, int maxX, int minY, int maxY ) {
this.minX = minX;
this.maxX = maxX;
this.minY = minY;
this.maxY = maxY;
}
public Rektangel( Rektangel other ) {
this( other.minX, other.maxX, other.minY, other.maxY );
} |
|
|
| Vi lægger ud med de fire instansvariable og konstruktorerne.
|
|
| Copy-konstruktoren benytter et kald videre
til set-konstruktoren, og da Rektangel ikke har nogen set-/get-metoder
gøres det ved direkte læsning.
|
|
|
public int areal() {
return ( maxX - minX ) * ( maxY - minY );
}
public int omkreds() {
return 2 * ( ( maxX - minX ) + ( maxY - minY ) );
} |
|
|
| De to metoder implementeres begge som direkte returneringer, med anvendelse
af den relevante formel for henholdsvis areal og omkreds.
|
|
|
public void move( int deltaX, int deltaY ) {
minX += deltaX;
maxX += deltaX;
minY += deltaY;
maxY += deltaY;
} |
|
|
| Her adderes de relative ændringer til de tilhørende instansvariable.
|
|
|
private boolean indenfor( int min, int max, int tal ) {
return min <= tal && tal <= max;
}
public boolean indeholder( Rektangel r ) {
return indenfor( minX, maxX, r.minX ) && // er r.minX indenfor?
indenfor( minX, maxX, r.maxX ) && // er r.maxX indenfor?
indenfor( minY, maxY, r.minY ) && // er r.minY indenfor?
indenfor( minY, maxY, r.maxY ); // er r.maxY indenfor?
} |
|
|
| Anvendelsen af en service-metode gør indeholder betydelig lettere at
læse, men udtrykket bliver til gengæld ikke mindre i omfang.
|
|
|
public Rektangel omkrandsende( Rektangel other ) {
return new Rektangel( min( this.minX, other.minX ), // mindste minX
max( this.maxX, other.maxX ), // største maxX
min( this.minY, other.minY ), // mindste minY
max( this.maxY, other.maxY ) ); // største maxY
} |
|
|
| Med anvendelse af min- og max-metoderne, bliver det enklere at forstå hvordan
omkrandser finder det mindste omkrandsende
rektangel, idet den benytter den sammenhæng der observeres i figur
3.
|
|
|
public boolean equals( Rektangel other ) {
return this.minX == other.minX &&
this.maxX == other.maxX &&
this.minY == other.minY &&
this.maxY == other.maxY;
} |
|
|
| equals er enkel og monoton.
|
|
|
public String toString() {
return "[X:" + minX + "-" + maxX + ", Y:" + minY + "-" + maxY + "]";
}
} |
|
|
| I tråd med opfattelsen af at et rektangel er givet ved min/max
værdier for x og y, returneres x's og y's intervaller.
|
|
| Endelig har vi en testanvendelse:
|
|
|
Rektangel r1 = new Rektangel( 1, 3, 5, 8 );
Rektangel r2 = new Rektangel( r1 );
Rektangel r3 = new Rektangel( 3, 4, 1, 9 );
println( "r1: " + r1 );
println( "r2: " + r2 );
println( "r3: " + r3 );
println( "r1 lig med r2: " + r1.equals( r2 ) );
r1.move( 1, -2 );
println( "r1 flyttet x+1 og y-2: " + r1 );
println( "r1 lig med r2: " + r1.equals( r2 ) );
println( "Arealet af r1: " + r1.areal() );
println( "Omkredsen af r1: " + r1.omkreds() );
Rektangel r4 = r1.omkrandsende( r3 );
println( "Omkrandsende r1 og r3: " + r4 );
Rektangel r5 = r4.omkrandsende( r2 );
println( "Omkrandsende r1, r2 og r3: " + r5 ); |
r1: [X:1-3, Y:5-8]
r2: [X:1-3, Y:5-8]
r3: [X:3-4, Y:1-9]
r1 lig med r2: true
r1 flyttet x+1 og y-2: [X:2-4, Y:3-6]
r1 lig med r2: false
Arealet af r1: 6
Omkredsen af r1: 10
Omkrandsende r1 og r3: [X:2-4, Y:1-9]
Omkrandsende r1, r2 og r3: [X:1-4, Y:1-9] |
|
|
| |