Föreläsningar 11, 12 (okt 08, okt 12 v 40).

Poster och Abstrakta datatyper.

Komplexa tal definierade med en klass med publika instansvariabler.

Vi ska implementera en ny egendefinerad typ Complex för komplexa tal i en klass Complex med publika instanvariabler , en publik instanvariabel för det komplexa talets realdel och en publik instanvariabel för det komplexa talets realdel.

Detta kan göras genom att skriva denna klass (på filen Complex.java) och kompilera:

public class Complex {

public double re; //instansvariabel, ej static!

public double im; //instansvariabel, ej static!

}

Alla typer bortsett från dom inbyggda primitiva typerna (int , long, short, byte, char, boolean , double, float) och arrayer definieras i Java av klasser. Detta är den normala användningen av klasser.

Tidigare har vi använt klasser för att hysa en eller flera klassmetoder. Detta är en mindre vanlig användning av klasser, men i en applikation kan man använda en klass på detta sätt för att hysa main-metoden (som ju är en klassmetod, det står static framför en klassmetod). Ett annat exempel på en klass med bara klassmetoder (statiska metoder) är Math.

Ordet Complex kan användas som en typ i andra klasser i samma katalog som där Complex.class finns. När man skapar ett nytt komplex tal eller flera nya komplexa tal så representeras varje komplext tal av en referens till ett objekt. I varje objekt kommer det att finnas de två flyttalsvariablerna re och im, som kallas för instansvariabler. Märk att det inte står static framför double!

Vi ska som förut implementera algebran för komplexa tal i en klass Comp, men nu med hjälp av den nya typen Complex . I klassen skall finnas definitioner för samma statiska metoder som fanns när vi skrev Comp med hjälp av tvåelementssarrayer.

Detta kan göras på detta vis:

public class Comp {

public static Complex mkComplex(double ire, double iim) {

Complex result = new Complex();

result.re = ire;

result.im = iim;

return result;

}

public static boolean isEqual(Complex z1, Complex z2){

return (z1.re == z2.re) && (z1.im == z2.im);

}

public static Complex add(Complex z1, Complex z2){

/*

Complex result = new Complex();

result.re = z1.re + z2.re;

result.im = z1.im + z2.im;

return result;

*/

return mkComplex(z1.re + z2.re, z1.im + z2.im);

}

public static Complex sub(Complex z1, Complex z2){

return mkComplex(z1.re - z2.re, z1.im - z2.im);

}

public static Complex mult(Complex z1, Complex z2){

Complex result = new Complex();

return mkComplex(z1.re*z2.re - z1.im*z2.im,

z1.re*z2.im + z1.im*z2.re);

}

public static Complex conjugate(Complex z1){

return mkComplex( z1.re, - z1.im);

}

public static double modulus(Complex z1){

return Math.sqrt(z1.re*z1.re + z1.im*z1.im);

}

public static Complex div(Complex z1, Complex z2){

if (isEqual(z2, mkComplex(0,0))) {

throw new RuntimeException("Tried to divid by 0 + 0i");

} else {

double mo = modulus(z2);

double dd = mo*mo;

Complex zz = mult(z1, conjugate(z2));

return mkComplex( zz.re/dd, zz.im/dd);

}

}

public static double re(Complex z1){

return z1.re;

}

public static double im(Complex z1){

return z1.im;

}

public static String toString(Complex z1) {

return z1.re + " + " + z1.im + "i";

}

}

En komplext tal är i vår lösning (vi ska kommande veckor göra på många andra sätt, bättre sätt) en referens till ett objekt. Objektet innehåller realdelen i re och med imaginärdelen i im.

Vi kan nu göra beräkningar på komplexa tal med TestComplex, samma program som vi använde förut när vi lät komplexa tal vara av typen double [][].

