Helsingin yliopisto / Tietojenkäsittelytieteen laitos
Copyright © 2005 Arto Wikla. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.

4.7 Näkyvyyden säätely

(Muutettu viimeksi 18.11.2005)

Nimien käytettävyydestä eli ns. näkyvyydestä (scope) erilaisissa ohjelmanosissa on ollut puhetta jo monessa yhteydessä. Tässä luvussa kerrataan ja kerätään yhteen Javan tapa hallita nimien näkyvyyttä. (Tässä luvussa ei puututa sisäluokkien näkyvyyskysymyksiin.)

Näkyvyysalueet

Erilaisia näkyvyysalueita - "nimiavaruuksia" - muodostuu seuraaviin rakenteisiin: Huom: Käännösyksikkö ei ole näkyvyysalue! Käännösyksikön luokat tosin kuuluvat aina samaan pakkaukseen.

Javan näkyvyydensäätelymääreillä (access control modifiers) säädellään luokkien, kenttien, konstruktoreiden ja metodeiden näkyvyyttä:

Huom: Nimi ymmärretään Javassa aika väljästi: esimerkiksi x, x(), ja x(3) ovat eri nimiä! Ensimmäinen voi olla vaikka kentän tai paikallisen muuttujan nimi, toinen parametrittoman metodin nimi, kolmas x(int)-metodin nimi, ... Tuotujen (import) pakkausten nimiin voidaan viitata täydellisillä polkunimillä ...

Huom: Java-kääntäjä käyttää hyväksi nimen käyttöyhteyttä nimen merkityksen ymmärtämiseen.

Kielessä on mielestäni sallittu liian laaja nimien kuormittaminen: Perverssi esimerkki teoksesta Arnold, Gosling: The Java Programming Language, Addison-Wesley, 1996:

   class Reuse {
     Reuse Reuse(Reuse Reuse) {
       Reuse:
         for (;;) {
           if (Reuse.Reuse(Reuse) == Reuse)
             break Reuse;
         }
         return Reuse;
     }
   }
Kääntäjälle ohjelma kelpaa, mutta ihmiselle se on hankala ymmärtää. Tuohon tyyliin nimiä käyttämällä voi varmistaa itselleen vaikeuksia niin "kokeessa kuin elämässäkin"!

Nimet metodissa ja konstruktorissa

Metodin ja konstruktorin paikalliset muuttujat ja muodolliset parametrit ovat nimenomaan paikallisia: Niiden yhteydessä ei voi käyttää näkyvyydensäätelymääreitä. Muuttujat ovat olemassa vain niin kauan kuin metodin tai konstruktorin suoritus on kesken.

[Seuraavassa puhutaan vain metodeista vaikka tarkoitetaan sekä metodeita, että konstruktoreita!]

Metodin paikallisten nimien - muuttujien ja muodollisten parametrien - on oltava yksikäsitteisiä. Mutta paikallisten muuttujien käyttöaluetta metodissa voi rajoittaa alilohkoihin, for-lauseeseen ja lohkojen loppuosiin:

    public void m(int p) {
      int i=4;    // p ja i ovat käytettävissä koko metodissa
      i += p;
      { int j=67; // j on käytettävissä vain alilohkossa
        j -= i;
      }
      i += p;
      for (int a=1; a<7; ++a) {
        ...       // a on käytettävissä vain for-lausessa
      }
      int b=77;
      ...         // b on käytettävissä vain metodin loppuosassa
   }
Siis myös alilohkossa määritellyn muutujan nimen on erottava metodin kaikista muista paikallisista nimistä! Toisin sanoen metodin paikallisia nimiä ei voi metodin alilohkoissa peittää. (Katso kuitenkin Lohkot ja muuttujien määrittelylauseet kappaleessa 3.4.)

Paikallisen nimistön lisäksi metodissa voivat olla käytettävissä kaikki metodin oman luokan nimet. Metodi voi paikallisilla nimillään peittää näkyvistä luokan nimiä, mutta tällöinkin luokan nimiin pääsee käsiksi:

Esimerkkejä:
   public class Koe1 {
     private int x, y;
                      // x ja x() ovat eri nimiä!
     public void x() {}

     private static int z;

     public void m(){
       int x, z=5;
       x = this.x;
       Koe1.z = z;
       x();
     }
   }

   public class Koe2 {
     private static  int x, y;

     public static void x() {}

     public static  void m(){
       int x,z;
       x = Koe2.x;
       x();
     }
   }
 

