1. Obiektowość Javy I 

(repetytorium i synteza)


Wykład poświęcony jest powtórzeniu podstawowych pojęć, związanych z programowaniem obiektowym w Javie. Przedstawione koncepcje będą ilustrowane na przykładzie budowy konkretnej klasy, opisującej pojazdy.


1.1. Podstawowe definicje

Java  jest językiem obiektowym. Języki obiektowe posługują się pojęciem obiektu i klasy.

Obiekt – to konkretny lub abstrakcyjny byt, wyróżnialny w modelowanej rzeczywistości, posiadający określone granice i atrybuty (właściwości) oraz mogący świadczyć określone usługi.


Usługa – to określone działanie (zachowanie) obiektu, które jest on zobowiązany przejawiać.


Obiekty współdziałają ze sobą wymieniając komunikaty.

Komunikat -  to wąski i dobrze określony interfejs, opisujący współzależność działania obiektów.


Komunikaty zwykle żądają od obiektów wykonania określonych (właściwych im) usług.

Klasa - to opis takich cech grupy podobnych obiektów, które są dla nich niezmienne (np. zestaw atrybutów i metod czyli usług, które mogą świadczyć)


Definicje powyższe stanowią abstrakcyjne odzwierciedlenie cech rzeczywistości.

Na podstawie:

Kazimierz Subieta. Słownik pojęć obiektowych , FAQ na liście pl.comp.object , P. Coad, E.Yourdon. Analiza obiektowa, Oficyna Wydawnicza READ ME, W-wa 1994 P. Coad, E.Yourdon. Projektowanie obiektowe, Oficyna Wydawnicza - READ ME, W-wa 1994

1.2. Abstrakcja obiektowa. Klasy i obiekty

Gdybyśmy mieli w języku programowania podobne pojęcia, to moglibyśmy ujmować projekt rozwiązania rzeczywistego problemu i jego oprogramowanie w języku adekwatnym do problemu. I to zapewniają języki obiektowe. Jest to ich bardzo ważna cecha – abstrakcja obiektowa, znacznie ułatwiająca tworzenie oprogramowania.

Programowanie polega na przetwarzaniu danych. Dane zawsze są określonych typów, a typ to nic innego jak rodzaj danych i działania które na nich można wykonać.
Z pragmatycznego punktu widzenie możemy więc powiedzieć, że klasa to typ, jej definicja opisuje właściwości typu danych (również funkcjonalne tzn. jakie są dostępne operacje na danych tego typu).
Języki obiektowe pozwalają na definiowanie własnych klas – własnych typów danych, co właśnie oznacza programowanie w języku problemu.
O obiektach możemy myśleć jako o egzemplarzach określonych klas.

Możemy mieć np. klasę pojazdów o następujących atrybutach: szerokość, wysokość, długość, ciężar, właściciel,  stan (stoi, jedzie, zepsuty itp.)  oraz udostępniających usługi: ruszania, zatrzymywania, zmiany właściciela (sprzedaż pojazdu) itp .

rys


Gdy mamy np. dwa obiekty – egzemplarze klasy pojazdów, oznaczane przez zmienne a i b, to możemy symulować w programie sekwencję działań: uruchomienie pojazdu a, uruchomienie pojazdu b, zatrzymanie obu pojazdów - za pomocą komunikatów posyłanych do obiektów, np.:
a.start();       // komunikat do pojazdu a: ruszaj!
b.start();       // komunikat do pojazdu b: ruszaj!
a.stop();       // komunikat do pojazdu a: zatrzymaj się!
b.stop();       // komunikat do pojazdu b: zatrzymaj się!

Mówi się również: a.start()  - to użycie metody start() na rzecz obiektu oznaczanego przez zmienną a.


1.3. Hermetyzacja


Oprócz odzwierciedlenia w programie "języka problemu" abstrakcja obiektowa ma jeszcze jedną ważną przewagę nad ujęciami nieobiektowymi.
Mianowicie, atrybuty obiektu nie powinny być bezpośrednio dostępne. W programie z obiektami "rozmawiamy" za pomocą komunikatów, obiekty same "wiedzą najlepiej" jak zmieniać swoje stany. Dzięki temu nie możemy nic nieopatrznie popsuć, co więcej nie możemy zażądać od obiektu usługi, której on nie udostępnia.

Dane (atrybuty) są ukryte i są traktowane jako nierozdzielna całość z usługami.
Nazywa się to hermetyzacją i oznacza znaczne zwiększenie odporności programu na błędy.

Sama koncepcja klasy jako zestawu pól i metod już zapewnia określony poziom hermetyzacji.
Nie możemy np. do obiektów klasy Vehicle posłać komunikatu sing(), bowiem metoda sing() nie występuje jako składowa tej klasie.

Dodatkowo języki obiektowe (w tym Java) pozwalają ukrywać dane (i metody)  przed powszechnym dostępem.
Dostęp do składowych klasy regulują tzw. specyfikatory dostępu, których używamy w deklaracjach zmiennych, stałych i metod.

