Računalništvo 1 (PRA)

lekcije / Objektno programiranje v Javi

Objektno programiranje v Javi

Java je objektni programski jezik, zato glavno vlogo igrajo objekti. Objekt je skupek podatkov, s katerim želimo uporavljati kot s celoto. Vsak objekt pripada nekemu razredu. Če pripada objekt x razredu C, potem pravimo tudi da je xobjekt tipaC. Nekaj primerov objektov iz Javine standardne knjižnice: Seveda pa so objekti uporabni predvsem zato, ker lahko programer definira nove razrede in objekte.
Osnovni pristop objektnega programiranja bi lahko strnili takole:
Objektno programiranje
objektpodatek + metode za delo s podatkom
računanjeobjektu povemo, katero metodo naj izvede
Ko želimo s kakim podatkom/objektom kaj narediti, mu signaliziramo, kaj naj se zgodi. To storimo tako, da pokličemo ustrezno metodo v objektu. Primerjamo ta princip s klasičnim programiranjem, kjer velja naslednji pogled:
Klasično programiranje
programpodatkovne strukture + funkcije
računanjeizvedemo funkcijo na podatkih
Ko želimo kak podatek obdelati v klasičnem programiranju, to storimo tako, da pokličemo ustrezno metodo in ji podamo kot argument podatke, ki naj jih obdela.
Razlika med obema pristopoma je razvidna že iz tega, kako v objektnem in klasičnem programiranju pokličemo metodo f na podatku x:
1
2
3
4
5
// klasično: izvedi staticno metodo f na podatku x
f(x);

// objektno: podatku/objektu x signaliziraj, da naj izvede metoda f
x.f();

Komponente in konstruktorji

Oglejmo si primer objektnega programiranja. Denimo, da bi radi napisali program, ki vodi evidenco o študentih. Podatki o študentu obsegajo ime, priimek, letnico vpisa na univerzo in vpisno številke (seveda je to poenostavljen primer). Torej objekt, ki predstavlja študenta, vsebuje štiri podatke:
Student.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student {
    public String ime;
    public String priimek;
    public int leto_vpisa;
    public String vpisna_st;
    
    public Student(String ime, String priimek, int l, String v) {
      this.ime = ime;
      this.priimek = priimek;
      this.leto_vpisa = l;
      this.vpisna_st = v;
    }
}
S tem smo povedali, da je vsak objekt tipa Student sestavljen iz štirih komponente: ime, priimek, leto_vpisa, vpisna_st.
Če je a objekt tipa Student, potem lahko dostopamo do njegove komponente ime tako, da napišemo a.ime.
Nov objekt naredimo z ukazom new, kot to kaže naslednji primer:
TestStudent.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TestStudent {
    public static void main(String args[]) {

	Student a = new Student();
	a.ime = "Miha";
	a.priimek = "Novak";
	a.leto_vpisa = 2000;
	a.vpisna_st = "40002304";

	Student b = new Student();
	b.ime = "Mojca";
	b.priimek = "Zver";
	b.leto_vpisa = 2001;
	b.vpisna_st = "40004377";

	Student c = b;
	c.ime = "Katarina";

	System.out.println("Student a:\n" + a.ime + " " + a.priimek +
                           " " + a.leto_vpisa + " (" + a.vpisna_st + ")\n");

	System.out.println("Student b:\n" + b.ime + " " + b.priimek +
                           " " + b.leto_vpisa + " (" + b.vpisna_st + ")\n");

	System.out.println("Student c:\n" + c.ime + " " + c.priimek +
                           " " + c.leto_vpisa + " (" + c.vpisna_st + ")\n");
    }
}
> javac Student.java TestStudent.java
> java TestStudent
Student a:
Miha Novak 2000 (40002304)

Student b:
Katarina Zver 2001 (40004377)

