Oliot luokan kenttinä: Piste ja Suorakulmio
Luokan kenttien tyyppinä voi olla toinen luokka.
Kirjoitetaan luokka Piste tason pisteen esittämiseen. Käytetään sitten Piste-luokkaa apuna luokan Suorakulmio määrittelyssä. Suorakulmio esitetään kahden kulmapisteen avulla.
Luokan Piste kentät:
int xKoord
int yKoord
Luokan Piste konstruktorit ja metodit
Piste()
Piste(int x, int y)
int annaX()
int annaY()
void siirrä(int dx, int dy)
double etaisyys(Piste piste1)
String toString()
Luokan Suorakulmio kentät
Piste kulma1
Piste kulma2
Luokan Suorakulmio konstruktorit ja metodit
Suorakulmio(Piste piste1, Piste piste2)
Piste alakulma()
Piste ylakulma()
int leveys()
int korkeus()
int pintaAla()
String toString()
class Piste {
private int xKoord;
private int yKoord;
//---- konstruktorit: ------
public Piste() {
xKoord = 0;
yKoord = 0;
}
public Piste(int x, int y) {
xKoord = x;
yKoord = y;
}
//---- aksessorit: ------
public int annaX() {
return xKoord;
}
public int annaY() {
return yKoord;
}
public void siirra(int dx, int dy) {
xKoord = xKoord + dx;
yKoord = yKoord + dy;
}
//---- Muut metodit -----
public double etaisyys(Piste piste1) {
double eta;
eta = Math.sqrt(nelio(this.xKoord - piste1.xKoord) +
nelio(this.yKoord - piste1.yKoord));
return eta;
}
private static int nelio(int luku) {
return luku * luku;
}
public String toString() {
return "(" + xKoord + ", " + yKoord + ")";
}
}
class Suorakulmio {
Piste kulma1;
Piste kulma2;
//----- konstruktori: --------
public Suorakulmio(Piste piste1, Piste piste2) {
kulma1 = piste1;
kulma2 = piste2;
}
//----- aksessorit: ----------
public Piste alakulma() {
if (kulma1.annaY() < kulma2.annaY())
return kulma1;
else
return kulma2;
}
public Piste ylakulma() {
if (kulma1.annaY() > kulma2.annaY())
return kulma1;
else
return kulma2;
}
//---- muut metodit: ---------
public int leveys() {
int lev;
lev = Math.abs(kulma1.annaX() - kulma2.annaY());
return lev;
}
public int korkeus() {
int kork;
kork = Math.abs(kulma1.annaY() - kulma2.annaY());
return kork;
}
public int pintaAla() {
int lev, kork, ala;
lev = this.leveys();
kork = this.korkeus();
ala = lev * kork;
return ala;
}
public String toString() {
return kulma1.toString() + " - " + kulma2.toString();
}
}
public class Kulmiotesti {
public static void main(String[] args) {
Piste p1, p2;
Suorakulmio kulmio;
int ala;
p1 = new Piste();
p2 = new Piste(4,8);
System.out.println("p1: " + p1 + " p2: " + p2);
System.out.println("etäisyys: " + p1.etaisyys(p2));
kulmio = new Suorakulmio(p1, p2);
System.out.println("kulmio: " + kulmio);
ala = kulmio.pintaAla();
System.out.println("kulmion pinta-ala: " + ala);
p2.siirra(-1, -2);
System.out.println("p2: " + p2);
System.out.println("kulmio: " + kulmio);
}
}
Tässä on olettettu, että luokat Piste, Suorakulmio ja Kulmiotesti on kirjoitettu samaan tiedostoon. Sen vuoksi vain viimeisen luokan otsikossa on määre public.
Huomautuksia olioista
Olion vaatimaa muistitilaa ei tarvitse mitenkään vapauttaa. Javassa on automaattien roskankerääjä, joka vapauttaa olion vaatiman tilan sen jälkeen, kun mikään muuttuja ei enää viittaa olioon.
Olion voi luoda jo samalla kuin määrittelee oliomuuttujan, esimerkiksi
public class Vektoritesti4 {
public static void main(String[] args) {
Vektori aVektori = new Vektori(3, 4);
toimii samalla tavalla kuin versio
public class Vektoritesti4 {
public static void main(String[] args) {
Vektori aVektori;
aVektori = new Vektori(3, 4);
Virhetilanteiden käsittely metodin paluuarvon avulla
Miten voidaan kertoa kutsuvalle ohjelmalle, että metodin normaali suoritus ei onnistu (esimerkiksi limsa-automaatissa ei ole limsapulloja tai käyttäjä ei ole antanut tarpeeksi rahaa)?
Monesti metodin suorituksen onnistumisesta kerrotaan metodin paluuarvon avulla. Jos metodi ei muuten palauttaisi mitään arvoa, se voidaan muuttaa boolean-tyyppiseksi. Paluuarvo true merkitsee metodin onnistunutta suoritusta, paluuarvo false suorituksen epäonnistumista.
(Javassa on myös mahdollista käyttää poikkeuksia virhetilanteiden käsittelyyn. Niistä tarkemmin myöhemmin.)
Esimerkiksi LimsaAutomaatti-luokan ostaLimsa uudelleen:
public boolean ostaLimsa(double annettuRaha){
final double HINTA = 5.0;
if ( limsojenLkm >= 1 &&
annettuRaha >= HINTA) {
rahat = rahat + annettuRaha;
limsojenLkm--;
return true;
}
else
return false;
}
Pääohjelmassa paluuarvon avulla voidaan tutkia, onnistuiko limsan osto:
public class Limsatesti3 {
public static void main(String[] args) {
LimsaAutomaatti raksanAutom;
raksanAutom = new LimsaAutomaatti();
raksanAutom.lisaaLimsoja(5);
if (raksanAutom.ostaLimsa(6.0))
System.out.println("Limsa tuli!");
else
System.out.println("Osto ei onnistu");
...
}
}
Miten meneteltäisiin, jos haluttaisiin kertoa, johtuiko oston epäonnistuminen limsapullojen loppumisesta vai liian vähistä rahoista?
Alkeistyyppi char
Yksittäisten merkkien tallentamiseen voidaan käyttää char-tyyppistä muuttujaa.
Javassa char-tyypin arvo on kahden tavun kokoinen ja käyttää ns. Unicode-koodausta. Tämä tarkoittaa sitä, että hyvin monien kielien (esimerkiksi kreikka, venäjä, heprea, arabia, bengali, tamili) kirjain- ja numeromerkit voidaan esittää Javan char-tyypin avulla.
Kuten int, double ja boolean, myös char on alkeistyyppi: arvoja käsitellään sellaisenaan eli ne eivät ole olioita, joita käsitellään viitteen kautta.
Merkkivakiot esitetään yksinkertaisissa lainausmerkeissä: 'A', 'k', ' ', '-', '5'
Huomaa ero:
'k' on yksittäinen merkki
"k" on merkkijono, joka sisältää yhden merkin
Näitä kahta käsitellään ohjelmissa täysin eri tavoin
Merkkiarvon voi sijoittaa int-tyyppiselle muuttujalle, mutta ei päinvastoin:
int i = 7;
char c = ' ';
i = c; // SALLITTU, i saa arvokseen
// merkkikoodin
c = i; // KIELLETTY!
Jos haluaa jostain syystä sijoittaa int-arvon char-muuttujan arvoksi, on suoritettava tyypinmuunnos:
int i = 45;
char c;
c = (char) i;
System.out.println(c);
tulostaa merkkikoodia 45 vastaavan merkin -
Arto Wiklan Lue-luokan metodi merkki() lukee yhden rivin ja palauttaa arvonaan rivin ensimmäisen merkin
Sen avulla voidaan esimerkiksi ohjelmoida silmukka, joka toistaa toimintaa niin kauan kuin käyttäjä haluaa.
public class MerkinLukuTesti {
public static void main(String[] args) {
char vastaus;
do {
System.out.println("Tehdään juttu kerran.");
// ... tehdään jotain
System.out.println("Haluatko jatkaa (k/e)?");
vastaus = Lue.merkki();
} while (vastaus != 'e');
}
}
Luokka String
Olemme jo usein käyttäneet String-tyyppisiä muuttujia. String on yksi Javan mukana tulevista valmiista luokista, ja merkkijonot ovat Javassa olioita.
String mjono;
määrittelee muuttujan mjono, jonka arvoksi voidaan sijoittaa viite String-tyyppiseen olioon.
Sijoitus
mjono = "kissa";
luo yhden uuden String-tyyppisen olion, joka sisältää merkkijonon kissa, ja asettaa muuttujan mjono arvoksi viitteen tähän uuteen merkkijonoon.
Huomaa: String-olion sisältämää merkkijonoa ei voi muuttaa sen jälkeen, kun olio on luotu. (Sen sijaan voidaan luoda uusia String-olioita, joiden merkkijono on joku muunnos nykyisen String-olion merkkijonosta.)
Kun suoritetaan lauseet
String jono = "kissa";
jono = jono + " kävelee";
luodaan kolme eri String-oliota.
String-luokan metodeita
String-luokassa on parikymmentä erinimistä metodia merkkijonojen käsittelyyn, monet metodeista on lisäksi kuormitettuja. Alla esitetään osa metodeista.
public int length() on metodi, jonka arvona on merkkijonon pituus.
String jono;
int pituus;
jono = "Kissa";
pituus = jono.length();
Seuraava ohjelma pyytää käyttäjältä merkkijonon ja tulostaa sen pituuden.
public class JononPituus {
public static void main(String[] args) {
String mjono;
int pituus;
System.out.println("Anna merkkijono!");
mjono = Lue.rivi();
pituus = mjono.length();
System.out.println("Sen pituus on " + pituus);
}
}
public boolean equals(Object anObject) palauttaa arvon true, jos nykyisen String-olion sisältö on sama kuin parametrina annettu toinen String-olio.
Seuraava ohjelma pyytää käyttäjältä kaksi riviä ja tutkii, ovatko ne samat:
public class JonotSamat {
public static void main(String[] args) {
String ekaRivi, tokaRivi;
System.out.println("Anna 1. rivi");
ekaRivi = Lue.rivi();
System.out.println("Anna 2. rivi");
tokaRivi = Lue.rivi();
if (ekaRivi.equals(tokaRivi))
System.out.println("Ovat samat");
else
System.out.println("Ovat eri rivit");
}
}
public boolean equalsIgnoreCase(String jono2)
toimii kuin equals-metodi, mutta katsoo isot ja pienet kirjaimet samoiksi kirjaimiksi.
Huomaa, että operaattorilla == ei voi verrata merkkijonojen sisältöjä. Se tutkii vain, onko String-oliot talletettu samaan muistipaikkaan.
public int compareTo(String jono2) vertaa nykyistä merkkijonoa parametrina merkkijonoon. Metodin arvo on
< 0, jos nykyinen String-olio edeltää aakkosjärjestyksessä parametrina annettavaa String-oliota
0, jos olioiden sisältämät merkkijonot ovat samat
> 0, jos nykyinen String-olio on aakkosjärjestyksessä parametrina annetun jonon jälkeen
Esimerkkiohjelma lukee kaksi riviä ja kertoo, kumpi on aakkosjärjestyksessä ensin.
public class JonoVertailu {
public static void main(String[] args) {
String ekaRivi, tokaRivi;
System.out.println("Anna 1. rivi");
ekaRivi = Lue.rivi();
System.out.println("Anna 2. rivi");
tokaRivi = Lue.rivi();
if (ekaRivi.equalsIgnoreCase(tokaRivi))
System.out.println("Ovat samat");
else
System.out.println("Ovat eri rivit");
}
}
Aakkosjärjetystä ei vertailla oikein kaikkien skandinaavisten merkkien osalta.
public char charAt(int index) palauttaa arvonaan merkkijonossa paikassa index olevan merkin. Ensimmäisen merkin indeksi on 0. Parametrin pitää olla merkkijonon alueella.
Esimerkkiohjelma pyytää käyttäjältä merkkijonon ja tulostaa sen kirjaimet jokaisen omalla rivillään:
public class KirjaimetAllekkain {
public static void main(String[] args) {
String rivi;
int i;
System.out.println("Anna rivi");
rivi = Lue.rivi();
for (i = 0; i < rivi.length(); i++)
System.out.println(rivi.charAt(i));
}
}
public int indexOf(char merkki) palauttaa arvonaan tiedon, monentenako merkkijonossa on ensimmäinen parametrina annettu merkki. (Merkkien numerointi alkaa 0:sta.) Jos merkkiä ei löydy, metodi palauttaa arvon -1.
String jono = "kissa";
System.out.println(jono.indexOf('s'));
System.out.println(jono.indexOf('t'));
public int indexOf(char merkki, int lahtoInd) toimii kuin edellinen, mutta aloittaa etsinnan paikasta lahtoInd
public int indexOf(String jono) ja
public int indexOf(String jono, int lahtoInd) ovat kuin edelliset metodit, mutta hakevat merkin sijasta merkkijonoa jono.
public String substring(int lahtoInd,
int loppuInd) luo ja palauttaa viitteen uuteen merkkijonoon, joka on nykyisen merkkijono osamerkkijono paikasta lahtoInd paikkaan loppuInd-1.
Public String toLowerCase() ja
public String toUpperCase() luovat ja palauttavat viitteen uuteen merkkijono-olioon, jossa nykyisen merkkijonon isot (pienet) kirjaimet on muutettu pieniksi (isoiksi).
String jono = "Kissa";
System.out.println(jono.toUpperCase());
System.out.println("KIRJA".toLowerCase());
Olion, johon operaatio kohdistetaan, ei siis tarvitse olla muuttujan arvona.
Public String trim() luo ja palauttaa viitteen olioon, jonka alusta ja lopusta on poistettu välilyönnit.
String jono1, jono2;
jono1 = " Olipa kerran... ";
jono2 = jono1.trim();
System.out.println(">" + jono2 + "<");
Taulukot
Tarkastellaan seuraavaa ongelmaa: Tee ohjelma, joka pyytää käyttäjältä 30 opiskelijan koetulokset (pisteet). Kun käyttäjä on syöttänyt kaikki tulokset, ohjelma tulostaa syötetyt tulokset ja niiden keskiarvon.
Koska syötetyt luvut pitää lopuksi tulostaa, pitää niistä jokainen tallentaa johonkin muuttujaan. Jos jokainen tulos tallennetaan omaan muuttujaansa, pitää ohjelmaan kirjoittaa arvon lukeminen ja tallentaminen 30 kertaan.
Ratkaisu: käytetään taulukkoa. Taulukko on joukko yhteenkuuluvia alkioita, joilla on kaikilla sama tyyppi. Yksittäiseen taulukon alkioon päästään käsiksi indeksin avulla: taulukon taulu viidenteen alkioon päästään käsiksi merkinnällä taulu[4]. Alkioiden numerointi alkaa 0:sta.
Indeksin ei tarvitse olla kokonaislukuvakio, vaan se voi olla mikä tahansa lauseke, jonka arvo on kokonaisluku.
Javassa taulukot ovat myös olioita. Taulukkomuuttuja, jonka avulla voidaan viitata int-tyyppisiä alkioita sisältävään taulukkoon, määritellään seuraavasti:
int[] pisteet;
Tämä ei vielä luo itse taulukkoa, vaan se on luotava erikseen:
pisteet = new int[30];
luo taulukon, joka voi sisältää 30 kokonaislukua.
Koepisteet kysyvä ja tulostava ohjelma:
public class KirjaimetAllekkain {
public static void main(String[] args) {
String rivi;
int i;
System.out.println("Anna rivi");
rivi = Lue.rivi();
for (i = 0; i < rivi.length(); i++)
System.out.println(rivi.charAt(i));
}
}
Huomautuksia taulukosta
Taulukon indeksit ovat aina int-tyyppisiä. Sen sijaan taulukon alkioiden tyyppi voi olla mikä vaan (valitaan taulukkomuuttujan määrittelyssä).
Taulukon ensimmäinen indeksi on aina 0 ja viimeinen koko - 1, missä koko on taulukko-olion luomisessa annettu koko.
Taulukon käyttäjä on vastuussa siitä, että indeksinä ei käytetä koskaan arvoa, joka ei ole oikealla välillä.
Taulukon alkioilla on oletusalkuarvot.
Taulukko-olion kokoa ei voi koskaan muuttaa, mutta taulukkomuuttuja voi saada uudeksi arvokseen viitteen erikokoiseen taulukkoon.
Taulukon koon saa selville ilmauksella .length Esimerkiksi taulukon pisteet koon saa selville lausekkeella pisteet.length
Taulukkomuuttujan voi alustaa myös seuraavasti, mutta vain muuttujan määrittelyn yhteydessä:
int[] luvut = {5, -23, 12, 31, 8};
luo viisialkoisen taulukon ja antaa sen alkioille luetellut alkuarvot
boolean[] totta = {true, true, false};
luo kolmialkioisen taulukon ja antaa alkioille luetellut alkuarvot.
Esimerkki: alkulukujen etsiminen
Halutaan tulostaa kaikki alkuluvut annettuun ylärajaan asti, esimerkiksi väliltä 1 - 1000.
Alkuluku on kokonaisluku, joka on jaollinen vain ykkösellä ja itsellään.
Käytetään menetelmänä Eratostheneen seulaa:
Asetetaan aluksi kaikki luvut jonoon kahdesta eteenpäin haluttuun ylärajaan asti.
Poistetaan niistä kaikki kahdella jaolliset luvut, paitsi kakkonen. Se on ensimmäinen alkuluku.
Haetaan jonosta viimeksi löydettyä alkulukua seuraava luku, jota ei ole poistettu. Se on seuraava alkuluku.
Poistetaan jonosta kaikki tällä jaolliset luvut.
Jatketaan kohdasta 3, kunnes ollaan ohitettu neliöjuuri alkuperäisestä ylärajasta.
Kaikki jonossa jäljellä olevat luvut ovat alkulukuja.
Toteutus:
Käytetään suurta boolean[]-tyyppistä taulukkoa, jossa jokainen alkio kuvaa yhtä lukua.
Alussa kaikkien alkioiden arvo on true.
Kun tutkinta edistyy ja todetaan, ettei jokin luku ole alkuluku, asetetaan alkion arvoksi false.
Lopussa kaikki ne alkiot, joiden arvona on true, ovat alkulukuja.
Tarvitaan kaksi laskuria: toinen (luku) viittaa tällä hetkellä tiedossa olevaan suurimpaan varmaan alkulukuun, ja toinen (kerrannainen) siihen monikertaan, jota ollaan poistamassa.
public class Alkuluku {
public static void main(String[] args) {
final int MAX = 1001; // yhtä suurempi kuin haluttu yläraja
int ylaraja, luku, kerrannainen, i;
boolean[] ehdokkaat;
ylaraja = (int) Math.ceil(Math.sqrt(MAX));
ehdokkaat = new boolean[MAX];
for (i = 2; i < MAX; i++)
ehdokkaat[i] = true;
luku = 2;
while (luku <= ylaraja) {
kerrannainen = 2 * luku;
while (kerrannainen < MAX) { // poista luvun kerrannaiset
ehdokkaat[kerrannainen] = false;
kerrannainen += luku;
}
do //etsi seuraava alkuluku;
luku = luku + 1;
while (!ehdokkaat[luku] && luku <= ylaraja);
}
for (i = 2; i < MAX; i++) // tulostetaan alkuluvut;
if (ehdokkaat[i])
System.out.println(i);
}
}
Etsiminen taulukosta: peräkkäishaku
Yksi tyypillinen ohjelmointitehtävä on annetun arvon etsiminen taulukosta. Seuraavassa tarkastellaan annetun luvun etsimistä kokonaislukutaulukosta.
Jos luvut ovat taulukossa satunnaisessa järjestyksessä, käytetään peräkkäishakua: Käydään taulukkoa läpi järjestyksessä alkio kerrallaan. Jokaista taulukon alkiota verrataan etsittävään lukuun. Hakua jatketaan, kunnes haluttu luku löytyi tai päästiin taulukon loppuun.
Kirjoitetaan metodi hakua varten. Metodi saa parametrinaan taulukon ja etsittävän luvun.
/* Peräkkäishaku */
public class Hae {
private static int hae(int[] taulu, int haettava) {
int i = 0, palautusindeksi = -1, koko;
boolean loytyi = false;
koko = taulu.length;
while (!loytyi && i < koko) {
if (taulu[i] == haettava) {
palautusindeksi = i;
loytyi = true;
}
i++;
}
return palautusindeksi;
}
public static void main(String[] args) {
int[] t1 = {40, 20, 50, 10, 30};
System.out.println(hae(t1,10));
System.out.println(hae(t1,30));
System.out.println(hae(t1,35));
}
}
Binäärihaku
Jos taulukko on järjetyksessä, binäärihaku on selvästi tehokkaampi.
Binäärihaun idea:
verrataan etsittävää lukua taulukon keskimmäiseen alkioon.
jos etsittävä luku on suurempi, jatketaan etsimistä taulukon loppuosasta samalla menetelmällä.
jos etsittävä luku on pienempi, jatketaan etsimistä taulukon alkuosasta samalla menetelmällä.
jos etsittävä luku on yhtäsuuri, haettu luku on löytynyt ja palautetaan verrattavan alkion indeksi.
tätä jatketaan niin kauan, että etsittävä alkio löydetään tai hakualue on tyhjä.
Binäärihaku on huomattavasti tehokkaampi kuin peräkkäishaku, koska jokaisella kierroksella hakualue pienenee puoleen alkuperäisestä. Jos taulukossa on N alkiota, tarvitaan pahimmillaankin vain noin log N taulukon alkion ja haettavan alkion vertailua.
/* Binäärihaku */
public class BinHae {
private static int binHae(int[] taulu, int haettava) {
int vasen, oikea, keski;
int palautusindeksi = -1;
vasen = 0;
oikea = taulu.length - 1;
do {
keski = (vasen + oikea) / 2;
if (haettava < taulu[keski])
oikea = keski - 1;
else
vasen = keski + 1;
} while (haettava != taulu[keski] &&
vasen <= oikea);
if (taulu[keski] == haettava)
palautusindeksi = keski;
return palautusindeksi;
}
public static void main(String[] args) {
int[] t1 = {10, 20, 30, 40, 50};
System.out.println(binHae(t1,10));
System.out.println(binHae(t1,30));
System.out.println(binHae(t1,35));
}
}
Taulukon järjestäminen
Toinen yleinen ohjelmointiongelma on taulukon järjestäminen. Esimerkiksi: taulukon alkiot ovat opiskelijoiden nimiä. Järjestä nimet aakkosjärjestykseen niin, että aakkosjärjestyksessä ensimmäisenä tuleva nimi on taulukon alussa.
Järjestämiseen on olemassa useita eri algoritmeja, joiden tehokkuus vaihtelee. Tässä esitellään kaksi, vaihto-järjestäminen ja lisäysjärjestäminen. Myöhemmin rekursion yhteydessä esitellään pikajärjestäminen (quicksort). Esimerkkiohjelmat järjestävät kokonaislukutaulukoita.
Vaihtojärjestämisen perusidea:
Vertaillaan taulukon ensimmäistä alkiota vuorotellen kaikkiin muihin alkioihin. Aina, kun ensimmäinen alkio on vertailtavaa alkiota suurempi, vaihdetaan näiden kahden paikkaa.
Kun koko taulukko on käyty läpi, ensimmäisenä alkiona on taulukon pienin alkio. Sitä ei tarvitse tämän jälkeen käsitellä.
Otetaan taulukon toinen alkio ja verrataan sitä kaikkiin taulukon loppupään alkioihin. Vaihdetaan aina tarvittaessa vertailtavien alkoiden paikkaa.
Jatketaan samaa taulukon kolmannelle alkiolle jne.
Kun taulukon toiseksi viimeinen alkio on käsitelty, taulukko on järjestetty.
/* Yksinkertainen vaihtojärjestäminen */
public class Vaihtojarj {
private static void vaihtojarj(int[] taulu) {
int i, j, apu, koko;
koko = taulu.length;
for (i = 0; i < koko-1; i++)
for (j = i+1; j < koko; j++)
if (taulu[i] > taulu[j]) {
apu = taulu[i];
taulu[i] = taulu[j];
taulu[j] = apu;
}
}
public static void main(String[] args) {
int[] taulukko = {40, 20, 50, 10, 30, 60};
int i;
for (i = 0; i < taulukko.length; i++)
System.out.print(taulukko[i] + " ");
System.out.println();
vaihtojarj(taulukko);
for (i = 0; i < taulukko.length; i++)
System.out.print(taulukko[i] + " ");
System.out.println();
}
}
Koska taulukko on olio, taulukkoparametriin tehdyt muutokset näkyvät kutsuvassa ohjelmassa.