Każda składowa klasy może być:


Po co jest prywatność? 

1. Ochrona przed zepsuciem (pola powinny być prywtane) 2.  Zapewnienie właściwego interfejsu  (metody "robocze" winny być prywatne) 3. Ochrona przed konsekwencjami zmiany implementacji


Mamy też w Javie pojęcie klas publicznych i pakietowych (klasy w Javie są albo publiczne albo pakietowe).Klasa pakietowa jest dostępna tylko z klas pakietu. Klasa publiczna jest dostępna zewsząd (z innych pakietów).Klasę publiczną deklarujemy ze specyfikatorem public


Stosując regułę ukrywania danych i specyfikatory dostępu możemy teraz przedstawić przykładową definicję klasy Person, a następnie zmodyfikować definicję klasy Vehicle.

rys


Atrybuty obiektów klasy Person przedstawiono jako pola prywatne.
Spoza klasy nie ma do nich dostępu.
Przy tworzeniu obiektu jego elementy odpowiadające tym polom są inicjalizowane za pomocą wywołania konstruktora. Później zmiany tych elementów danych nie są już możliwe, możemy tylko uzyskać dane za pomocą publicznych metod getName() i getPesel().


Przypomnienie
  • programowanie w Javie polega na posługiwaniu się obiektami
  • przed użyciem obiekty muszą być tworzone
  • do tworzenia obiektów służy wyrażenie new
  • po słowie new podajemy odwołanie do konstruktora z odpowiednimi argumentami
  • wyrażenie new zwraca referencję (odniesienie, swoisty adres) do nowoutworzonego obiektu
  • posługiwanie się obiektami w Javie polega wyłącznie na operowaniu na referencjach

rys







Terminologia

"Adres" obiektu nazywa się odniesieniem. Zmienna, która zawiera odniesienie nazywa się odnośnikiem.
Często nie rozgranicza się tych dwóch pojęć i mówi się o odniesieniu i odnośniku jako o referencji. Jest to czasem wygodniejsze, szczególnie, gdy różnica treści zawsze jasna jest z kontekstu.
Dla wygody używać będziemy także skrótu myślowego mówiąc o obiektach, gdy naprawdę chodzi o referencje np. zamiast "metoda zwraca referencję do obiektu klasy Color" – "metoda zwraca obiekt" lub nawet "zwraca kolor".  Pamiętajmy jednak zawsze, że w Javie operuje się wyłącznie na referencjach.


Modyfikując i rozbudowując klasę Vehicle w myśl reguł hermetyzacji:
  public class Vehicle  {

  // stany
  public final int BROKEN = 0, STOPPED = 1, MOVING = 2;
  private final String[] states =  { "ZEPSUTY", "STOI", "JEDZIE" };

  private int width, height, length, weight;
  private Person owner;
  private int state;

  public Vehicle(Person p, int w, int h, int l, int ww)  {  // konstruktor
     owner = p;      width = w;     height = h;
     length = l;     weight = ww;   state = STOPPED;
  }

  public void start()  {     setState(MOVING);  }

  public void stop()   {     setState(STOPPED);  }

  // Prywatna metoda robocza wykorzystywana w metodach start() i stop().
  // Arbitralne ustalenie stanu spoza klasy nie jest możliwe
  private void setState(int newState)  {
     if (state == newState || state == BROKEN)
        System.out.println("Nie jest mozliwe przejscie ze stanu " +
                            states[state] + " do stanu " + states[newState]);
     else state = newState;
  }

  public void repair()  {
     if (state == MOVING)
       System.out.println("Nie można reperować jadącego pojazdu");
     else if (state != BROKEN)
            System.out.println("Pojazd sprawny");
     else state = STOPPED;
  }

  // Sprzedaż pojazdu
  public void sellTo(Person p)  {
     owner = p;
  }

  public String toString()  {
    return "Pojazd, właścicielem którego jest "
            + owner.getName() + " - " + states[state];
  }

}


Zwróćmy uwagę:

1.4. Statyczne składowe klasy

Stałe (oznaczające stany i ich nazwy) są teraz zawarte w każdym tworzonym obiekcie klasy Vehicle. Niewątpliwie jest to marnotrawstwo: powinniśmy móc zdefiniować je jako właściwość raczej całej klasy obiektów, a nie każdego obiektu z osobna (zawsze są przecież takie same). Na pomoc przychodzi koncepcja składowych statycznych.

Składowe klasy mogą być statyczne i niestatyczne.
Niestatyczne zawsze wiążą się z istnieniem jakiegoś obiektu (pola - odpowiadają elementom obiektu, metody muszą być wywoływane na rzecz obiektu, są komunikatami do obiektu)
Składowe statyczne (pola i metody):