Student c:
Katarina Zver 2001 (40004377)
V programu smo naredili dva nova objekta razreda Student, ki sta shranjena v spremenljivkah a in b. Kot vedno, se nove objekte naredi z ukazom new. Spremenljivka c pa je isti objekt kot b, se pravi, da se v 17. vrstici ni naredila nova kopija objekta b, ampak se spremenljivki b in csklicujeta na isti objekt.
V vrsticah 4—8 smo naredili objekt a in nastavili vrednosti njegovih komponent. Takole nastavljanje je v praksi dokaj neprimerno, ker se zlahka zgodi, da kako komponento pozabimo nastaviti. Zato Java omogoča, da delamo nove objekte na bolj praktičen način s pomočjo konstruktorjev. Konstruktor je posebna metoda, ki ima enako ime kot je ime razreda. Konstruktor uporabimo v ukazu new. Java najprej naredi nov objekt, nato pa pokliče konstruktor. Znotraj konstruktorja se pravkar narejeni objekt imenuje this. Primer:
Student2.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student2 {
    public String ime;
    public String priimek;
    public int leto_vpisa;
    public String vpisna_st;

    public Student2(String i, String p, int l, String v) {
	this.ime = i;
	this.priimek = p;
	this.leto_vpisa = l;
	this.vpisna_st = v;
    }
}
TestStudent2.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TestStudent2 {
    public static void main(String args[]) {

	Student2 a = new Student2("Miha", "Novak", 2000, "40002304");

	Student2 b = new Student2("Mojca", "Zver", 2001, "40004377");

	Student2 c = b;
	c.ime = "Katarina";

	System.out.println("Student a:\n" + a.ime + " " + a.priimek +
                           " " + a.leto_vpisa + " (" + a.vpisna_st + ")\n");

	System.out.println("Student b:\n" + b.ime + " " + b.priimek +
                           " " + b.leto_vpisa + " (" + b.vpisna_st + ")\n");

	System.out.println("Student c:\n" + c.ime + " " + c.priimek +
                           " " + c.leto_vpisa + " (" + c.vpisna_st + ")\n");
    }
}
Zgornji program deluje povsem enako kot program TestStudent.java, je pa vsekakor bolj pregleden. Namen konstruktorja je, da nastavi vrednosti komponent objekta. Ker konstruktorji (pa tudi druge metode znotraj razreda) zelo pogosto dostopajo do komponent, nam Java omogoča, da namesto this.komponenta pišemo kar komponenta:
Student3.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student3 {
    public String ime;
    public String priimek;
    public int leto_vpisa;
    public String vpisna_st;

    public Student3(String i, String p, int l, String v) {
	ime = i;
	priimek = p;
	leto_vpisa = l;
	vpisna_st = v;
    }
}
Ponovimo, kaj smo se do sedaj naučili:
  1. Vsak objekt razreda Bla ima svojo kopijo vseh komponent, ki so naštete v definiciji razreda Bla.
  2. Do komponente foo v objektu a dostopamo z a.foo.
  3. Konstruktorji so posebne metode, s katerimi nastavimo komponente novih objektov. V konstruktorju se novo nastali objekt imenuje this.
  4. Namesto this.foo lahko pišemo foo.
Za vajo definirajmo razred Kompleksno za delo s kompleksnimi števili. Ker je kompleksno število podano z realno in imaginarno komponento, ima razred Kompleksno dve komponenti tipa double, ki ju poimenujemo re in im. Napišemo tudi konstruktor:
1
2
3
4
5
6
7
8
9
public class Kompleksno {
    public double re;
    public double im;

    public Kompleksno(double x, double y) {
	this.re = x;
	this.im = y;
    }
}
Konstruktorjev v danem razredu je lahko več. Na primer, napišemo lahko še konstruktor, ki sprejme samo realni del števila, imaginarnega pa nastavi na nič:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Kompleksno {
    public double re;
    public double im;

    public Kompleksno(double x, double y) {
	re = x;
	im = y;
    }

    public Kompleksno(double x) {
	re = x;
	im = 0.0;
    }
}
Primer uporabe:
TestKompleksno.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestKompleksno {
    public static void main(String args[]) {

	Kompleksno z = new Kompleksno(2.0, -3.0);

	Kompleksno v = new Kompleksno(5.0);

	System.out.println("z.re = " + z.re);
	System.out.println("z.im = " + z.im);

	System.out.println("v.re = " + v.re);
	System.out.println("v.im = " + v.im);
    }
}
> java TestKompleksno
z.re = 2.0
z.im = -3.0
v.re = 5.0
v.im = 0.0
Objekt z smo naredili tako, da smo podali realni in imaginarni del, objekt v pa tako, da smo podali le realni del, zato se je izvedel drugi konstruktor, ki je nastavil imaginarni del na 0.0.

