Opiskelumateriaali - Abstraktit tietotyypit ja olio-ohjelmointi

Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
Abstraktit tietotyypit ja olio-ohjelmointi
Edellisessä osassa käsiteltiin aliohjelmia prosessiabstraktion välineenä.
Prosessiabstraktio onkin vanhimpia ohjelmointikielten suunnittelukäsitteitä ja
sisältynyt ohjelmointikieliin niiden alkutaipaleelta asti. Vähääkään edistyneempi
ohjelmointi ei ole mahdollista ilman prosessiabstraktiota, koska vasta algoritmien ja
operaatioiden abstrahointi tekee mahdolliseksi hallita suuria ohjelmakokonaisuuksia.
Data-abstraktio seurasi luonnollisella tavalla prosessiabstraktiota, kun havaittiin, että
on tarpeellista yhdistää tietotyyppeihin määrättyjä operaatioita, joiden yksityiskohtia ei
tietotyypin käyttäjän kuitenkaan tarvitse tuntea. Data-abstraktio toteutetaan
abstrakteja tietotyyppejä käyttämällä; nämä tietotyypit puolestaan johtivat olioohjelmoinnin syntyyn. Tässä osassa käsitellään sekä abstrakteja tietotyyppejä että olioohjelmointia.
1. Abstraktit tietotyypit
Abstrakti tietotyyppi (abstract data type, ADT) on tietotyyppi, joka toteuttaa
seuraavat ehdot ([Seb], kappale 11.2.2):
1. Tyypin määrittelyn ja operaatioiden yksityiskohdat eivät näy tietotyypin
ulkopuoliselle käyttäjälle.
2. Tyypin esittely samoin kuin tietotyyppiin liittyvät operaatioiden esittelyt
sijaitsevat yhdessä syntaktisessa yksikössä. Muut ohjelmayksiköt voivat
muodostaa tämän tyypin muuttujia.
Ensimmäistä ehtoa sanotaan tiedon kätkennäksi (information hiding) tiedon ja toista
kapseloinniksi (encapsulation). Kätkentään katsotaan kuuluvaksi myös se tietotyypin
ominaisuus, että tyyppiin voidaan kohdistaa suoraan ainoastaan sellaisia operaatioita,
jotka tyypin esittely sallii. Kapseloinnilla erotetaan tyypin rajapinta ja toteutuksen
yksityiskohdat toisistaan: tyypin sisäiseen esitykseen ja operaatioihin voidaan tehdä
muutoksia muiden tyypin käyttäjien kärsimättä, kunhan tyypin rajapinta ei muutu.
Kapselointi helpottaa suuren ohjelman loogista ja teknistä hallintaa. Periaatteena on
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
koota data ja sitä käsittelevät operaatiot yhteen. Kun kootaan loogisesti
yhteenkuuluvat operaatiot samaan yksikköön, myös niiden mentaalinen hallinta
helpottuu. Teknisesti kapselointi helpottaa ohjelman kääntämistä, koska yhdestä
syntaktisesta yksiköstä voidaan muodostaa erikseen käännettävä käännösyksikkö
(compilation unit). Näin ollen voidaan tehdä muutoksia jonkin tietotyypin koodiin
joutumatta kääntämään koko ohjelmaa erikseen. Samaan kokonaisuuteen liittyvien
abstraktien tietotyyppien ja aliohjelmien sanotaan muodostavan moduulin, joka
voidaan siis kääntää itsenäisesti ja esimerkiksi sijoittaa kirjastoon uudelleen
käytettäväksi ohjelmayksiköksi. Sellaisissa ALGOL- pohjaisissa kielissä (esimerkiksi
Pascal), joissa noudatetaan alisteista sisäkkäisiin ohjelman osiin perustuvaa
ohjelmarakennetta, kapseloinnin toteuttaminen on ongelmallista.
Tiedon kätkentä on erittäin tärkeä periaate tietotyypin luotettavuuden kannalta, sillä
ainoastaan tietotyypin sisäiset operaatiot voivat väliaikaisesti rikkoa olion tilan, mutta
palauttavat sen jälleen kuntoon ennen kontrollin siirtymistä tietotyypin käyttäjälle.
Lisäksi koodin ylläpito helpottuu, mikäli tietotyypin joihinkin operaatioihin on tehtävä
muutoksia, koska ulospäin näkyvä rajapinta säilyy silti samana. Huomaa, että myös
kielen sisäiset tietotyypit voivat tämän määritelmän mukaan olla abstrakteja. Tässä
käsitteellä tarkoitetaan kuitenkin käyttäjän määrittelemää abstraktia
tietotyyppiä. Kirjan [Har] luvussa 9 käsitellään myös kapselointimekanismeja ja
abstrakteja tietotyyppejä.
1.1. Abstraktit tietotyypit eri ohjelmointikielissä
Abstraktit tietotyypit sisällytettiin ensimmäiseksi SIMULA 67 - kieleen ([Seb], kappale
11.4). Abstrakti tietotyyppi määriteltiin luokkana ja se sisälsi luokan muuttujat,
aliohjelmien esittelyt sekä niiden koodin. Luokkien ilmentymät eli oliot luotiin
pinodynaamisesti ja niihin voitiin viitata ainoastaan osoitinmuuttujan välityksellä.
Luokassa määriteltyjä muuttujia ei piilotettu ilmentymän luovalta sovellukselta, joten
tiedon kätkennän toteutus oli puutteellinen. Abstraktien tietotyyppien käyttö yleistyi
ohjelmointikielissä vasta useita vuosia SIMULA 67-kielen julkaisun jälkeen. Useimmissa
nykyisissä korkean tason ohjelmointikielissä voidaan käyttää abstrakteja tietotyyppejä;
erityisesti tämä koskee olio-ohjelmointikieliä.
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
Adassa abstrakteja tietotyyppejä voidaan simuloida käyttämällä pakkauksia
(packages), joiden avulla tietoja voidaan kapseloida. Pakkaukset koostuvat kahdesta
osasta, joita molempia myös kutsutaan pakkauksiksi. Nämä osat ovat määrittelyosa
(specification package) ja runkoon (body packege), jotka molemmat esitellään
samannimisinä pakkauksina, mutta runko-osassa lisätään määre body pakkauksen
nimen eteen. Pakkausta käyttävä sovellus näkee aina vain määrittelyosan, mutta ei
runkoa. Pakkauksella ei välttämättä olekaan runko-osaa lainkaan.
Tiedon kätkentä voidaan Adassa toteuttaa jakamalla määrittelyosa julkiseen ja
yksityiseen osaan. Julkinen osa on kokonaisuudessaan näkyvissä pakkauksen käyttäjille,
mutta yksityinen, private -määreellä merkitty osa on käyttäjältä piilotettu. Jollei
määrettä ole annettu, tieto on julkista. Esimerkiksi linkitetyn listan määrittelevä
tietotyyppi
package LINKITETTY_LISTA is
type SOLMU;
type OSOITIN is access SOLMU;
type SOLMU is
record
DATA: INTEGER;
LINKKI: OSOITIN;
end record;
end;
package body LINKITETTY_LISTA is
-- Paketin runko
end;
sallisi pääsyn myös SOLMU -tyypin muuttujiin DATA ja LINKKI. Mikäli SOLMU
haluttaisiin näkyviin paketin ulkopuolelle, mutta piilottaa sen yksityiskohdat, voitaisiin
pakkaus määritellä seuraavasti:
package LINKITETTY_LISTA is
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
type SOLMU is private;
-- Listan käsittelymetodit
private
type SOLMU;
type OSOITIN is access SOLMU;
type SOLMU is
record
DATA: INTEGER;
LINKKI: OSOITIN;
end record;
end LINKITETTY_LISTA;
package body LINKITETTY_LISTA is
-- Paketin runko
end LINKITETTY_LISTA;
Nyt SOLMU -tyypin esitys on piilotettu, mutta tyyppi näkyy kuitenkin pakkauksen
ulkopuolelle. Pakkaus otetaan käyttöön ohjelmassa käyttämällä with ja use -lauseita:
with LINKITETTY_LISTA; use LINKITETTY_LISTA;
procedure LISTA_OHJELMA is
OMA_SOLMU: SOLMU;
begin
-- listan käsittelyä
end LISTA_OHJELMA;
Tässä with -lause sisällyttää ulkoisen pakkauksen ohjelmaan, ja use -lauseella
poistetaan tarve viitata pakkaukseen muuttujiin ja operaatioihin pakkauksen nimellä,
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
ts. voidaan käyttää nimeä SOLMU nimen LINKITETTY_LISTA.SOLMU asemasta. Reino
Kurki-Suonion kirjan [Kur] luvussa 6 on hieman tarkemmin käsitelty Adan pakkauksia.
Pascalissa ja C-kielessä on mahdollista määritellä omia tyyppejä, mutta niihin ei voi
kytkeä operaatioita, joten abstraktien tietotyyppien toteuttaminen näissä kielissä on
mahdotonta. Sen sijaan C++ -kielessä samoin kuin Javassa voidaan luokkien avulla
määritellä abstrakteja tietotyyppejä. Seuraavassa perehdytään hieman C++:n ja Javan
luokkien käyttöön nimenomaan data-abstraktion toteuttajana. Varsinaiseen olioohjelmointiin palataan tuonnempana.
1.2. Abstraktit tietotyypit C++ -kielessä
C++ rakennettiin liittämällä C-kieleen oliotuki; data-abstraktio toteutetaan luokkien
avulla (ks. [Strou], luku 10). Erona Adan abstraktiomalliin on, että C++ -kielen luokat
ovat tietotyyppejä toisin kuin Adan pakkaukset, jotka sisältävät tietotyyppien
määrittelyjä. C++:ssa voidaan siis määritellä luokkatyyppisiä muuttujia; luokkien
julkisiin jäsenmuuttujiin voidaan viitata ainoastaan luokan instanssin kautta, kun taas
Adassa sisällytetyn pakkauksen muuttujiin voidaan viitata suoraan (kunhan käytetään
use-lausetta).
C++:n luokka on C-kielen tietueen eli struct-tyypin laajennus. C-kielen structia voidaan
pitää luokan erikoistapauksena, jossa ei ole lainkaan operaatioita ja kaikki
jäsenmuuttujat ovat julkisia. Luokkien operaatioita kutsutaan jäsenfunktioiksi. C++ kielen luokkamalli muistuttaa SIMULA 67:n mallia, jossa luokka oli myös tietotyyppi.
Luokan jäsenmuuttujat (member variables)ja -funktiot (member functions) voivat olla
julkisia (public), suojattuja (protected) tai yksityisiä (private). Julkinen jäsenmuuttuja
tai -funktio on näkyvissä ulkopuolelle, ts. luokan instanssin (olion) haltija voi suoraan
viitata olion jäsenmuuttujaan tai kutsua jäsenfunktiota. Yksityiset jäsenmuuttujat ja funktiot näkyvät ainoastaan luokan sisällä ja luokan ystäväluokissa ja -funktioissa.
Ystäväfunktio on luokan ulkopuolella määritelty funktio, jolla on pääsy luokan
yksityiseen alueeseen. Tällaisia funktioita tarvitaan määriteltäessä luokkien välistä
yhteistä rajapintaa tilanteessa, jossa olisi määriteltävä sama metodi useampiin eri
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
luokkiin. Ystäväfunktio on siis luokan ulkopuolinen funktio, jota ei kutsuta luokan
jäsenfunktiona. Kokonainen luokkakin voidaan määritellä toisen luokan ystäväksi:
tällöin ystäväluokan jäsenfunktiot voivat käsitellä luokan yksityisiä osia kuten omiaan.
Ystävyys ei ole automaattisesti molemminpuolista. Jos halutaan antaa luokalle pääsy
sen ystäväluokan yksityisiin osiin, on luokka määriteltävä ystäväksi toiselle luokalle.
Sisäkkäisten luokkien tapauksessa on useimmiten syytä määritellä ulompi luokka
sisemmän luokan ystäväksi, oletusarvoisesti ulommalla luokalla ei ole pääsyä sisemmän
luokan yksityisiin osiin. Suojattujen osien näkyminen liittyy periytymiseen; tähän
palataan olio-ohjelmointia koskevassa osassa.
Jos jäsenmuuttujat määritellään staattisiksi, ne luodaan jo käännösvaiheessa ja niiden
elinaika päättyy vasta ohjelman loputtua. Luokan staattisten jäsenmuuttujien
näkyvyyttä voidaan säädellä samoilla määreillä kuin ei-staattisten muuttujienkin.
Luokan ulkopuolella niihin voidaan viitata (silloin, kun se näkyvyyden kannalta on
sallittua) luokan nimen ja näkyvyysoperaattorin (::) avulla. Myös funktiot voidaan
määritellä staattisiksi, jolloin ne voivat käsitellä vain staattisia tietotyyppejä. Tällaisia
funktioita voidaan kutsua, vaikka luokasta ei olisikaan luotu instanssia. Staattisten
funktioiden näkyvyys määräytyy kuten muidenkin jäsenfunktioiden. Luokan staattiset
tietotyypit tulee alustaa luokan ulkopuolella, jotta niitä voidaan käyttää luokan
funktioissa.
C++ -kielen funktioiden joukossa on kaksi erikoisasemassa olevaa funktiotyyppiä:
muodostimet (konstruktorit, constructors) ja hajottimet (destruktorit, destructors).
Muodostinta kutsutaan olion luomisen ja hajotinta olion tuhoamisen yhteydessä.
Muodostimen nimi on aina sama kuin luokan nimi eikä sillä ole paluuarvoa;
muodostinfunktioita voi olla useita (ts. niitä voidaan ylikuormittaa) jolloin olion
luomisen yhteydessä parametrilistan perusteella päätellään, mitä muodostinta
kutsutaan. Hajotin on sen sijaan yksikäsitteinen ja sen muoto on
~LuokkaNimi()
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
Hajotinta kutsutaan automaattisesti, kun olio tuhotaan. Yleensä hajottimeen on syytä
kirjoittaa vapautusoperaatiot dynaamisesti varatulle muistille jne. Seuraavassa
esimerkissä on toteutettu pinorakenne kokonaisluvuille:
class Pino {
private:
// Jäsenmuuttujat
int *pino_osoitin;
int koko;
int huippu;
public:
// Muodostin
Pino (int pinonkoko) {
pino_osoitin = new int [pinonkoko];
koko = pinonkoko;
huippu = -1;
}
// Hajotin
~Pino () {
delete [] pino_osoitin;
}
// Muut metodit
void push(int luku) { /* funktion runko …*/ … }
int pop( ) { /* funktion runko …*/ }
int top( ) { /* funktion runko …*/ }
int empty( ) { /* funktion runko …*/ }
};
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
Tässä versiossa jäsenfunktioiden koodit on kirjoitettu esittelyn yhteyteen. Toinen tapa
kirjoittaa luokan määrittelyyn ainoastaan funktioiden otsikot ja kirjoittaa funktioiden
rungot erikseen seuraavasti:
// Tiedostossa Pino.h
class Pino {
private:
int *pino_osoitin;
int koko;
int huippu;
public:
Pino (int pinonkoko);
~Pino ();
void push(int luku);
int pop( );
int top( );
int empty( );
};
// Tiedostossa Pino.cpp
#include "Pino.h"
Pino::Pino (int pinonkoko) {
pino_osoitin = new int [pinonkoko];
koko = pinonkoko;
huippu = -1;
}
Pino::~Pino () {
delete [] pino_osoitin;
}
void Pino::push(int luku) { /*funktion runko …*/}
int Pino::pop( ) { /* funktion runko …*/ }
int Pino::top( ) { /* funktion runko …*/ }
int Pino::empty( ) { /* funktion runko …*/ }
Usein koodi kirjoitetaan niin, että hyvin lyhyet funktiot kirjoitetaan kokonaan
määrittelyyn ja pitempien funktioiden toteutukset erilliseen kooditiedostoon.
Sekä muodostinta että hajotinta voidaan periaatteessa myös eksplisiittisesti kutsua
koodissa, mutta useimmiten siihen ei ole mitään syytä. C++ -ohjelmassa olioille voidaan
varata muisti kaikilla samoilla tavoilla kuin muuttujillekin. Oliot voivat siis olla staattisia
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
muuttujia (myös näkyvyydeltään globaaleja), pinodynaamisia tai kekodynaamisia,
esimerkiksi
// Globaali olio
Pino pino_glob;
int main() {
// Pinodynaaminen olio
Pino pino_stack;
pino_stack.push(12);
int luku=pino_stack.pop();
// Kekodynaaminen olio
Pino *pino_heap = new Pino();
pino_heap->push(12);
luku=pino_heap->pop();
…
delete pino_heap;
}
Kekomuistista varattavat oliot on ohjelmoijan itsensä vapautettava deleteoperaattorilla.
1.3. Abstraktit tietotyypit Javassa
Javan luokkamalli muistuttaa C++ -kielen mallia, mutta joitakin merkittäviä poikkeuksia
on. Javan oliot ovat aina kekodynaamisia ja ne luodaan new-operaattorilla, pinosta ei
koskaan varata olioita. Javassa ohjelmoija ei itse voi suoraan vapauttaa muistia, vaan
roskien keruu huolehtii tästä. Ohjelmoija voi kuitenkin vaikuttaa siihen esimerkiksi
asettamalla olioviitteen arvoon null, jolloin roskien kerääjä aikanaan vapauttaa varatun
muistin, ellei olioon ole muita viitteitä. Roskienkerääjän voi myös ohjelmallisesti
aktivoida (ks. [Arn], kappale 15.2). Javassa voidaan tehdä olion tuhoamisen yhteydessä
tarpeelliset operaatiot ylikirjoittamalla finalize() -metodi, jonka kaikki oliot perivät
Object-luokalta. Java-koodissa tätä tarvitsee vain harvoin kuitenkaan tehdä, koska
muistivuotoja ei tapahdu. (Javan luokkiin voi tutustua esimerkiksi teoksen [Arn] luvusta
2).
Javan olioihin viitataan niiden nimillä, jotka ovat viittaustyypin (referenssityypin)
muuttujia. Javassa ei käytetä osoitinoperaattoreita, mikä merkitsee, ettei Javan
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
primitiivisten tietotyyppien muistiosoitteita voida käsitellä, koska ne ovat
arvotietotyyppejä. Olioiden jäseniin viitataan pistenotaatiolla. Javassa ei ole luokan
ulkopuolista elämää: kaikkien metodien ja tietotyyppien on kuuluttava johonkin
luokkaan. Myös metodien määrittely toteutetaan samassa luokassa, jossa metodi
esitellään (paitsi abstraktien metodien tapauksessa; tähän palataan olio-ohjelmoinnin
yhteydessä). Javan luokissa voidaan C++-kielen tapaan määritellä sekä staattisia että eistaattisia jäsenmuuttujia ja -metodeja. Luokan staattiset jäsenet allokoidaan
käännösvaiheessa. Erotuksena C++-kieleen staattiset jäsenmuuttujat voidaan myös
alustaa luokassa, jolloin niitä voidaan suoraan käyttää luokan metodeissa.
Luokan ei-staattisia jäseniä käytetään luokan ilmentymän kautta. Jokaisella
ilmentymällä on omat kopionsa luokassa määritellyistä ei-staattisista jäsenmuuttujista.
Staattisista jäsenmuuttujista, luokkamuuttujista (class variable), on kustakin vain yksi
kopio, jota kaikki ilmentymät käyttävät. Luokan jäsenmetodit, luokkametodit (class
method) allokoidaan myös staattisesti. Myös luokkametodeja voidaan käyttää ilman
dynaamisesti luotua oliota luokan nimen ja pisteoperaattoria avulla.
Javan tiedonkätkentä perustasolla on hieman monimutkaisempi kuin C++ -kielessä.
Javassa voidaan käyttää samoja näkyvyysmääreitä (public, protected ja private) kuin
C++ -kielessäkin ja niiden merkityskin on sama. Java-luokassa voidaan kuitenkin jättää
näkyvyysmääre myös antamatta, jolloin käytetään oletusnäkyvyytenä
pakkausnäkyvyyttä (package scope), joka poikkeaa kolmen luetellun määreen
näkyvyydestä ([Arn], kappale 10.2). Javassa voidaan nimittäin luokkia koota
pakkauksiksi (ja juuri näistä liitetään ohjelmaan luokkia kaikille Java-ohjelmoijille tutulla
import-lauseella). Oletusnäkyvyyden jäsenet näkyvät pakkauksen sisällä tai, ellei
pakkausta ole määritelty, samassa hakemistossa. Tällä mekanismilla on korvattu C++ kielen ystäväluokat ja -funktiot, joita Javassa ei ole toteutettu. Mikäli luokan halutaan
näkyvän pakkauksen ulkopuolella, se on määriteltävä public-tyyppiseksi ja se on
sijoitettava tiedostoon, jonka nimi on sama kuin luokan nimi (lisättynä java tarkenteella).
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
2. Olio-ohjelmointi
1980 -luvun aikana havaittiin, että ohjelmistokehityksessä tuottavuutta voidaan
parhaiten lisätä ohjelmien uudelleenkäytöllä. Abstraktit tietotyypit ovat
ominaisuuksiensa (datan kapselointi ja tiedon kätkentä) ansiosta sopivia yksiköitä
kierrättämiseen. Yleensä olemassaolevat tietotyypit eivät kuitenkaan täysin sovi uuteen
käyttökohteeseensa, vaan niihin olisi tehtävä pieniä muutoksia ja lisäyksiä. Tämä voi
olla työläs ja hankala operaatio, eivätkä abstraktit tietotyypit sinänsä muodosta
välttämättä hierarkkista rakennetta, jota saatetaan tarvita ohjelman rakentamisessa.
Tämä ongelma voidaan ratkaista, mikäli abstraktit tietotyypit voivat periä aiemman
tietotyypin ominaisuudet ja datan. Näin saadaan aiempi koodi uudelleen käytettyä ja
sen ominaisuuksia voidaan muokata sekä lisätä uuteen tietotyyppiin uusia
ominaisuuksia. Tämä on olio-ohjelmoinnin perusta. Oliokielten abstrakteja
tietotyyppejä kutsutaan luokiksi ja luokkien instansseja olioiksi. Olio-ohjelmointi
keksittiin jo 1960 -luvulla, mutta yleistyi vasta 1980 -luvulla ja kohosi sittemmin
johtavaksi ohjelmointiparadigmaksi. Oliotuki on lisätty myöhemmin moniin sellaisiin
kieliin, joista se on alun perin puuttunut.
Olio-ohjelmoinnin opettelu vaatii prosessisuuntautuneen ohjelmoinnin koulukunnan
edustajalta muutosta ajattelutavassa. Prosessisuuntautuneessa ajattelumallissa
ohjelman aktiiviset operaatiot (sijoituslauseet ja aliohjelmat) käsittelevät passiivisia
tietoalkioita. Olio-ohjelmoinnissa tietoalkiot eli oliot ovat itse aktiivisia ja ohjelman
toiminta koostuu olioiden toisilleen lähettämistä viesteistä ja niihin saaduista
vastauksista. Viestit voivat muuttaa olion tilaa, millä tarkoitetaan olion jäsenmuuttujien
kulloisiakin arvoja. Olio-ohjelmoinnin ihanteisiin kuuluu, että toiset oliot eivät voi
yleensä muuttaa tai lukea suoraan olion tilaa, vaan siihen käytetään erillisiä saanti- ja
asetusmetodeja. Tässä esityksessä luokkien jäsenfunktioita kutsutaan metodeiksi;
terminologiassa saattaa esiintyä vaihtelua eri lähteissä, esimerkiksi Stroustrup ([Strou],
s. 310) tarkoittaa metodilla luokan virtuaalista jäsenfunktiota.
Tässä esitellään aluksi olio-ohjelmoinnin peruskäsitteitä, kuten periytyvyys,
monimuotoisuus ja dynaaminen sidonta, minkä jälkeen tarkastellaan, kuinka kyseiset
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
ominaisuudet on toteutettu C++- ja Java -kielissä. Näistä poikkeavia oliokieliä on
runsaasti, mutta niihin ei tässä yhteydessä perehdytä. Sebestan ([Seb]) luvussa 12 on
jonkin verran käsitelty myös muita oliokieliä. Myös Harsu esittelee kirjansa [Har]
luvussa 10 joitakin olio-ohjelmointikieliä.
2.1. Periytyvyys
Periytyvyys (inheritance) on olio-ohjelmoinnin keskeisin käsite, kuten jo aiemmin on
käynyt ilmi; periytyvyys yhdessä koostamisen kanssa kuvaa olio-ohjelmoinnin
abstraktien tietotyyppien eli luokkien väliset suhteet. Luokkaa, joka peritään, kutsutaan
usein kantaluokaksi (base class) tai yliluokaksi (superclass) ja perivää luokkaa sen
aliluokaksi (subclass) tai johdetuksi luokaksi (derived class). Joskus myös perittävää
luokkaa sanotaan vanhemmaksi ja perivää luokkaa sen lapseksi. Periytymisessä
kantaluokan olioiden käyttäytyminen siirtyy osaksi aliluokan olioiden käyttäytymistä.
Aliluokka voi sekä laajentaa (extend) että erikoistaa (specialize) kantaluokan
käyttäytymistä. Laajentamisella tarkoitetaan uusien jäsenmuuttujien ja metodien
liittämistä aliluokkaan. Erikoistaminen tarkoittaa, että aliluokassa korvataan perittyjä
määrittelyjä luokan omilla määrittelyillä (yleensä tämä tarkoittaa kantaluokan metodin
korvaamista aliluokan omalla versiolla).
Tyypin T alityypiksi kutsutaan sellaista tietotyyppiä S, johon voidaan soveltaa mitä
tahansa tyyppiin T sovellettavaa operaatiota. Tällöin tyyppiä T sanotaan tyypin S
ylityypiksi. Tärkeä kysymys oliokielessä on, ovatko aliluokat kantaluokan alityyppejä.
Mikäli aliluokalle sallitaan periytymisessä ainoastaan yllä mainitut laajentaminen ja
erikoistaminen, aliluokka on kantaluokan alityyppi ja siihen soveltuu ns.
Alityyppiperiaate: Alityypin olio voi ohjelmassa esiintyä missä tahansa sen ylityypin
odotetaan esiintyvän.
Tämä on yleensä voimassa olio-ohjelmointikielissä. Joissakin kielissä voidaan kuitenkin
myös rajoittaa periytyviä ominaisuuksia, jolloin aliluokka ei ole enää kantaluokan
alityyppi.
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
Näkyvyysmääreet säätelevät, mitkä kantaluokan ominaisuudet ovat aliluokan
käytettävissä. Usein julkisten jäsenten lisäksi vain suojatut jäsenet ovat näkyvissä
aliluokassa. Näkyvyysmääreitä on käsitelty yleisesti aiemmin; tuonnempana
tutustutaan näkyvyysmääreiden käyttöön C++ ja Java -kielissä.
Mikäli luokalla voi olla monta kantaluokkaa, sanotaan että ohjelmointikieli tukee
moniperiytymistä (multiple inheritance). Jos moniperiytymistä ei sallita,
luokkahierarkia voidaan esittää puurakenteena, moniperiytymisen tapauksessa
hierarkia muodostaa verkon. C++ tukee moniperiytymistä, Javassa peritään aina
ainoastaan yksi luokka.
Jos aliluokassa määritellään jäsenmuuttuja, jonka nimi on sama kuin periytymisen
yhteydessä saadulla muuttujalla, piilottaa määrittely yliluokan vastaavan muuttujan.
Aliluokan metodi, jolla on sama otsikko kuin kantaluokan metodilla, määrittelee
uudelleen (override) perityn metodin. Yliluokan piilotettuihin muuttujiin ja
uudelleenmääriteltyihin metodeihin voidaan yleensä viitata yliluokan nimen ja
tarkoitukseen määrätyn operaattorin avulla. Tyypillisiä uudelleenmääriteltäviä
metodeja ovat olion tulostamiseen liittyvät metodit.
2.2. Monimuotoisuus ja dynaaminen sidonta
Monimuotoisuus (polymorfismi, polymorphism) tarkoittaa saman operaattorin tai
funktion sitomista erilaisiin toteutuksiin tilanteesta riippuen. Olio-ohjelmoinnissa tämä
tarkoittaa yleensä monimuotoisuutta, joka saavutetaan viestin dynaamisella sidonnalla
metodin määrittelyyn. Tämä mekanismi yhdessä edellä mainitun alityyppiperiaatteen
kanssa sallii muodostaa monimuotoisia muuttujia, ts. muuttujia joiden tyyppi on
kantaluokka, mutta sen muuttujat voivat olla jonkin aliluokan olioita. Mikäli aliluokan
metodi määrittelee uudelleen kantaluokassa olevan metodin ja monimuotoisen
muuttujan metodia kutsutaan, kutsu sidotaan oikean luokan metodiin.
Dynaaminen sidonta on varsin hyödyllinen ominaisuus ohjelmiston ylläpidon kannalta.
Kun muodostetaan uusia luokkia, kantaluokkien koodiin ei tarvitse tehdä
perustoiminnoissa muutoksia. Esimerkiksi olion tulostaminen tapahtuu dynaamisen
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
sidonnan avulla aina oikein, vaikka perusluokka ei sisältäisikään tulostusmahdollisuutta
uudelle luokalle, koska ohjelmassa kutsutaan oikean luokan tulostusmetodia, vaikka
olioon viittaava muuttuja olisi kantaluokan tyyppinen. Dynaaminen sidonta tapahtuu
luonnollisesti ohjelman suorituksen aikana (muuten ei mahdollista tietää, minkä
tyyppiseen olioon muuttuja viittaa), joten se on tehottomampaa kuin staattinen,
käännösaikana tapahtuva sidonta. Tästä syystä joissakin oliokielissä kaikki sidonta ei
automaattisesti ole dynaamista, vaan ohjelmoija voi itse säätää, mitkä metodit
sidotaan dynaamisesti. Edelleen, dynaaminen sidonta asettaa haasteita kielen
tyypintarkistukselle, sillä monimuotoinen muuttuja voi osoittaa tietotyyppiin, joka on
tietotyypin määrittelemän tyypin alityyppi, joten on päätettävä, missä vaiheessa
dynaamisesti sidottavan metodin tyypintarkistus tehdään. Mikäli kieli on vahvasti
tyypitetty, tyypintarkistus olisi tehtävä staattisesti. Metodikutsujen yhteydessä
tarvitaan kahdentyyppistä tarkistusta: parametrien tyyppien vastaavuus
parametrilistaan ja paluuarvon tyypin vastaavuus odotettuun paluuarvotyyppiin.
Toinen mahdollisuus on luopua staattisesta tyypintarkistuksesta ja tarkistaa tyypit
dynaamisesti metodikutsun yhteydessä.
Yleensä oliohierarkian kantaluokka kannattaa suunnitella niin, että se sisältää
täsmälleen kaikkien aliluokkien tarvitsemat operaatiot. Joissakin tapauksissa tällainen
kantaluokka on niin yleistä muotoa, ettei siitä ole tarkoituksenmukaista muodostaa
olioita. Tällöin luokasta voidaan tehdä abstrakti luokka, josta ei voi luoda instansseja.
Abstrakti luokka voi sisältää abstrakteja metodeja, joille on määritelty ainoastaan
prototyyppi, mutta ei lainkaan runkoa. (Joissakin esityksissä abstrakteja metodeja
kutsutaan [puhtaasti] virtuaalisiksi metodeiksi.) Tyypillinen esimerkki tämän
kaltaisesta tapauksesta olisi ohjelma, jossa käsiteltäisiin yksinkertaisia tasokuvioita.
Kaikilla tasokuvioilla on pinta-ala ja kaikki tarvitsevat metodin, joka piirtää kuvion
näytölle. Muut ominaisuudet saattavat vaihdella kuvion tyypistä riippuen; tällöin voisi
olla tarkoituksenmukaista määritellä abstrakti kantaluokka, jossa on jäsenmuuttuja
kuvaamaan pinta-alaa ja metodi piirtämiselle. Kutakin tasokuviotyyppiä kohti
kirjoitettaisiin oma luokka, joka perii kantaluokan ja määrittelee uudelleen
piirtometodin.
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
2.3. Oliokielten suunnittelukysymyksiä
Edellä esitettyjen asioiden yhteenvetona voidaan todeta, että olio-ohjelmointikielen
suunnittelijan on otettava kantaa ainakin seuraaviin kysymyksiin.
1. Onko kielessä muita tietotyyppejä kuin olioita? Esimerkiksi Smalltalk on puhtaasti
oliokieli, kun taas Javassa ja C++:ssa esiintyy perustietotyyppejä, joiden
ilmentymät eivät ole olioita.
2. Ovatko aliluokat aina perittyjen luokkien alityyppejä?
3. Sallitaanko moniperiytyminen?
4. Miten hoidetaan olioiden muistinvaraaminen ja -vapauttaminen? Esimerkiksi
Javassa olio voidaan varata ainoastaan kekomuistista, kun C++:ssa olio ei eroa
muista muuttujista tältäkään osin.
5. Sidotaanko metodit aina dynaamisesti vai onko staattinen sidonta mahdollista?
6. Sallitaanko sisäkkäisiä luokkia?
3. Olio-ohjelmointi C++ ja Java -kielissä
Tässä osassa tarkastellaan edellä mainittuja käsitteitä konkreettisesti tutkimalla, miten
ne ilmenevät C++- ja Java-kielissä. Vaikka oliomalli molemmissa kielissä on perustaltaan
samankaltainen, on kielten välillä myös huomattavia eroja. Muihin oliokieliin lukija voi
perehtyä esimerkiksi Loudenin teoksesta ([Lou], luku 9) ja Sebestan kirjasta ([Seb], luku
12).
3.1. C++
Jo abstraktien tietotyyppien yhteydessä esiteltiin C++ -kielen luokkamalli. Tässä
perehdytään periytymisen ja myöhäisen sidonnan toteutukseen C++:ssa. Asiaa on
mahdotonta käsitellä kattavasti tässä yhteydessä, kiinnostunut lukija voi perehtyä
C++:n oliomalliin syvällisemmin esimerkiksi Stroustrupin ([Strou]) kirjan osasta II.
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
C++-kielessä periytyminen kirjoitetaan seuraavasti:
class derived_class_name: acces_mode base_class_name {
<data members>
<member functions>
};
Tässä access_mode voi olla jokin näkyvyysmääre, ts. public, protected tai private, joista
yleisin käytännön ohjelmoinnissa on public. C++-kielen näkyvyysmääreillä säädellään,
miten kantaluokan jäsenet näkyvät aliluokassa. Normaalisti (access_mode on public)
aliluokassa näkyvät kaikki public- ja protected-tyyppiset muuttujat ja funktiot ja niiden
näkyvyysmääre on sama kuin kantaluokassa. Sen sijaan private-tyyppisiin jäseniin
aliluokalla ei ole pääsyä. Tällöin aliluokka on kantaluokan alityyppi ja tämän tyypin
ilmentymä voi esiintyä missä tahansa yhteydessä, jossa kantaluokkaa käytetään.
Toisaalta private-näkyvyysmäärettä käyttämällä ainoastaan aliluokan jäsenet ja ystävät
saavat pääsyn kantaluokan public- ja protected-tyyppisiin jäseniin. Käytettäessä
protected-määrettä, lisäksi aliluokasta perityt luokat ja niiden ystävät saavat pääsyn
kantaluokan public- ja protected -tyyppisiin jäseniin. Kummassakaan tapauksessa
aliluokka ei ole kantaluokan alityyppi.
Esimerkki. Olkoon ohjelmassa määritelty luokat
class Kanta
{
public:
void kanta_julkinen_funktio(){
cout << "Olen kantaluokan julkinen funktio" << endl;
}
};
class AliLuokka:public Kanta
{
};
ja funktio
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
void kutsuja(Kanta k)
{
k.kanta_julkinen_funktio();
}
Nyt ohjelma
AliLuokka ak;
ak.kanta_julkinen_funktio();
kääntyy, koska AliLuokka on Kanta -luokan alityyppi ja sen olion kautta funktion
kanta_julkinen_funktio näkyvyys on public. Näin sitä voidaan kutsua. Jos kuitenkin
vaihdetaan aliluokka muotoon
class AliLuokka:private Kanta
{
};
tai
class AliLuokka:protected Kanta
{
};
koodi ei käänny, koska AliLuokka-luokassa perityn funktion näkyvyys ei ole public. Näin
ollen AliLuokka-luokka ei enää olekaan Kanta-luokan alityyppi. Samasta syystä koodi
AliLuokka ak;
kutsuja(ak);
kääntyy ensimmäisessä tapauksessa, mutta kahdessa jälkimmäisessä koodi ei ole
sallittua, vaikka jätettäisiin funktion kutsuja runko tyhjäksikin (AliLuokka -luokan oliota
ei voi muuttaa Kanta -luokan olioksi).
C++ -kielessä moniperiytyminen on mahdollinen, mistä johtuu, että luokkahierarkia on
verkkomainen. Moniperiytyminen aiheuttaa myös sen, että aliluokassa voi tulla
nimitörmäyksiä, kun samanniminen jäsen peritään useammasta luokasta. Samoin
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
dynaamisen sidonnan toteuttaminen on hieman hankalampaa kielessä, jossa
moniperiytyminen on sallittu.
C++ -kielessä ei noudateta automaattisesti dynaamista sidontaa, vaan kun esimerkiksi
laaditaan luokkarakenne:
class Kanta
{
public:
void kanta_funktio()
{
cout << "Olen Kanta -luokan funktio" << endl;
}
};
class AliKanta: public Kanta
{
public:
void kanta_funktio()
{
cout << "Olen AliKanta -luokan funktio" << endl;
}
};
class Alimmainen:public AliKanta
{
public:
void kanta_funktio()
{
cout << "Olen Alimmainen -luokan funktio" << endl;
}
};
ja kirjoitetaan funktio
void kutsu_funktio(Kanta *k)
{
k->kanta_funktio();
}
koodi
Alimmainen alin;
kutsu_funktio(&alin);
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
tulostaa
Olen Kanta -luokan funktio
koska funktiokutsussa olion osoite välittyy parametrina osoittimena Kanta -luokan
olioon ja funktiota ei sidota dynaamisesti oikeaan luokkaan. Sama koskee viitetyypin
muuttujia, ts. jos funktio on
void kutsu_funktio(Kanta &k)
{
k.kanta_funktio();
}
ja ohjelmakoodi
Alimmainen alin;
kutsu_funktio(alin);
tulos on sama. Jos sen sijaan muutettaisiin Kanta-luokan funktio virtuaaliseksi
seuraavasti:
class Kanta
{
public:
virtual void kanta_funktio()
{
cout << "Olen Kanta -luokan funktio" << endl;
}
};
tulostuisi
Olen Alimmainen -luokan funktio
koska virtuaalisiksi määritellyt metodit sidotaan dynaamisesti. C++:ssa kantaluokan
virtuaaliset metodit periytyvät virtuaalisina koko hierarkian läpi, ts. ne ovat virtuaalisia
kaikissa aliluokissa, näiden aliluokissa jne., vaikka aliluokissa niitä ei erikseen
määriteltäisi virtuaalisiksi.
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
Joskus kantaluokasta halutaan tehdä abstrakti luokka. Tämä onnistuu C++ -kielessä
sisällyttämällä luokkaan vähintään yksi puhtaasti virtuaalinen funktio, jolle ei anneta
runkoa vaan merkitään se nollaksi. Esimerkiksi yllä Kanta -luokan funktio voitaisiin
määritellä
class Kanta
{
public:
virtual void kanta_funktio() = 0;
};
Tällöin luokasta tulee abstrakti, eikä siitä voi luoda oliota. Mikäli luokan aliluokista
halutaan konkreettisia, niissä on aina ylikirjoitettava puhtaasti virtuaaliset metodit.
3.2. Java
Lopuksi tarkastellaan periytymistä ja dynaamista sidontaa Java-kielessä ja vertaillaan
sitä C++-kieleen. Vaikka Javakaan ei ole puhtaasti oliokieli (primitiiviset tietotyypit eivät
ole olioita), se on sitä voimakkaammin kuin C++, joka sisältää esimerkiksi luetellut
tietotyypit, jotka eivät ole olioita. Samoin C++:n taulukot eivät ole olioita. Javassa kaikki
muu kuin primitiiviset tietotyypit perustuu olioihin. Edelleen, Javan kaikki luokat perivät
Object -luokan, C++-luokka ei oletusarvoisesti peri mitään. Näin ollen C++ -kielessä on
mahdollista kirjoittaa luokka, jolla ei ole lainkaan jäseniä, mikä ei onnistu Javassa. (Ks.
[Arn], luku 3)
Javassa jokainen luokka perii (extends) täsmälleen yhden luokan (ellei luokan ilmoiteta
perivän toista luokkaa, se perii Object -luokan). Näin ollen Javan luokkahierarkia on
puumainen. Moniperiytymisen puutetta korvaamaan on Javassa otettu rajapinta
(interface). Rajapinta on eräänlainen puhtaasti abstrakti luokka, joka sisältää
ainoastaan metodien esittelyt. Jokainen konkreettinen luokka, joka toteuttaa
(implements) rajapinnan, on velvollinen toteuttamaan rajapinnan kaikki metodit.
Abstrakti luokka voi sen sijaan jättää rajapinnan metodin toteuttamatta. Luokka voi
toteuttaa rajoittamattoman määrän rajapintoja. Rajapinnat voivat myös periä toisia
rajapintoja.
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
Javassa luokka pitää tarvittaessa erikseen määritellä abstraktiksi ja tällainen luokka voi
sisältää abstrakteja metodeja (vaikka luokka voidaan määritellä abstraktiksi siitä
huolimatta, että sillä ei ole abstrakteja metodeja). Abstrakti luokka voidaan kirjoittaa
esimerkiksi seuraavasti:
public abstract class MessagePasser
{
public abstract void send(Object o);
public void send(int i)
{
send(new Integer(i));
}
public void send(double d)
{
send(new Double(d));
}
public abstract Object receive();
public int receiveInt
{
return(((Integer)this.receive()).intValue());
}
public double receiveDbl
{
return(((Double)this.receive()).doubleValue());
}
}
Mikäli halutaan muodostaa konkreettinen luokka, joka perii ylläolevan luokan, on
metodit send(Object o) ja receive() toteutettava. Tällöin saadaan automaattisesti
käyttöön metodin send muut versiot ja metodit receiveInt() sekä receiveDbl() .
Javan periytymisessä ei voida näkyvyyttä säätää kuten C++:ssa. Javan periytyminen
vastaa C++:n public-tyyppistä periytymistä. Siis aliluokka perii kaikki public- ja protected
-tyyppiset jäsenet ja niiden näkyvyydet säilyvät samana aliluokassa. Private-tyyppiset
jäsenet eivät näy aliluokassa. Tästä seuraa myös se, että aliluokka on aina kantaluokan
alityyppi, mikä helpottaa ohjelmointia. Javassa voidaan perimisketju katkaista
määrittelemällä luokka final-tyyppiseksi. Tällöin luokkaa ei voi enää periä aliluokkiin.
Myös luokan metodi voidaan määritellä final-tyyppiseksi, mikä tarkoittaa sitä, että
metodia ei voi enää aliluokissa määritellä uudelleen.
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
Javassa kaikki sidonta on dynaamista, ts. ohjelmoija ei voi itse valita sidonnan tyyppiä
kuten C++:ssa. Tämä toisaalta tekee ohjelmoinnin helpommaksi ja luotettavammaksi;
koska sidonta toimii aina samalla lailla, ei voi tehdä vahingossa koodia, jossa
kutsuttaisiin väärän luokan metodia funktiokutsun seurauksena, mikäli vain tuntee
oletussidonnan. Näin ollen
class Kanta
{
public void kanta_funktio()
{
System.out.println("Olen Kanta -luokan funktio");
}
}
class AliKanta extends Kanta
{
public void kanta_funktio()
{
System.out.println("Olen AliKanta -luokan funktio");
}
}
class Alimmainen extends AliKanta
{
public void kanta_funktio()
{
System.out.println("Olen Alimmainen -luokan funktio");
}
}
public class DynBind
{
static void kutsuja(Kanta k)
{
k.kanta_funktio();
}
public static void main(String[] args)
{
Alimmainen alin = new Alimmainen();
kutsuja(alin);
}
}
tulostaa
Ari Vesanen, Tietojenkäsittelytieteiden laitos, Oulun
yliopisto
815338A Ohjelmointikielten periaatteet: Abstraktit tietotyypit ja olio-ohjelmointi
Olen Alimmainen -luokan funktio
Yhteenvetona voidaan todeta, että vaikka Javan luokkamalli pohjautuukin C++-kielen
malliin, sitä on yksinkertaistettu monin tavoin. Moniperiytyminen on poistettu ja
korvattu rajapintojen käytöllä. Kaikki luokat perivät yhteisen perusluokan (Object).
Edelleen kaikki sidonta on dynaamista. Erityisesti periytymiseen liittyvät
näkyvyysmääreet on yksinkertaistettu C++:n vaikeasti hallittavasta järjestelmästä
huomattavasti selkeämmäksi ja helppokäyttöisemmäksi mekanismiksi. Myös C++ kielen sallimasta ystäväjärjestelmästä on luovuttu. Näistä ominaisuuksista ainoastaan
moniperiytymisen puuttumista voidaan vakavasti arvostella ohjelmoijan työtä
hankaloittavana seikkana.
Sekä C++ että Java sallivat sisäkkäisten luokkien määrittelyn, mitä jotkut teoreetikot
ovat arvostelleet epäonnistuneena ratkaisuna.
Lähteet
[Arn] Arnold, Ken Gosling, James. The Java Programming Language, Second Edition,
Addison-Wesley 1998.
[Har] Harsu, Maarit. Ohjelmointikielet, Periaatteet, käsitteet, valintaperusteet,
Talentum 2005.
[Kur] Kurki-Suonio Reino. Ada-kieli ja ohjelmointikielten yleiset perusteet. MODEEMI ry
Tampere 1983.
[Lou] Louden, Kenneth C. Programming Languages, Principles and Practice, PWS-KENT
1993.
[Seb] Sebesta, Robert W. Concepts of Programming Languages 10th edition, Pearson
2013.
[Strou] Stroustrup, Bjarne. The C++ Programming Language, 3rd edition, Murray Hill
1997.