Uwaga: ze statycznych metod nie wolno odwoływać się do niestatycznych składowych klasy (obiekt może nie istnieć). Możliwe są natomiast odwołania do innych statycznych składowych.

Spoza klasy do jej statycznych składowych możemy odwoływać się na dwa sposoby:

Uczynimy zatem stałe zdefiniowane w klasie Vehicle statycznymi, dostarczymy też metody pozwalającej pokazać możliwe nazwy stanów pojazdów, nawet jeśli nie ma jeszcze żadnych  obiektów klasy Vehicle. Skorzystamy też z koncepcji składowych statycznych po to, by każdemu pojazdowi tworzonemu w naszym programie nadawać unikalny numer (powiedzmy od jednego), a także zawsze mieć rozeznanie ile obiektów typu Vehicle dotąd utworzyliśmy (co – uwaga – nie znaczy, że wszystkie jeszcze istnieją!).

public class Vehicle  {

  public final static int BROKEN = 0, STOPPED = 1, MOVING = 2;
  private final static String[] states =  { "ZEPSUTY", "STOI", "JEDZIE" };

  private static int count;   // ile obiektów dotąd utworzyliśmy
  private int currNr;         // bieżący numer pojazdu

  //...
  public Vehicle(Person p, int w, int h, int l, int ww)  {
     // ....
     // Każde utworzenie nowego obiektu zwiększa licznik o 1
     // bieżąca wartość licznika nadawana jest jako numer pojazdu
     // numer pojazdu jest niestatycznym polem klasy, a więc elementem obiektu
     currNr = ++count;
  }
  //.....

  // zwraca unikalny numer pojazdu
  public int getNr()  { return currNr; }

  // zwraca liczbę dotąd utworzonych obiektów
  // metoda jest statyczna, by móc zwrócić 0
  // gdy nie ma jeszcze żadnego obiektu
  static int getCount()  { return count; }

  // zwraca tablicę nazw stanów
  // Metoda jest statyczna, bo dopuszczalne stany
  // nie zależą od istnienia obiektów
  static String[] getAvailableStates()  {
     return states;
  }
  // ...
}

Wykorzystanie:

// jakie są dopuszczalne nazwy stanów
// pojazdów?
String[] stany = Vehicle.getAvailableStates();

// ile obiektów dotąd utworzyliśmy?
int n =Vehicle.getCount()

1.5. Inicjacje 

Przy tworzeniu obiektu:
Obiekt klasy Vehicle składa się z następujących elementów (odpowiadają one polom klasy):

    private int currNr;
    private int width, height, length, weight;
    private Person owner;
    private int state;

W trakcie tworzenia obiektu  ( new Vehicle(...) ) uzyskają one wartości 0 (dla elementów typu int) oraz null dla elementu, odpowiadającego referencji owner.
Następnie zostanie wywołany konstruktor, w którym dokonujemy właściwej inicjacji.

Można by było napisac np.

    private int state = STOPPED; // jawna inicjacja pola

i usunąć z konstruktora instrukcję state = STOPPED.

A co z polami statycznymi? I jaka jest kolejność inicjacji?

Reguły inicjacji
  • Każde pierwsze odwołanie do klasy inicjuje najpierw pola statyczne. Pierwszym odwołaniem do klasy może być odwołanie do składowej statycznej   (np. Vehicle.getCount()) lub utworzenie obiektu ( np. Vehicle v – new Vehicle());
  • Tworzenie obiektu (new) inicjuje pola niestatyczne, po czym   wykonywany jest konstruktor,
  • Kolejność inicjacji pól - wg ich kolejności w definicji klasy   (w podziale na statyczne i niestatyczne, najpierw statyczne)


Gdybyśmy np. zmienili interpretację statycznego pola count (niech będzie to teraz pierwszy dopuszczalny numer identyfikacyjny) i jawnie je zainicjowali wielkością 100, to w kontekście:

class Vehicle {
   private int currNr = ++count;
   private static count = 100;
   ...
   public int getNr() { return currNr; }
   public static int getCount() { return count; }
}

po:

    Vehicle.getCount()
    Vehicle v = new Vehicle(...);

wyrażenie:

    v.getNr()

miałoby wartość 101.

Z inicjacją wiąże się również pojęcie bloków inicjacyjnych.
Jak pamiętamy - w Javie w zasadzie nie można używać instrukcji wykonywalnych (m.in. sterujących) poza ciałami metod. Od tej zasady istnieją jednak dwa wyjątki:
Niestatyczny blok inicjacyjny wprowadzamy ujmując kod wykonywalny w nawiasy klamrowe i umieszczając taką konstrukcję w definicji klasy poza cialem jakiejkolwiek metody. Kod bloku zostanie wykonany na etapie inicjacji obiektu, czyli przy tworzeniu obiektu, przed wywołaniem konstruktora.
Taka  możliwość może okazać się przydatna, gdy mamy kilka konstruktorów (o wielu różnych konstruktorach definiowanych w jednej klasie - zob. następny punkt) i chcemy wyróżnić pewien kod, który będzie inicjował obiekt niezależnie od użytego konstruktora i przed użyciem jakiegikolwiek z nich.