Objektne metode

Poleg definicije komponent in konstruktorjev vsebuje razred tudi definicije objektnih metod. Metodam pravimo tudi funkcije in podprogrami. Metoda je zaporedje ukazov, ki kaj izračunajo in vrnejo rezultat.
Objektna metoda se razlikujejo od običajnih funkcij v ostalih programskih jezikih in od statičnih metod v Javi v tem, da jo vedno kličemo na objektu. V metodi se objekt, na katerem je poklicana, imenuje this.
Zapomnimo si:
  1. Objektno metodo vedno kličemo na objektu.
  2. V metodi se objekt, na katerem je klicana, imenuje this.
Za primer dodajmo razredu Kompleksno metodo abs, ki izračuna absolutno vrednost kompleksnega števila:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Kompleksno {
    public double re;
    public double im;

    public Kompleksno(double x, double y) {
	re = x;
	im = y;
    }

    public Kompleksno(double x) {
	re = x;
	im = 0.0;
    }

    public double abs() {
	return Math.sqrt(this.re * this.re + this.im * this.im);
    }
}
Metodo abs() kličemo takole:
Kompleksno z = new Kompleksno(5.0, -12.0);
double r = z.abs();
Lahko bi napisali tudi takole:
double r = (new Kompleksno(5.0, -12.0)).abs();
Napišimo še metode, ki računajo osnovne operacije nad kompleksnimi števili, in metodo, ki kompleksno število pretvori v niz:
Kompleksno vsota(Kompleksno z)
vrne vsoto kompleksnega števila (this) in kompleksnega števila z.
void pristej(Kompleksno z)
kompleksnemu številu prišteje kompleksno število z.
Kompleksno produkt(Kompleksno z)
vrne produkt kompleksnega števila (this) in kompleksnega števila z.
String toString()
kompleksno število pretvori v niz. Če tako metodo dodamo razredu, lahko objekte razreda prištevamo nizom in le-ti se bodo pretvorili v nize.
Kompleksno.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Kompleksno {
  public double re;
  public double im;
  
  public Kompleksno(double x, double y) {
    this.re = x;
    this.im = y;
  }
  
  public Kompleksno(double x) {
    this.re = x;
    this.im = 0.0;
  }
  
  public static double abs(Kompleksno z) {
    return Math.sqrt(z.re * z.re + z.im * z.im);
  }
  
  public double abs() {
    return Math.sqrt(this.re * this.re + this.im * this.im);
  }
  
  public static Kompleksno vsota(Kompleksno z, Kompleksno w) {
    return new Kompleksno(z.re+w.re, z.im+w.im);
  }
  
  public Kompleksno vsota(Kompleksno z) {
    return new Kompleksno(this.re + z.re, this.im + z.im);
  }
  
  public Kompleksno kvadrat() {
    return this.produkt(this);
  }
  
  public void pristej(Kompleksno z) {
    this.re += z.re;
    this.im += z.im;
  }
  
  public Kompleksno produkt(Kompleksno z) {
    return new Kompleksno(re * z.re - im * z.im, re * z.im + im * z.re);
  }
  
 public String toString() {
    return this.re + " + " + this.im + " * i";
 }
}
Preizkusimo delovanje metod; izračunajmo vsoto kompleksnih števil
z0 + z1 + ... + zn-1
kjer je z = cos(2π/n) + i sin(2π/n).
Vsota.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Vsota {
    public static void main(String args[]) {
	final int N = 1000000;
	final double phi = 2.0 * Math.PI / N;
	Kompleksno z = new Kompleksno(Math.cos(phi), Math.sin(phi));
	Kompleksno s = new Kompleksno(0.0);
	Kompleksno w = new Kompleksno(1.0);

	for (int k = 0; k < N; k++) {
	    s.pristej(w);
	    w = w.produkt(z);
	}

	System.out.println("Vsota je enaka " + s);
    }
}
> javac Vsota.java
> java Vsota
Vsota je enaka -6.228765392357616E-9 + 2.439338273715903E-7 * i
Naloga 1: Ulomki (racionalna števila)
Kot vemo je ulomek ali racionalno število p/q podano s števcemp in imenovalcemq, ki sta celi števili, pri čemer velja še q ≠ 0. Definirajmo razred Ulomek za delo z ulomki:
1
2
3
4
public class Ulomek {
  public int st; // stevec
  public int im; // imenovalec
}
Seveda je treba razred opremiti še s konstruktorji in metodami:
  1. Napiši konstruktor, ki sprejme vrednost števca in imenovalca ter nastavi vrednost komponent st in im.
  2. Napiši konstruktor, ki sprejme celo število k in naredi ulomek k/1.
  3. Napiši objektno metodo void pokrajsaj(), ki pokrajsa ulomek this. Verjetno se splača napisati še pomožno metodo, ki vrne največji skupni delitelj dveh celih števil.
  4. Napiši objektno metodo boolean equals(Ulomek a), ki vrne true, če sta ulomka this in a enaka, sicer vrne false.
  5. Napiši objektno metodo int compareTo(Ulomek a), ki primerja ulomka this in a in vrne:
    • negativno število, če je this < a.
    • število 0, če je this = a.
    • pozitivno število, če je this > a.
  6. Napiši objektno metodo String toString(), ki vrne niz znakov, ki predstavlja ulomek. Na primer, ulomek new Ulomek(-18, -6) predstavimo z nizom "-18/-6".
  7. Napiši objektni metodi Ulomek vsota(Ulomek a) in void pristej(Ulomek a). Prva metoda vrne nov ulomek, ki je enak vsoti this in a, druga pa ne vrača ničesar, ampak samo prišteje ulomek a ulomku this.
  8. Napiši objektni metodi za odštevanje Ulomek razlika(Ulomek a) in void odstej(Ulomek a).
  9. Napiši objektni metodi za množenje Ulomek zmnozek(Ulomek a) in void zmnozi(Ulomek a).
  10. Napiši objektni metodi za delenje Ulomek kvocient(Ulomek a) in void deli(Ulomek a).