public class TestComplex {

public static void main(String [] iargs) {

System.out.println(" (3+4i)= " +

Comp.toString(Comp.mkComplex(3, 4)));

System.out.println(" (3+4i)/(1-2i) = " + Comp.toString(

Comp.div(Comp.mkComplex(3, 4),

Comp.mkComplex(1, -2))));

System.out.println(" (3+2i)(4+5i) = " + Comp.toString(

Comp.mult(Comp.mkComplex(3, 2),

Comp.mkComplex(4, 5))));

System.out.println(" Ex 10.2.14 = " +

Comp.toString(

Comp.sub(

Comp.div(Comp.mkComplex(1, -2),

Comp.mkComplex(3, 4)),

Comp.div(Comp.mkComplex(2, 1),

Comp.mkComplex(0, 5)))));

}

}

Om vi i en metod gör denna deklaration Complex result; händer detta:

Om vi i en metod gör detta: eller (samma sak):

Complex result; Complex result = new Complex();

result = new Complex();

händer detta:

I resten av metoden och i andra metoder, kan vi i uttryck använda resultat.re och resultat.im för att räkna på komponternas värden, och resultat.re = .. och resultat.im = .. för att ge komponeterna nya värden. Om vi i en metod i en annan klass

definierar Complex z = new Complex() ; kan vi även i den metoden i den klassen använda z.re och z.im på samma sätt.

Så här ser det ut i datorn just före return result;

i metoden mkComplex när main gjort anropet Comp.mkComplex(3, 4)).

Körresultat när vi kör TestComplex :

(3+4i)= 3.0 + 4.0i

(3+4i)/(1-2i) = -0.9999999999999998 + 1.9999999999999996i

(3+2i)(4+5i) = 2.0 + 23.0i

Ex 10.2.14 = -0.4 + 0.0i

Det sätt på vilket vi nu använt Java motsvarar vad som i t ex programspråket Pascal kallas (en pekare till) en post, eller på engelska a record. En post kan innehålla många delar av olika typer, dvs instansvariablerna kan deklareras med valfri typ. I arrayer måste alla komponter vara av samma typ. Att vi i vårt fall med komplexa tal kan välja mellan en array-implemetation och en "record"-implementation beror på att både realdel och imaginärdel är flyttal, dvs double .

Det vi lärt oss fram till nu motsvarar ungefär en första kurs i programmering med ett traditionelt imperativt språk, som Pascal eller C. Traditionell imperativ programmering i Java innebär att man använder klassmetoder och klassvariabler, dvs static. Vi kommer nu att gradvis gå över till att använda Java på ett objekt-inriktat sätt, det vanliga sättet att använda Java.

Komplexa tal definierade med en klass med privata instansvariabler.

Vi ska implementera en ny egendefinerad typ Complex för komplexa tal i en klass Complex med privata instanvariabler, en privat instansvariabel för det komplexa talets realdel och en privat instansvariabel för det komplexa talets imaginärdel. Detta göres genom att skriva private istället för public när vi definierar instansvariablerna.

I resten av metoden och i andra metoder i samma klass kan vi som förut i uttryck använda resultat.re och resultat.im för att räkna på komponternas värden, och resultat.re = .. och resultat.im = .. för att ge komponeterna nya värden,

men .re och.im kan inte användas för att komma åt realdel och imaginärdel i andra klasser, eftersom instansvariablerna är "privata" för klassen.

Att på detta sätt skydda instansvariablerna kallas att man gör Complex till en abstarkt datatyp (ADT). Men vi måste fortfarande kunna skapa nya komplexa tal och kunna få tillgång till ett komplext tals realdel och imaginärdel. Vi ändrar därför klassen Complex på detta sätt:

public class Complex {

private double re;

private double im;

public Complex(double ire, double iim) { // En "Konstuktor"

re = ire; // Används för att skapa nya komplexa tal

im = iim; // med startvärden för instansvariblerna

}

public double re(){ //instansmetod för att få realdelen

return re;

}

public double im(){ //instansmetod för att få imaginärdelen

return im;

}

}I den tidigare lösningen hade vi en "default" konstruktor Complex() som om vi skrivit den själva sett ut så här :

public Complex() { // Java framställer själv denna definition

}