Np. możemy w bloku inicjacyjnym wyodrębnić nieco bardziej zaawansowaną postać inicjacji zmiennej state, opisująceje stan pojazdu.

public class Vehicle {
  // ...
  private int state;

  // Niestatyczny blok inicjacyjny
  // -- w niedzielę wszystkie samochody inicjalnie stoją
  // -- w poniedziałek te o parzystuych numerach inicjalnie jadą,
  //    inne - stoją
  // -- w pozostałe dni tygodnia: pierwszy stworzony inicjalnie stoi,
  //    inne jadą

  { // początek bloku
    int dayOfWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK);
    switch (dayOfWeek) {
      case Calendar.SUNDAY : state = STOPPED; break;
      case Calendar.MONDAY : state = (currNr % 2 == 0 ? MOVING : STOPPED);
                             break;
                   default : state = (currNr == 1 ? STOPPED : MOVING);
                             break;
    }
  } // koniec bloku


  // Konstruktory i metody klasy
  public Vehicle(...)  {
    // ...
  }
  // ...
} 

Uwaga: dla uzyskania aktualnego dnia tygodnia stosujemy klasę Calendar.

W pokazanym przypadku takie wyodrębnienie niestatycznego bloku inicjacyjnego ma pewien sens (kod konstruktorów jest może bardziej czytelny), ale zazwyczaj nie jest to zbyt interesujące rozwiązanie, i - jeśli już - należy go stosować z wyjątkowym umiarem (bowiem może ono  oznaczać trudniej czytelny kod - rozproszony po wielu miejscach, zamiast skupiony w naturalnym miejscu inicjacji - konstruktorach).

Ciekawszym rozwiązaniem wydaje się statycznyblok inicjacyjny . Czasem pojawia się potrzeba wykonania jakiegoś kodu jeden raz, przy pierwszym odwołaniu do klasy. Przy inicjacji pól statycznych możemy skorzystać z dowolnych wyrażeń, składających się ze zmiennych i stalych statycznych oraz  z wywołań statycznych metod, ale  - oczywiście - nie sposób użyć instrukcji wykonywalnych (np. sterujących).

Statycznym blok inicjacyjny wprowadzamy  słowem kluczowym static z następującym po nim kodem ujętym w nawiasy klamrowe. Kod ten będzie wykonywany jeden raz przy pierwszym odwołaniu do klasy (np. użyciu metody statycznej lub stworzeniu pierwszego obiektu). Oczywiście, z takiego bloku możemy odwoływać się wyłącznie do zmiennych statycznych (obiekt jeszcze nie istnieje).

Przykład: wyobraźmy sobie, że numeracja pojazdów zależy od domyślnej lokalizacji aplikacji (inna jest jeśli lokalizacją jest USA - inną dla, powiedzmy, Włoch). Będziemy więc inicjować zmienną statyczną count w zależności od lokalizacji (w tym przypadku oczywiście count nie będzie oznaczać liczby utworzonych w programie obiektów, ale inicjalny numer pojazdu). Taki problem można rozwiązac właśnie za pomocą statycznego bloku inicjacyjnego.

public class Vehicle  {

  public final static int BROKEN = 0, STOPPED = 1, MOVING = 2;
  private final static String[] states =  { "ZEPSUTY", "STOI", "JEDZIE" };
  private static int count;

  // Statyczny blok inicjacyjny
  // za jego pomocą inicjujemy zmienną count w taki sposób,
  // by numery pojazdów zaczynały się w zależności
  // od domyślnej lokalizacji aplikacji
  // np. jeśli aplikacja jest wykonywana w lokalizacji włoskiej
  // numery zaczynają się od 10000.

  static { 
    // Locale.setDefault(Locale.ITALY);
    Locale[] loc = { Locale.UK, Locale.US, Locale.JAPAN, Locale.ITALY, };
    int[] begNr =  { 1, 100, 1000, 10000, };

    count = 300;  // jeżeli aplikacja działa w innej od wymienionych
                  // w tablicy lokalizacji, zaczynamy numery od 300

    Locale defLoc = Locale.getDefault(); // jaka jest domyślna lokalizacja? 

    for (int i=0; i < loc.length; i++)
      if (defLoc.equals(loc[i])) {
        count = begNr[i];
        break;
      }
  }  // koniec bloku


  // pola niestatyczne

  private int currNr = ++count;
  private int width, height, length, weight;
  // ...
  // Konstruktory i metody
  // ...

} // koniec klasy





1.6. Przeciążanie metod i konstruktorów

W klasie (i/lub jej klasach pochodnych) możemy zdefiniować metody o tej samej nazwie, ale różniące się liczbą i/lub typami argumentów.
Nazywa się to przeciążaniem metod.