Vse metode tudi preizkusi.
Naloga 2: Polinomi
Polinom
p(x) = a0 + a1 x + ... + an xn
predstavimo s tabelo ulomkov Ulomek[] p = {a0, a1, ..., an}. Ničelni polinom p(x) = 0 predstavimo s prazno tabelo.
Napiši ustrezne komponente, konstruktorje in naslednje metode:
  1. metoda int stopnja(), ki vrne stopnjo polinoma. Po dogovoru je stopnja ničelnega polinoma enaka −1.
  2. metoda Ulomek vrednost(Ulomek a), ki vrne vrednost polinoma this v točki x = a. Uporabi Hornerjev algoritem!
  3. metoda boolean equals(Polinom q), ki vrne true, če sta polinoma this in q enaka, sicer vrne false.
  4. metoda String toString(), ki vrne niz, ki predstavlja polinom this. Na primer, polinom 3/4 + 7/2 x2 + 10 x3 predstavimo z nizom "3/4 + 7/2 x^2 + 10/1 x^3".
  5. metoda Polinom vsota(Polinom q), ki vrne vsoto polinomov this in q.
  6. metoda Polinom zmnozek(Polinom q), ki vrne zmnozek polinomov this in q.
Vse metode tudi preizkusi.

Vaje iz objektnega programiranja

Na vajah boste sestavili razrede in objekte za predstavitev podatkov (ime, priimek in datum rojstva) o osebah.
Naloga 3
Naloga 4
Definiraj ustrezne konstruktorje in metodo public String toString(), ki predstavi osebo z nizom znakov.
Razred preizkusi tako, da narediš osebo, ki predstavlja tebe.
Naloga 5
Naloga 6
Naloga 7
Naloga 8
Naloga 9