Märk att en konstruktor har samma namn som klassen och att det inte står void eller någon returtyp i konstruktorns rubrik! I ett användande program när man skapar nya objekt används alltid ett anrop till en konstruktor efter det reserverade ordet new. Skriver man en egen konstruktor får man ingen "default" konstruktor Complex().

De två metoderna för att "komma åt" realdel respektive imaginärdel är instansmetoder, metoder som hör till varje komplext tal (dvs till varje instans av typen Complex ). Märk att det i metodrubrikernaD inte står static ! Man kan (förenklat) tänka sig att i varje objekt (varje komplext tal) kommer det att finnas de två metoderna re() och im().

Om vi i en metod gör detta: eller (samma sak):

Complex result; Complex result = new Complex(3.0, 4.0);

result = new Complex(3.0, 4.0);

händer detta:

Vi ska implementera algebran för komplexa tal i en klass Comp med hjälpa av den nya typen Complex. I klassen skall finnas definitioner för samma klassmetoder som fanns när vi skrev Comp med hjälp av en klass med publika klassvariabler eller med tvåelementssarrayer:

public class Comp {

public static Complex mkComplex(double ire, double iim) {

return new Complex(ire, iim);

}

public static boolean isEqual(Complex z1, Complex z2){

return (re(z1) == re(z2)) && (im(z1) == im(z2));

}

public static Complex add(Complex z1, Complex z2){

return new Complex(re(z1) + re(z2), im(z1) + im(z2));

}

public static Complex sub(Complex z1, Complex z2){

return new Complex(re(z1) - re(z2), im(z1) - im(z2));

}

public static Complex mult(Complex z1, Complex z2){

return new Complex(re(z1)*re(z2) - im(z1)*im(z2),

re(z1)*im(z2) + im(z1)*re(z2));

}

public static Complex conjugate(Complex z1){

return new Complex(re(z1), - im(z1));

}

public static double modulus(Complex z1){

return Math.sqrt(re(z1)*re(z1) + im(z1)*im(z1));

}

public static Complex div(Complex z1, Complex z2){

if (isEqual(z2, new Complex(0,0))) {

throw new RuntimeException("Tried to divid by 0 + 0i");

} else {

double mo = modulus(z2);

double dd = mo*mo;

Complex zz = mult(z1, conjugate(z2));

return new Complex( re(zz)/dd, im(zz)/dd);

}

}

public static double re(Complex z1){

return z1.re();

}

public static double im(Complex z1){

return z1.im();

}

public static String toString(Complex z1) {

return re(z1) + " + " + im(z1) + "i";

}

}

Märk att anrop till en instansmetod i en annan klass föregås av namnet på det objekt som "rår om metoden" följt av en punkt!

Anrop till en klassmetod i en annan klass föregås normalt av namnet på klassen där metoden definierats följt av en punkt! Jämför hur metoderna ritas i minnesbilderna!

Vi kan nu göra beräkningar på komplexa tal med TestComplex , samma program som vi använde förut.

Men vi kan även skriva om TestComplex och direkt använda konstruktorn

Complex(.., ...) istället för mkComplex(.., ...) :

public class TestComplex {

public static void main(String [] iargs) {

System.out.println(" (3+4i)= " +

Comp.toString(new Complex(3, 4)));

System.out.println(" (3+4i)/(1-2i) = " + Comp.toString(

Comp.div(new Complex(3, 4),

new Complex(1, -2))));

System.out.println(" (3+2i)(4+5i) = " + Comp.toString(

Comp.mult(new Complex(3, 2),

new Complex(4, 5))));

System.out.println(" Ex 10.2.14 = " +

Comp.toString(

Comp.sub(

Comp.div(new Complex(1, -2),

new Complex(3, 4)),

Comp.div(new Complex(2, 1),

new Complex(0, 5)))));

}

}

Skulle man inte kunna tänka sig att flytta alla klassmetoder i Comp till Complex och göra

dem till instansmetoder istället? Jo, det är detta som är Objekt-inriktad (objekt-orienterad, OO) programmering och är det vanliga sättet att använda Java. Att göra komplexa tal på ett objektorienterat sätt kommer vi att göra kommande vecka.