r


Tak samo możemy mieć kilka wersji konstruktorów:

r


Klasa wcale nie musi mieć jawnie zdefiniowanego konstruktora.
W tym przypadku do definicji klasy automatycznie dodawany jest konstruktor domyślny , bezparametrowy (tj. nie mający żadnych parametrów). Konstruktor domyślny "nic nie robi" (jego ciało jest puste), a wtedy niestatyczne pola klasy są inicjowane według podanych w ich deklaracjach inicjatorów lub (gdy inicjatorów brak) na gwarantowane wartości ZERO (0, false, null).

Jeśli zdefiniowaliśmy jakikolwiek konstruktor, to konstruktor bezparametrowy NIE jest automatycznie dodawany i musimy go sami zdefiniować


Przypomnienie

Słowo kluczowe this w ciele konstruktorów i metod niestatycznych oznacza referencję do danego obiektu (tzn. tego, który jest inicjowany przez konstruktor lub tego na rzecz którego użyto metody).

Zwykle nie używamy this: odwołania do składowych klasy wewnątrz konstruktora lub metody są jednoznaczne – dotyczą tego obiektu (inicjowanego przez konstruktor lub na rzecz którego wywołano metodę). Np.

public void start()  {     setState(MOVING);  }
=
public void start()  {     this.setState(Vehicle.MOVING);  }

i

public int getState()     { return state; }
=
public int getState()     { return this.state; }



Czasem musimy użyć słowa this.

1. Przy przesłonięciu nazw zmiennych oznaczających pola.
Np.:
class Vehicle {
  private int width, height, length, weight;
  private Person owner = null;
  private int state;

  public Vehicle(Person owner, int width, int height, int length, int weight)  {
     this.owner = owner;      this.width = width;     this.height = height;
     this.length = length;      this.weight = weight;
}


2. Drugi - częsty - przypadek koniecznego użycia słowa this występuje wtedy, gdy metoda musi zwrócić TEN obiekt (na rzecz którego została wywołana).

Możemy np. zdefiniować metodę repair() tak, by zwracała ten pojazd (który został nareperowany)
Vehicle repair() {
  // ...
  return this;
}
i pisać szybko:

    Vehicle v .... ;
    ...
    v.repair().start();

3. Trzeci przypadek użycia słowa this - to wywołanie z konstruktora innego konstruktora (o czym mowa była przed chwilą).

1.7. Pełny przykład klasy Vehicle i jej wykorzystania

public class Vehicle  {

  public final static int BROKEN = 0, STOPPED = 1, MOVING = 2; // stany
  private final static String[] states =  { "ZEPSUTY",
                                            "STOI",
                                            "JEDZIE" };        // nazwy stanów

  private static int count;       // liczba dotąd utworzonych obiektów,
                                  // inicjalnie 0

  private int currNr = ++count;   // przy tworzeniu obiektu count zwiększa się o 1
                                  // i staje się numerem pojazdu

  private int width, height, length, weight;    // atrybuty pojazdu
  private Person owner;                         // ... właściciel
  private int state = STOPPED;                  // ... stan

  public  Vehicle()  {  }                       // konstruktor bezparametrowy

  // konstruktor; tworzy pojazdy bez właściciela
  public Vehicle(int w, int h, int l, int ww) {
     this(null, w, h, l, ww);
  }

  // pełny konstruktor
  public Vehicle(Person p, int w, int h, int l, int ww)  {
     owner = p;     width = w;
     height = h;     length = l;
     weight = ww;
  }

  public void start()  {      // start pojazdu
     setState(MOVING);
  }

  public void stop()  {       // zatrzymanie pojazdu
     setState(STOPPED);
  }

  // Robocza metoda zmiany stanu, wykorzystywana w stop() i start()
  // Uwzględnia możliwe zmiany stanów (sprawdza to)
  // Jest prywatna: arbitralna zmiana stanu pojazdu
  //nie jest możliwa spoza klasy
  private void setState(int newState)  {
    if (state == newState || state == BROKEN)
      System.out.println("Nie jest mozliwe przejscie ze stanu "
                          + states[state] + " do stanu " + states[newState]);
    else state = newState;
  }

  // Reperowanie pojazdu:
  // zawsze zakończone sukcesem, jeśli tylko można przystąpić do naprawy :-)
  public Vehicle repair()  {
     if (state == MOVING)
       System.out.println("Nie można reperować jadącego pojazdu");
     else if (state != BROKEN) System.out.println("Pojazd sprawny");
     else state = STOPPED;
     return this;
  }

  // zwraca stan jako liczbę;
  // można ją porównać ze stałymi Vehicle.BROKEN, Vehicle.STOPPED  itp.
  public int getState()  { return state; }

  // Zwraca nazwę stanu podanego jako argument
  public static String getState(int state)  { return states[state];}

  // czy pojazd stoi?
  public boolean isStopped()  { return state == STOPPED; }

