« poprzedni punkt  następny punkt »

3. Typy zmiennych. Deklaracje

W przeciwieństwie do literałów typy zmiennych i stałych programista musi podać sam w deklaracjach.

Deklaracja zmiennej (stałej) polega na określeniu nazwy zmiennej (stałej), jej typu oraz ew. pewnych innych właściwości, które są istotne wtedy, gdy zmienna jest polem klasy

Składnię deklaracji zmiennej - w uproszczeniu - można przedstawić w następujący sposób:

nazwa_typu nazwa_zmiennej;

Na przykład:

int a; // deklaracja zmiennej całkowitotoliczbowej a
char c; // deklaracja zmiennej, która może zawierać znaki Unikodu
double price; // deklaracja zmiennej typu double o nazwie price


Przy okazji deklarowania zmiennych można ustalać ich wartości, co nazywa się inicjacją.

Inicjacja zmiennej - to ustalenie jej wartości przy okazji deklaracji

Deklarację z inicjację zapisujemy w formie:

nazwa_typu nazwa_zmiennej = wyrażenie;

Na przykład:

int a = 3;

co jest skróconą formą od:

int a;
a = 3;

W tym przypadku wyrażeniem inicjującym był literał. Ogólnie może być to dowolne wyrażenie (w tym wywołanie metody)

Np.
int a = 3;
int b = a + 1; // deklaracja zmiennej b i ustalenie wartości na 4

Zauważmy, że użycie zmiennej a w wyrażeniu inicjującym wartośc zmiennej b było możliwe tylko dlatego, że deklaracja zmiennej a wystąpiła przed użyciem jej w tym wyrażeniu.

Do deklarowania stałych używamy słowa kluczowego final.

Wartość stałej można ustalić tylko raz i poźniej ta wartość nie może być już zmieniona


Piszemy:

final nazwa_typu nazwa_stałej [ = wyrażenie ]

przy czym inicjacja (podana w nawiasach kwadratowcyh) jest tu opcjonalna, bowiem istotne jest tylko to by wartość stałej została ustalona tylko raz. Najczęściej jednak wartość tę ustalać będziemy w momencie deklaracji.

Np.

final int NUM = 55;
final double PI_SMALL = 3.14;
final char C;
...
C = 'c';


Deklaracje zmiennych i stałych, które oznaczają obiekty zapisujemy w analogiczny sposób. Zobaczmy.
Jeżeli int ma być typem zmiennej x (która oznacza liczbę całkowitą) to piszemy:

int x;

Podobnie jeżeli s jest nazwą zmiennej, która oznacza obiekt klasy String, to piszemy:

String s;

To samo ze zmiennymi, które oznaczają obiekty każdej innej klasy (np. z zestawu "klas standardowych", dostarczanych wraz ze środowiskiem Java 2 SDK lub też klas, które sami zdefiniujemy):

Button b; // zmienna b  oznacza obiekt klasy Button
Frame frame; // zmienna frame  oznacza obiekt klasy Frame


Reguły dobrego stylu programowania w zasadzie nakazują umieszczanie deklaracji każdej zmiennej w odrębnym wierszu programu wraz z dodatkowym komentarzem opisującym przeznaczenie zmienenej, gdy nazwa zmiennej nie mówi całkowicie wyraźnie co dana zmienna oznacza.

Zauważmy jeszcze, że dopuszczalne jest deklarowanie kilku zmiennych tego samego typu w jednej deklaracji poprzez wymienienie ich (wraz z ew. inicjatorami) na liście rozdzielonej przecinkami np.
int num = 1, count, ff = 10;
Button b1, b2, b3;


Trzeba koniecznie pamętać, że wszystkie zmienne w programie muszą być zadeklarowane.

Deklaracje obowiązkowo muszą poprzedzać użycie zmiennych i stałych w innych instrukcjach programu.
Nie znaczy to jednak, że deklaracje muszą poprzedzać wszystkie instrukcje programu.
Wręcz przeciwnie: zmienne i stałe deklarujemy zwykle możliwie najbliżej miejsca ich użycia, co zwiększa czytelność kodu.

Zobaczmy przykład podobny do obliczania ceny komputera w początkowych wykładach:

public class CompPrice {