Först ytterligare ett exempel på olika implementationer av en typ Time. Denna typ definieras i DD kapitel 8 direkt på ett objekt-orienterat sätt, men här kommer typen att defineras på flera olika sätt motsvarande de olika sätt vi implementerat Complex.

Tid (timmar, minuter, sekunder) definierade med arryer.

import java.text.DecimalFormat; // used for number formatting

// This class maintains the time in 24-hour format in an int []

public class Ti {

public static int [] mkTime(int ih, int im, int is) {

int [] result = new int [3];

result[0] = ih;

result[1] = im;

result[2] = is;

return result;

}

public static int [] mkTime() {

int [] result = new int [3];

return result;

}

// Change a time value using universal time. Perform

// validity checks on the data. Set invalid values to zero.

public static void setTime(int[] iTime, int h, int m, int s ){

iTime[0] = ( ( h >= 0 && h < 24 ) ? h : 0 );

iTime[1] = ( ( m >= 0 && m < 60 ) ? m : 0 );

iTime[2] = ( ( s >= 0 && s < 60 ) ? s : 0 );

}

// Convert a time to a String in universal-time format

public static String toUniversalString(int [] iTime){

DecimalFormat twoDigits = new DecimalFormat( "00" );

return twoDigits.format( iTime[0] ) + ":" +

twoDigits.format( iTime[1] ) + ":" +

twoDigits.format( iTime[2]);

}

}

Ett testprogram:

public class TimeTest {

public static void main( String args[] ) {

int [] t1 = Ti.mkTime();

System.out.println("Klockan är " + Ti.toUniversalString(t1));

int [] t = Ti.mkTime(9, 15, 00);

System.out.println("Klockan är " + Ti.toUniversalString(t));

Ti.setTime(t, 10, 0 ,0);

System.out.println("Klockan är " + Ti.toUniversalString(t));

}

}

/*Körresultat :

Klockan är 00:00:00

Klockan är 09:15:00

Klockan är 10:00:00

*/

Tid definierad med en klass med publika instansvariabler.

// This class maintains the time in 24-hour format

public class Time {

public int hour;

public int minute;

public int second;

}

import java.text.DecimalFormat; // used for number formatting

// This class contains time methods

public class Ti {

public static Time mkTime(int ih, int im, int is) {

Time result = new Time();

result.hour = ih;

result.minute = im;

result.second = is;

return result;

}

public static Time mkTime() {

Time result = new Time();

return result;

}

// Change a time value using universal time. Perform

// validity checks on the data. Set invalid values to zero.

public static void setTime(Time iTime, int h, int m, int s ){

iTime.hour = ( ( h >= 0 && h < 24 ) ? h : 0 );

iTime.minute = ( ( m >= 0 && m < 60 ) ? m : 0 );

iTime.second = ( ( s >= 0 && s < 60 ) ? s : 0 );

}

// Convert a time to a String in universal-time format

public static String toUniversalString(Time iTime){

DecimalFormat twoDigits = new DecimalFormat( "00" );

return twoDigits.format( iTime.hour ) + ":" +

twoDigits.format( iTime.minute) + ":" +

twoDigits.format( iTime.second);

}

}

public class TimeTest { //Testprogram

public static void main( String args[] ) {

Time t1 = Ti.mkTime();

System.out.println("Klockan är " + Ti.toUniversalString(t1));

Time t = Ti.mkTime(9, 15, 00);

System.out.println("Klockan är " + Ti.toUniversalString(t));

Ti.setTime(t, 10, 0 ,0);

System.out.println("Klockan är " + Ti.toUniversalString(t));

}

} // Samma resultat som förut

Tid definerad med en klass med privata instanvariabler.

// This class maintains the time in 24-hour format

public class Time {

private int hour;

private int minute;

private int second;

public Time() { //Constructor

}

public Time(int ih, int im, int is) { //Constructor (another)

hour = ih; minute = im; second = is;

}

public int getHour() {

return hour;

}

public int getMinute() {

return minute;

}

public int getSecond() {

return second;

}

public void setHour(int ih) {

hour = ih;

}

public void setMinute(int im) {

minute = im;

}

public void setSecond(int is) {

second = is;

}

}

