następny punkt »

1. Odwołania do składowych klasy. Zmienna this

Nareszcie nadeszła pora na szczegółowe omówienie konstrukcji klasy Para.
Teraz (na chwilę) nie będziemy zwracać uwagi na specyfikatory dostępu (dla omawianego przykładu nie mają one znaczenia). Klasę Para zdefiniujemy też troszkę inaczej niż dotychczas (w programach przykładowych z katalogu samples do poprzednich wykładów), tak by móc rozważać różne ciekawe sytuacje.

class Para {

    int a;    // To są "dane" (zwane polami klasy).
              // Określają one z jakich elementów składać się
    int b;    // będą obiekty tej klasy.
              // a = pierwszy składnik pary, b - drugi


 Para(int x, int y) {   // konstruktor: nadaje wartość parze
   a = x  ;             // na podstawie przekazanych wartości x i y
   b = y;
 }

 Para(int x) {          // inny konstruktor: nadaje obu składnikom pary
   a = b = x;                  // (a i b) tę samą wartość x
 }


 void set(Para p)  {    // metoda ustalenia wartości pary
   a = p.a;             // na podstawie składników przekazanej pary
   b = p.b;
 }


 Para add(Para p) {             // metoda dodawania dwóch par
   Para wynik = new Para(a, b);
   wynik.a += p.a;
   wynik.b += p.b;
   return wynik;
 }

                               // metoda pokazująca parę
 void show(String s)  {
   System.out.println(s + " ( " + a + " , " + b + " )" );
 }

}

W innej klasie możemy użyć klasy Para, np. tak:

class ParaTest {

  public static void main(String[] args) {
    Para para1 = new Para(1,5);
    Para para2 = new Para(2,4);
    para1.show("Para 1 =");
    para2.show("Para 2 =");
    Para sumaPar = para1.add(para2);
    sumaPar.show("Suma par =");
    para1.set(para2);
    para1.show("Teraz para 1 = ");

  }

}

Para 1 = ( 1 , 5 )
Para 2 = ( 2 , 4 )
Suma par = ( 3 , 9 )
Teraz para 1 =  ( 2 , 4 )
 

Powyższy program wyprowadzi na konsolę następujące wyniki.
Zobaczmy co się naprawdę dzieje.

Gdy piszemy:
Para para1 = new Para(1, 5);
wyrażenie new tworzy obiekt tzn.:

  • wydziela miejsce w pamięci do przechowania obiektu-pary (miejsce na dwie liczby całkowite)
  • elementy pary odpowiadają polom a i b zadeklarowanym w klasie
  • elementy te otrzymają wartość 0 (domyślna inicjacja)
  • wywoływany jest konstruktor klasy Para z argumentami 1 i 5, a jego wykonanie powoduje, że elementy utworzonej pary odpowiadające polom a i b otrzymują odpowiednio wartości 1 i 5 
  • wyrażenie new zwraca referencję do nowoutworzonego obiektu
  • referencja ta podstawiana jest na zmienną para1 (w tej chwili zmienna para1 zawiera referencję - inaczej odniesienie - do nowoutworzonej pary)

Podobnie możemy napisać:

Para para2 = new Para(2,4);

Mamy teraz dwa obiekty para1 i para2.

para1 "wygląda" tak        para2 "wygląda" tak

Pola:                      Pola:

  int a; ( = 1)              int a; ( = 2)
  int b; ( = 5)              int b; ( = 4)
---------------------------------------------
Metody:                    Metody:

  void set(...)              void set(...)
  Para add(...)              Para add(...)
  void show(...)             void show(...)

Identyfikatory pól i metod są takie same!
Zatem trzeba ich używać "na rzecz" konkretnego obiektu (para1 albo para2).

Do tego rozróżniania służy kropka (nareszcie naocznie widać jej użyteczność):

para1.a - oznacza element a obiektu para1
para2.a - oznacza element a obiektu para2

To samo z metodami:

para1.show();  // obiektowi oznaczonemu para1 wysyłamy komunikat show (pokaż się)
                      // co oznacza wywołanie metody show na rzecz obiektu para1

para2.show();  // obiektowi oznaczonemu para2 wysyłamy komunikat show (pokaż się)
                      // co oznacza wywołanie metody show na rzecz obiektu para2

Uwagi:
  • o ile elementy obiektów (odpowiadające polom) są zawarte w obiektach, to metody - są "wspólne" dla wszystkich obiektów klasy; podpisanie ich pod każdym obiektem oznacza tylko, że mogą one być używane dla tego obiektu, a nie, że zajmują jakieś miejsce "w obiekcie"
  • w opisie użyto "skrótu myślowego" dla uproszczenia tekstu; mówiąc o obiektach para1 i para2 pamiętamy, że tak naprawde zmienne te są referencjami do obiektów.