  public static void main(String[] args) {

    int cProc = 700;   // cena procesora
    int cPly  = 500;   // ... płyty
    int cPam = 300;    // innych składnikow ...
    int cDysk = 400;
    int cInn = 500;

    final double VAT = 1.22;  // narzut podatku VAT

    // Liczymy cenę komputera bez monitora
    // wyrażenie sumujące składniki zapisując
    // jako wyrażenie inicjującę zmienną cKomp

    double cKomp = (cProc + cPly + cPam + cDysk + cInn) * VAT;

    System.out.println("Cena komputera bez monitora wynosi :");
    System.out.println(cKomp);

    int cMon = 1100;             // cena monitora netto
    cKomp = cKomp + cMon * VAT;  // i nowa cena komputera z monitorem

    System.out.println("Cena komputera z monitorem wynosi :");
    System.out.println(cKomp);

 }
}

Zauważmy: gdybyśmy zapomnieli dodać słówko int przed cMon - zmienna cMon byłaby niezadeklarowana.

    cMon = 1100;                 // cena monitora netto
    cKomp = cKomp + cMon * VAT;  // i nowa cena komoutera z monitorem

Kompilator wykryje tę sytuację i poinformuje nas o błędzie


CompPrice.java:19: cannot resolve symbol
symbol : variable cMon
location: class CompPrice
cMon = 1100; // cena monitora netto
^
CompPrice.java:20: cannot resolve symbol
symbol : variable cMon
location: class CompPrice
cKomp = cKomp + cMon * VAT; // i nowa cena komputera z monitorem
^
2 errors

Na marginesie, z komunikatem "cannot resolve symbol" będziemy się spotykać dość często. Oznacza on (zwykle), że używamy jakiejś nazwy, której kompilator nie może zidentyfikowac (np. niezadeklarowanej zmiennej).


Java rozróżnia wielkie i male litery w nazwach zmiennych, stałych, metod, klas


Jednym z typowych źródeł takich błędów są błędy w pisowni, a szczególnie mylenie wielkich i małych liter w nazwach. O ile z własnymi programami zwykle nie będziemy mieli tutaj wielkich problemow (ostatecznie sami wymyśliliśmy nazwy), to nazwy stosowane w standardowych pakietach Javy mogą nam sprawiać więcej kłopotu.

Stosowana jest tutaj dość klarowna konwencja nazewnicza, która bardzo nam pomoże, ale na początku często nie będziemy jeszcze pewni jak pisać np. HashTable czy Hashtable?


      Identyfikatory

Nazwy zmiennych, stałych, metod, klas - nazywają się identyfikatorami.
Identyfikatory muszą zaczynać się od litery lub podkreślenia (znaku _) i mogą składać się z dowolnego ciągu znaków alfanumerycznych (liter i cyfr) i/lub znaków podkreśleń.
Jako identyfikatorów nie wolno używać zarezerwowanych słów języka -
zob. spis słów zarezerwowanych Javy

      Konwencje nazewnicze:

  • nazwy zmiennych i metod - zaczynam małą literą, każdy składnik wyróżniamy rozpoczynając go dużą literą (np. x, price, numOfAllOccurs, liczbaDzieciMlodszych, increment(), getBackground())
  • nazwy stalych - piszemy dużymi literami, składniki nazwy rozróżniając za pomocą znaku podkreślenia (np. NUM, EXIT_ON_CLOSE)
  • nazwy klas zaczynamy dużą literą, i dalej wyrózniamy poszczególne składniki nazwy - też dużą literą (np. Frame, ArrayList)
Uwaga: konwencja wyróżniania składników nazwy poprzez rozpoczynanie ich dużą literą nazywa się notacją węgierską

Na zakończenie warto jeszcze raz zastanowić się jaki jest sens pojęcia typu.
Na "niskim poziomie" (instrukcji maszynowych) każda jednostaka danych rzeczywiście musi mieć typ. Wynika to ze znanego już nam faktu, że każda informacja (instrukcje maszynowe, dane) jest przedstawiana w pamięci komputera za pomocą ciągów bitów o wartościach 1 lub 0. Nazwa zmiennej oznacza zaś obszar pamięci, w którym znajduje się oznaczana przez tę zmienną wartość.
Np. jeśli mamy zmienne x i y, to nazwy te (jakoś) odsyłają do początków obszarów, w których zapisane są jakieś ciągi bitowe:

x 1 1 0 0 1 0 1 1 ....
y 1 ... 0 1 1 0 0 1 0 0 1 ...

Jak w takiej sytuacji komputer ma zinterpretować wyrażenie x + y ? Gdzie kończą się wartości x i y ? Co oznacza operacja + ? Odpowiedzi na te pytania wynikają właśnie z typów zmiennych x i y, i dlatego typy te muszą być określone.

Przykładowo: jeśli zmienne x i y są typu int, to do przechowywania wartości tych zmiennych zostaną wydzielone obszary pamięci o określonej długości (4 bajty), a instrukcja dodawania będzie skutkować w wygenerowaniu odpowiednich instrukcji maszynowych, które w sumie umożliwią dodanie do siebie dwóch wartości z tych obszarów przy zastosowaniu arytmetyki stałopozycyjnej. Gdyby zmienne x i y były typu float, to obszary zajmowanej przez nie pamięci byłyby inne (8 bajtów), a dodawanie realizowane w arytmetyce zmiennopozycyjnej.

Dlaczego jednak w programie pisanym w Javie, która przecież jest językiem wysokiego poziomu (abstrahującym - choć jak widzimy nie do końca - od "szczegółow technicznych") programista zawczasu musi deklarować zmienne, określając przy tym ich typy?

Są języki, które tego nie wymagają. Zazwyczaj są to języki interpretowane, bo okazuje się, że stworzenie kompilatora dla języka bez typów jest niezwykle trudne (choć nie niemożliwe, bo już są takie kompilatory, aczkolwiek raczej o charakterze eksperymentalnym, nie szerokodostępnym).

Mamy więc pierwszą odpowiedź na nasze pytanie: trudności stworzenia kompilatora
(Java jest językiem nie tylko interpretowanym, ale również kompilowanym, a prawdziwy powód oraz kontekst tego ostatniego za chwilę wyjasnimy).

Jest też inny argument, dotyczący szczególnie typów numerycznych. Dane różnych typów numerycznych (jak widzieliśmy) zajmują różną objętość pamięci. Mając do wyboru kilka typów liczbowych programista może więc lepiej sterować zajętością pamięci i efektywnością programu.
Jeśli np. musi przechowywać w pamięci 10 tys. liczb i wie, że wszystkie one mogą być tylko całkowite, a ich wielkości mieszczą się 4 bajtach, to używając typu int zamiast typu double oszczędza 40 tys. bajtów oraz znacznie zwiększa efektywność programu, gdyż działania na liczbach całkowitych wykonują się zazwyczaj wielokrotnie szybciej niż na liczbach rzeczywistych.
Powiedzmy jednak szczerze: ten kontekst jest bardzo ważny w zastosowaniach naukowych, przy dużych obliczeniach itd., natomiast na skutek postępu w sprzęcie oszczędności rzędu 1 MB pamięci czy kilka milionów operacji w programie dla codziennych powszechnych zastosowań oprogramowania nie są już tak istotne.

Dochodzimy więc do chyba najbardziej właściwej odpowiedzi na pytanie o sens deklarowania zmiennych i określania ich typów.
Otóż określanie typów danych (nawet tych najbardziej elementarnych) w deklaracjach umożliwia kompilatorowi kontrolę poprawności programu np. stwierdzenie czy zmienne określonych typów stosujemy we właściwych kontekstach albo czy przekazujemy funkcjom (metodom) argumenty właściwych typów. Nazywa się to kontrolą zgodności typów (przez kompilator) i często pomaga wykrywać błędy w programie już w fazie kompilacji.

Java jest językiem ze ścisłą kontrolą zgodności typów i temu głównie służy wybrany przez twórców Javy sposób kompilowania programów, skutkujący w składni języka.

Ścisła kontrola zgodności typów oznacza, że typ wyniku każdego wyrażenia znany jest już w fazie kompilacji

Przeświadczenie o tym, że ścisła kontrola zgodności typów chroni przed błędami, żywione przez twórców Javy, chyba najbardziej zaważyło na tym, iż w Javie trzeba deklarować zmienne i w deklaracjach określać ich typy, a stosując literały trzeba pamiętać o konwencjach traktowania literalnie podanych danych.


« poprzedni punkt  następny punkt »