import java.text.DecimalFormat; // used for number formatting

// This class contains time methods

public class Ti {

public static Time mkTime(int ih, int im, int is) {

return new Time(ih, im, is);

}

public static Time mkTime() {

return new Time();

}

// Change a time value using universal time. Perform

// validity checks on the data. Set invalid values to zero.

public static void setTime(Time iTime, int h, int m, int s )

{

iTime.setHour( ( h >= 0 && h < 24 ) ? h : 0 );

iTime.setMinute( ( m >= 0 && m < 60 ) ? m : 0 );

iTime.setSecond( ( s >= 0 && s < 60 ) ? s : 0 );

}

// Convert a time to a String in universal-time format

public static String toUniversalString(Time iTime)

{

DecimalFormat twoDigits = new DecimalFormat( "00" );

return twoDigits.format( iTime.getHour() ) + ":" +

twoDigits.format( iTime.getMinute() ) + ":" +

twoDigits.format( iTime.getSecond() );

}

}

Samma testprogram som förut kan användas :

public class TimeTest {

public static void main( String args[] ) {

Time t1 = Ti.mkTime();

System.out.println("Klockan är " + Ti.toUniversalString(t1));

Time t = Ti.mkTime(9, 15, 00);

System.out.println("Klockan är " + Ti.toUniversalString(t));

Ti.setTime(t, 10, 0 ,0);

System.out.println("Klockan är " + Ti.toUniversalString(t));

}

}

Testprogramet kan också direkt använda konstruktorerna:

public class TimeTest {

public static void main( String args[] ) {

Time t1 = new Time();

System.out.println("Klockan är " + Ti.toUniversalString(t1));

Time t = new Time(9, 15, 00);

System.out.println("Klockan är " + Ti.toUniversalString(t));

Ti.setTime(t, 10, 0 ,0);

System.out.println("Klockan är " + Ti.toUniversalString(t));

}

}

Tid definerad med en OO-klass.

Skulle man inte kunna tänka sig att flytta alla klassmetoder i Ti till Time och göra

dem till instansmetoder istället? Jo, det är detta som är objekt-inriktad (objekt-orienterad, OO) programmering och är det vanliga sättet att använda Java. Att programera Time på detta sätt görs i DD i kapitel 8. Vi kommer också att i kommande föreläsningar programmera Complex på detta sätt.

// This class maintains the time in 24-hour format

public class Time {

private int hour;

private int minute;

private int second;

public Time() { //Constructor

}

public Time(int ih, int im, int is) { //Constructor

hour = ih; minute = im; second = is;

}

public int getHour() {

return hour;

}

public int getMinute() {

return minute;

}

public int getSecond() {

return second;

}

public void setHour(int ih) {

hour = ih;

}

public void setMinute(int im) {

minute = im;

}

public void setSecond(int is) {

second = is;

}

// Change a time value using universal time. Perform

// validity checks on the data. Set invalid values to zero.

public void setTime(int h, int m, int s ) { //Parmetern av typ

hour = ( ( h >= 0 && h < 24 ) ? h : 0 ); // Time borta!

minute = ( ( m >= 0 && m < 60 ) ? m : 0 ); // Arbetar med

second = ( ( s >= 0 && s < 60 ) ? s : 0 ); // detta (this)

} // objekt, med

// "sig själv"

// Convert a time to a String in universal-time format

public String toString() { //DD toUniversalString

DecimalFormat twoDigits = new DecimalFormat( "00" );

return twoDigits.format( hour ) + ":" +

twoDigits.format( minute ) + ":" +

twoDigits.format( second );

}

}

Testprogramet måste nu skrivas om så att tidsobjekten använder "sina egna" metoder:

public class TimeTest {

public static void main( String args[] ) {

Time t1 = new Time();

System.out.println("Klockan är " + t1.toString());

Time t = new Time(9, 15, 00);

System.out.println("Klockan är " + t.toString());

t.setTime(10, 0 ,0);

System.out.println("Klockan är " + t.toString());

}

}