Zajrzyjmy teraz do wnętrza klasy. Skąd wiadomo co konkretnie oznacza a i b w konstruktorze albo w metodzie set?
Rozważmy konstruktor

class Para {
   int a, b;

public Para(int x, int y) {   // słowo public - niekonieczne
   a = x  ;                            
   b = y;
}
....
}

Wyrażenie new najpierw tworzy obiekt, a później wywoływany jest konstruktor. Zatem w momencie rozpoczęcia działania konstruktora obiekt już istnieje (jest mu przydzielona pamięć na przechowanie dwóch liczb całkowitych,  ich wartości  zostały inicjalnie określone, w naszym przypadku jako zera). Wykonanie konstruktora dotyczy właśnie tego nowoutworzonego  obiektu. W konstruktorze dostępna jest referencja do tego obiektu w postaci niejawnie zdefiniowanej zmiennej o nazwie this. (this = TEN).

Słowo this jest słowem kluczowym języka

Zatem this.a i this.b - zgodnie z interpretacją znaczenia kropki to pola a i  b tego obiektu,  którego dotyczą inicjacje wykonywane przez konstruktor.


Rys

Ponieważ i tak wiadomo, że samo a i b dotyczy pól (elementów) tego obiektu, dla którego akurat wołany jest konstruktor, to słowo this możemy pominąć.

To samo dotyczy metod wywoływanych na rzecz obiektów.
Wyobraźmy sobie, że na rzecz obiektu para1 wywołano metodę set z argumentem  para2.
Działanie metody set ma polegać na przepisaniu zawartości pary para2 do pary para1.

"Algorytm" metody set jest taki:

  1. polu a tego obiektu na rzecz którego wywołano metodę przypisz wartość pola a obiektu przekazanego jako argument
  2. polu b tego obiektu na rzecz którego wywołano metodę przypisz wartość pola b obiektu przekazanego jako argument

TEN obiekt na rzecz którego wywołano metodę jest wewnątrz metody reprezentowany słowem kluczowym this.

Rys

I znowu możemy pominąć słówko this, bo tu jasne jest z kontekstu.

Problem
void set(Para p) {
   a = p.a;
   b = p.b;
}


Zobaczmy teraz jak działa metoda dodawania dwóch par.

Po pierwsze: mamy dwie pary, które chcemy dodać - wobec tego silna jest pokusa by użyć metody z dwoma argumentami. Ale przecież programujemy obiektowo: pierwsza z par do której dodajemy drugą będzie obiektem do którego poślemy polecenie add:

    para1.add(para2);

Po drugie: co zrobić z wynikiem dodawania?
W rezultacie dodawania powinna powstać nowa para - suma dwóch dodanych par.
Ta nowa para winna być stworzona w metodzie add, a referencja do niej zwrócona jako wynik tej metody. Dlatego:

Rys

Problem
Zauważmy też, że wszystko co analizowaliśmy dotąd na przykładzie pól - dotyczy również metod.

Gydbyśmy w klasie Para mieli na przykład dwie metody getA() i getB(), które zwracają odpowiednie składniki pary, to w innej metodzie klasy (np. metodzie sumującej składniki pary) moglibyśmy się do nich odwołać:

class Para {
    int a, b;
    ...
    int getA() { return a; }
    int getB() { return b; }

    int sum() {
        int pierwsza = this.getA();
        int druga = this.getB();
        return pierwsza + druga;
    }
}

co oznaczałoby, że wywołujemy metody getA i getB na rzecz tego obiektu, na rzecz którego została wywywołana metoda sum.
Ponieważ jest to jasne z kontekstu, możemy napisac prościej:

int sum() {
    return getA() + getB();
}
   

Są przypadki, kiedy użycie słowa kluczowego this jest istotne. Na przykład, gdy identyfikatory parametrów przesłaniają (są takie same jak) identyfikatory pól klasy.

class Para {
    int a, b;

Para (int a,  int b) {
     a = ... // o które a chodzi: parametr czy pole???
...
}

void set(int a, int b) {
    a = ... // o które a chodzi: parametr czy pole???
}
}

Dla rozróżnienia należy użyć słowa this.
Zapis this.a zawsze oznacza pole a obiektu inicjowanego przez konstruktor lub tego na rzecz którego wywołano metodę. Zatem w powyższym kontekście piszemy:

class Para {
  int a;
  int b;

  Para(int a, int b) {
    this.a = a;    // polu a obiektu przypisz wartość parametru a
    this.b = b;    // polu b obiektu przypisz wartość parametru b
  }

  void set(int a, int b) {
    this.a = a;
    this.b = b;
  }
}

Więcej na temat przesłaniania identyfikatorów pod koniec wykładu.


 następny punkt »