Nimen merkityksen etsintäjärjestys

Nimen merkitystä etsitään seuraavassa järjestyksessä:
  1. metodin paikalliset nimet, muodolliset parametrit mukaanlukien
  2. oman luokan nimet, luokan perimät nimet mukaanlukien
  3. eksplisiittisesti tuodut luokat, so. import pakkaus.Luokka;
  4. omassa pakkauksessa näkyvät luokat, so. muut kuin private (oman luokan privaattikalusto toki näkyy)
  5. implisiittisesti tuodut luokat, so. import pakkaus.*;
  6. automaattisesti tuodut luokat, so. java.lang ja mahdolliset muut toteutuksen tarjoamat valmiit välineet
Huom: Metodista ei mitenkään voi koskaan päästä käsiksi minkään toisen metodin paikallisiin muuttujiin!

Nimet ja luokka

Ylläoleva luettelo selvittää myös, miten nimille etsitään merkitys luokan kenttien alustuslausekkeissa ja staattisissa alustuslohkoissa. Luvussa 4.4 tutustustuttiin perityn metodin korvaamiseen ja perityn kentän peittämiseen.

Ilmentymämuuttujien alustuslausekkeissa voidaan viitata sekä edeltäviin ilmentymämuuttujiin että kaikkiin luokkamuuttujiin. Kaikkia arvon palauttavia ilmentymämetodeita ja luokkametodeita voidaan kutsua ilmentymämuuttujien alustuslausekkeissa.

Esimerkki: Ilmentymämuuttujan oletusalkuarvon käyttäminen ennen alkuarvonasetuslausekkeen suorittamista. (Jos ilmentymät alustetaan aina kokonaisuudessaan konstruktorissa - kuten hyvä on - tässä nähtävää ongelmaa ei synny!)

class Alustus {

  int x = arvo();
  int y = 7;

  int arvo() {return y;}
}

class Akoe {

  public static void main(String[] args) {
    Alustus a = new Alustus();
    System.out.println(a.x+" "+a.y);
  }
} 
Ohjelma tulostaa:
0 7

Luokkamuuttujien (static) alustuslausekkeissa ja staattisissa alustuslohkoissa voidaan viitata edeltäviin luokkamuuttujiin sekä kaikkiin luokkametodeihin, alustuslausekkeissa tietenkin vain arvon palauttaviin luokkametodeihin.

Esimerkki: Luokkamuuttujan oletusalkuarvon käyttäminen ennen alkuarvonasetuslausekkeen suorittamista.

class StaAlustus {

  static int x = arvo();
  static int y = 7;

  static int arvo() {return y;}

  public static void main(String[] args) {
    System.out.println(x+" "+y);
  }
}
Tämäkin ohjelma tulostaa:
0 7



Luokan ja sen piirteiden näkyvyyttä säädellään näkyvyydensäätelymääreillä:

   public class Luokka {   // tarjolla kaikelle maailmalle

     private   int a;      // luokan omaan käyttöön
               int b;      // oman pakkauksen käyttöön
     protected int c;      // pakkauksen ja aliluokkien käyttöön
     public    int d;      // koko maailman käyttöön

     public Luokka() {...} // tällä kaikki saavat konstruoida
                           // ilmentymän luokasta

     private Luokka(int x) { // tällä vain luokka itse saa
       ...                   // konstruoida ilmentymän itsestään 
     }

     // Metodien näkyvyyttä säädellään samalla tavalla.
     // Luokkien näkyvyyttä säädellään samalla tavalla.
   }
Luokasta pääsee käsiksi yliluokan korvattuun metodiin ja peitettyyn kenttään ilmauksella super, elleivät ne ole private. Yliluokan konstruktorin voi oman konstruktorin ensimmäisenä lauseena käynnistää ilmauksella super(...)

Nimet ja pakkaus

Samaan pakkaukseen kuuluvat luokat näkevät toistensa ne piirteet, jotka on joko määritelty protected-määrellä tai joilla on oletusnäkyvyys (so. ei määrettä). Nimien etsintäjärjestys (kts. yllä) määrää nimen merkityksen valinnan tässäkin tapauksessa.


Takaisin luvun 4 sisällysluetteloon.