Hemuppgifter redovisning v44.

1. CBA-hemuppgift : Vi ska implementera linjär-algebra-vektorer i planet med klassdefinition med enbart publika instanvariabler och inga metoder.

Gör så här : Skapa lämpligen en egen katalog vectorRecord för denna uppgift.

Skriv en ny klass Vector.java utan instansmetoder med en publik instansvariabel

för vektorns x-komponent och en publik instansvariabel för vektorns y-komponent.

Kopiera filen med klassen Vect från hemuppgift 1 i förra hemupggifterna. Modifiera sedan den filen så att den istället för typen double [] använder typen Vector definierad med klassen Vector.java.

Detta kan göras snabbt om du lär dig använda Query Replace i Emacs-menyn Search.

Testkör klassen Vect med samma testprogram som förut.

2. CBA-hemuppgift : Linjär-algebra-vektorer i planet implementerade med klassdefinition med enbart privata instansvariabler och access- metoder ("Get-metoder").

Skapa lämpligen en egen katalog vectorADT för denna uppgift.

Skriv i denna hemuppgift klassen Vector.java med en privat instanvariabel

för vektorns x-komponent och en privat instanvariabel för vektorns y-komponent. För att "komma åt" x-komponenten rerspektive y-komponenten behövs också två publika acess-metoder. Dessutom måste en konstruktor skrivas för att skapa nya

vektorer i planet.

Kopiera filen med klassen Vect från hemuppgift 1 ovan . Kan den kompileras med vår nya variant av klassen Vector.java ? Pröva! Förklara varför det inte går.

Modifiera sedan den filen med klassen Vect så att den använder typen Vector definierad med vår nya variant av klassen Vector.java.

Testkör klassen Vect med samma testprogram som förut.

Sriv och testa också en variant av testprogrammet som inte använder Vect.mkVector( ..) utan i stället använder new och en konstruktor i Vector.

3. B-hemuppgift :

Lös uppgift 7.38 i DD sid 369 som en applikation. Det är OK om labyrinten definieras in med arrayinitiering, dvs med

...= {{..}, {...}, ... };

Lösningen skall förutom main-metod och metoden mazeTraverse innehålla en metod för utskrift. Exempel på körning, som hemuppgift 4 men all inmatning saknas, dvs startlabyrinten visas och sedan ritas labyrinten om och om igen med ett nytt X för varje ny utskrift tills man "fastnar" eller kommer ut ur labyrinten..

4. A-hemuppgift :

Som hemuppgift 3, men labyrinten skall vid körningen kunna definieras antingen genom ett slumpförfarande eller genom inmatning från tangentbordet. Lösningen skall förutom main-metod och metoden mazeTraverse innehålla en metod för utskrift, en metod för inläsning och en metod för slumpgenerering av en labyrint. Exempel på körning (inmatning kursiverat):

.......>java MazeTraversalA

Vill du slumpa fram en labyrint ge 1 : 1

Ge antalet rader i labyrinten : 5

Ge antalet kolonner i labyrinten : 20

Ge sannolikheten p för vägg i viss ruta i labyrinten (0.0<= p <=1.0): 0.25

#..#..#.###..#......

...##.....#.#.......

###.##..##.#...#.#.#

.#.....#...#.#....#.

...#....##.#.......#

Ge startrad i labyrinten : 2

Ge startkolonn i labyrinten : 1

#..#..#.###..#......

X..##.....#.#.......

###.##..##.#...#.#.#

.#.....#...#.#....#.

...#....##.#.......#

#..#..#.###..#......

XX.##.....#.#.......

###.##..##.#...#.#.#

.#.....#...#.#....#.

...#....##.#.......#

..osv.

Vill man istället mata in labyrinten från tangentbordet så svarar man inte 1 på första frågan. Inmatning sker sedan ungefär som inmatningen av biotopen i sista hemuppgiften förra veckan, du kan bestämma detaljerna själv.