  // Kolizja: w wyniku kolizji oba pojazdy w niej uczestniczące
  //stają się niesprawne
  // Nie może być kolizji, jeśli oba pojazdy stoją
  public void crash(Vehicle v)  {
      if (state != MOVING && v.state != MOVING)
         System.out.println("Nie ma kolizji");
      else  {
        setState(BROKEN);
        v.setState(BROKEN);
      }
  }

  public void sellTo(Person p)  {  // sprzedaż pojazdu osobie p
     owner = p;
  }

  public int getNr()  { return currNr; }  // zwraca unikalny numer pojazdu

  // Czy pojazd może przejechać pod konstrukcją o podanej wysokości?
  public boolean isTooHighToGoUnder(int limit)  {
      return height > limit ? true : false;
  }

  // Metoda toString uwzględnia teraz,
  // że pojazd może jeszcze nie mieć właściciela
  public String toString()  {
    String s = (owner == null ? "sklep" : owner.getName());
    return "Pojazd " + currNr + " ,właścicielem którego jest "
           + s + " - " + states[state];
  }

  // ile pojazdów utworzyliśmy dotąd?
  static int getCount()  { return count; }

  // zwraca tablicę nazw dostępnych stanów pojazdu
  static String[] getAvailableStates()  {
     return states;
  }

}

class VehTest  {  // klasa testująca

 // Metoda raportująca stan tablicy pojazdów
 // przekazanych jako argument
 static void report(String msg, Vehicle[] v)  {
   System.out.println(msg);
   for (int i=0; i < v.length; i++)
      System.out.println("" + v[i]);
 }


 public static void main(String[] args)  {
  System.out.println("Dopuszczalne stany pojazdow");
  String[] states = Vehicle.getAvailableStates();
  for (int i = 0; i < states.length; i++) System.out.println(states[i]);

  System.out.println("W programie mamy teraz pojazdow: " + Vehicle.getCount());

  Vehicle[] v =  { new Vehicle(new Person("Jan Piesio", "010268246"),
                               200, 150, 500, 900),
                   new Vehicle(new Person("Stefan Kot", "010262241"),
                               210, 250, 800, 1900),
                   new Vehicle(200, 230, 300, 600),
                  };

  System.out.println("W programie mamy teraz pojazdow: " + Vehicle.getCount());
  report("Na początku", v);
  System.out.println("Pojazd 3 zostaje sprzedany Ambrożemu");
  v[2].sellTo(new Person("Ambroży", "000000"));
  for (int i=0; i < v.length; i++) v[i].start();

  report("Po wyruszeniu", v);
  // Czy mogą przejechać pod wiaduktem 220 cm?
  for (int i=0; i < v.length; i++)
    if (v[i].isTooHighToGoUnder(220)) v[i].stop();

  report("Komu udało się przejechać pod wiaduktem ?", v);

  // Zatrzymane wyruszaja w objazd
  for (int i=0; i < v.length; i++)
    if (v[i].isStopped()) v[i].start();
  report("Po wyruszeniu w objazd", v);

  // Kolizja
  v[0].crash(v[1]);
  report("Po kolizji", v);
  System.out.println("Piesio próbuje ruszyć");
  v[0].start();
  System.out.println("Piesio sprzedaje pojazd Krówce. Ta go reperuje i jedzie");
  v[0].sellTo(new Person("Anna Krówka", "121212908"));
  v[0].repair().start();
  report("Ostatecznie",v);

  System.out.println("Stan pojazdu 3 " + Vehicle.getState(v[2].getState()));
 }

}
Po uruchomieniu programu otrzymamy wydruk:

Dopuszczalne stany pojazdow
ZEPSUTY
STOI
JEDZIE
W programie mamy teraz pojazdow: 0
W programie mamy teraz pojazdow: 3
Na początku
Pojazd 1 ,właścicielem którego jest Jan Piesio - STOI
Pojazd 2 ,właścicielem którego jest Stefan Kot - STOI
Pojazd 3 ,właścicielem którego jest sklep - STOI
Pojazd 3 zostaje sprzedany Ambrożemu
Po wyruszeniu
Pojazd 1 ,właścicielem którego jest Jan Piesio - JEDZIE
Pojazd 2 ,właścicielem którego jest Stefan Kot - JEDZIE
Pojazd 3 ,właścicielem którego jest Ambroży - JEDZIE
Komu udało się przejechać pod wiaduktem ?
Pojazd 1 ,właścicielem którego jest Jan Piesio - JEDZIE
Pojazd 2 ,właścicielem którego jest Stefan Kot - STOI
Pojazd 3 ,właścicielem którego jest Ambroży - STOI
Po wyruszeniu w objazd
Pojazd 1 ,właścicielem którego jest Jan Piesio - JEDZIE
Pojazd 2 ,właścicielem którego jest Stefan Kot - JEDZIE
Pojazd 3 ,właścicielem którego jest Ambroży - JEDZIE
Po kolizji
Pojazd 1 ,właścicielem którego jest Jan Piesio - ZEPSUTY
Pojazd 2 ,właścicielem którego jest Stefan Kot - ZEPSUTY
Pojazd 3 ,właścicielem którego jest Ambroży - JEDZIE
Piesio próbuje ruszyć
Nie jest mozliwe przejscie ze stanu ZEPSUTY do stanu JEDZIE
Piesio sprzedaje pojazd Krówce. Ta go reperuje i jedzie
Ostatecznie
Pojazd 1 ,właścicielem którego jest Anna Krówka - JEDZIE
Pojazd 2 ,właścicielem którego jest Stefan Kot - ZEPSUTY
Pojazd 3 ,właścicielem którego jest Ambroży - JEDZIE
Stan pojazdu 3 JEDZIE




1.8. Podsumowanie


W wykładzie przypomniano pojęcia:
pokazując ich zastosowanie przy budowie i wykorzystaniu klasy, opisującej pojazdy.


1.9. Zadania i ćwiczenia

Zadanie 1. Woda

Gromadzimy wodę mineralną. Są trzy rodzaje butelek: o pojemności 2 l (duże) , średnie - o pojemności 1 l oraz małe o pojemności 0.5 l.
Stworzyć klasę MyWater z metodami:

void addLarge(int)  - dodaje do zapasu wody podaną jako argument liczbę dużych butelek,
void addMedium(int)  - dodaje do zapasu wody podaną jako argument liczbę średnich butelek.
void addSmall(int)  - dodaje do zapasu wody podaną jako argument liczbę małych butelek

oraz z metodami umożliwiającymi uzyskanie informacji o tym ile jest każdego rodzaju butelek oraz jaka jest łączną pojemność zgromadzonej wody.

Pojemności butelek (dużych, średnich, małych) przedstawić jako pola statyczne klasy.
Dostarczyć metod, pozwalających uzyskiwac informacje o tych pojemnościcach oraz je zmieniać.

Klasę przetestować dodając do zapasu  różne liczby butelek wody i wyprowadzając potem komunikat w rodzaju:

Mam teraz 6.5 litrów wody
dużych butelek: 2
średnich butelek: 1
małych butelek: 3


Zadanie 2  - pudełka 

Zdefiniować klasę Box, której obiekty będą stanowić pudełka o zadanych rozmiarach (szerokość, wysokość).
Dostarczyć w tej klasie specjalnych metod lączenia pudełek:


Przetestować klasę w następujący sposób:


Zadanie 3 (wektory)


Stworzyć klasę Wektor, której obiekty będą stanowić wektory (tablice) liczb rzeczywistych i dostarczyć w niej następujących konstruktorów i metod:
  1. konstruktor tworzący wektor zerowy o rozmiarze n (n - argument konstruktora)
  2. konstruktor tworzący wektor z podanej jako argument tablicy liczb rzeczywistych,
  3. konstruktor tworzący wektor z podanego jako argument napisu, ktorego kolejne słowa będą stanowić elementy wektora,
  4. metoda get, która zwraca i-ty lement wektora,
  5. metoda set, która ustala i-ty element wektora na podaną wartość,
  6. metoda max, która zwraca maksymalny element wektora,
  7. me toda min, która zwraca minimalny element wektora,
  8. metoda size, która zwraca liczbę elementów wektora.
  9. metoda add, która zwraca wektor, będący sumą poelementową dwóch wektorów.
  10. metoda toString, która zwraca łańcuch znakowy przedstawiający elementy wektora,
  11. statyczna metoda show(double[]), która wyprowadza na konsoli w jednym wierszu elementy przekazanej jako argument tablicy,
  12. metoda sort, która zwraca posortowany wektor w p orządku rosnącym lub malejącym (to zależy od argumentu).
Etapy wykonania (wzrastająca skala trudności):

A.  należy wykonać punkty 1, 2, 4-8 i uzyskać prawidlowy wynik części poniższego programu zaznaczonej na czerwono.

B.  należy dodatkowo wykonać punkty 9-11, a także dodatkowo zapewnić prawidłowe wykonanie części programu  zaznaczonej na brązowo.

C.  należy dodatkowo wykonać punkty 3 i 12, przy czym  w punkcie 3 należy uwzględnić, że :
W punkcie 12 należy przyjąć, że argumentem metody sort jast napis oznaczający porządek sortowania: ASC - rosnąco, DESC - malejąca. Litery w napisie mogą być małe lub duże.


Program, który ma działać i wytwarzać pokazane wyniki:

public class Test {

// tu zapewne znajdzie się metoda increase

public static void main(String[] args) {
    double[] d = { 1, 2, -7, 3, 4 };
    Wektor w1 = new Wektor(d);
    Wektor w2 = new Wektor(5);
    w2.set(1, 12);
    w2.set(3, -1);
    System.out.println(w1.min() + " " + w1.max() + " " +
                       w2.min() + " " + w2.max());

    /*---------------------------------------------------*/
    Wektor w3 = w1.add(w2);
    Wektor.show(d);
    System.out.println(w1.toString());
    System.out.println(w2.toString());
    System.out.println(w3.toString());
    // Wywolana poniżej metoda increase ma zwiększać wartość każdego elementu
    // podanego wektora (tu w1)
    // o podaną wielkośc (tu 1)
    // dopóki suma elementów nie przekroczy podanej wartości (tu 5)
    increase(w1, 1, 5);
    System.out.println("Teraz w1 = " + w1);
    /*------------------------------------------------------*/
    // w4 i w5 oznaczają  obiekty klasy Wektor
    // ...
    try {
      w4 = new Wektor("2 7 a jeden z");
      w5 = new Wektor("      ");
    } catch (IllegalArgumentException exc) {
        System.out.println(exc.toString());
    }
    w5 = w4.sort("Asc");                // sortowanie w porządku rosnącym
    System.out.println(w4.toString());
    System.out.println(w5.toString());
    w5 = w4.sort("DESC");               // sortowanie w porządku malejącym
    System.out.println(w5.toString());
  }
}
Wyniki:

-7.0 4.0 -1.0 12.0

[ 1.0 2.0 -7.0 3.0 4.0 ]
[ 1.0 2.0 -7.0 3.0 4.0 ]
[ 0.0 12.0 0.0 -1.0 0.0 ]
[ 1.0 14.0 -7.0 2.0 4.0 ]
Teraz w1 = [ 2.0 3.0 -6.0 3.0 4.0 ]
java.lang.IllegalArgumentException: Wadliwy argument konstruktora Wektor(String)
[ 2.0 7.0 10.0 1.0 0.0 ]
[ 0.0 1.0 2.0 7.0 10.0 ]
[ 10.0 7.0 2.0 1.0 0.0 ]



 Zadanie 4 (liczba wystąpień słów)

Stworzyć klasę SortedWordsFromFile, której obiekty będą posortowanymi tablicami wszystkich słów z pliku tekstowego, którego nazwę podajemy przy tworzeniu obiektu jako argument konstruktora.
Dostarczyć w tej klasie metody getWords(), która zwraca posortowaną tablicę wszystkich słów oraz metody getUniqueWordsCount(), która zwraca tablicę obiektów klasy WordsCount, każdy z których reprezentuje parę: unikalne słowo - liczbe jego wystąpień w pliku.
Łańcuchowa reprezentacja takich obiektów, uzyskiwana metodą toString winna mieć formę: słowo liczba_wystąpień.
W klasie WordsCount dostarczyć statycznej metody sortowania tablic elementów tego typu według liczby wystąpień słowa - sortByCount(WordsCount[]).

Dodatkowe warunki, które ma spelniać program:
  1. tablica unikalnych słów musi mieć rozmiary dokładnie takie jak liczba unikalnych słów, 
  2. nie wolno dokonywać żadnych realokacji tablic,
  3. nie wolno używać żadnych klas kolekcyjnych,
  4. algorytmy sortowania należy napisac samemu, ale nie muszą być stabilne, ani szybkie, wystarczą najprostsze - typu "selection sort"
  5. metoda getUniqueWordsCount zwraca tablicę elementów posortowaną w alfabetycznym porządku słów,
  6. słowa mają być identyfikowane możliwie najpelniej, tak by dość sensowne było przetwarzanie tekstów (uwzględnić odpowiedni zestaw separatorów),
  7. zliczając wystąpienia, słów słowa różniące się tylko wielkością liter traktujemy jako takie same,
  8. tekst programu nie może przekraczać 110 wierszy i ma zawierać poniższą metodę main, która daje prawidłowy wynik dla przytoczonego poniżej przypadku testowego. 
  public static void main(String[] args)  {
     SortedWordsFromFile swf = new SortedWordsFromFile(args[0]);
     WordsCount[] wc = swf.getUniqueWordsCount();
     System.out.println("Wszystkich słów jest: " + swf.getWordsCount());
     System.out.println("Unikalnych: " + wc.length);
     for (int i=0; i<wc.length; i++) System.out.println(wc[i].toString());
     System.out.println("Posortowane wg liczby wystąpień od największej:");
     WordsCount.sortByCount(wc);
     for (int i=0; i<wc.length; i++) System.out.println(wc[i].toString());
  }

Zawartość pliku:
ala ma kota
kot ala pies pies ala
i "co tam, co tam" Ala! Kot? Pies;i kot

Wszystkich słów jest: 18
Unikalnych: 8
ala 4
co 2
i 2
Kot 3
kota 1
ma 1
pies 3
tam 2
Posortowane wg liczby wystąpień od największej:
ala 4
Kot 3
pies 3
tam 2
i 2
co 2
ma 1
kota 1