20 maja 2008

SCJP - Wartości domyślne zmiennych

Minęło parę dni od czasu ostatniego artykułu z serii SCJP, a to za sprawą alokowania mojego wolnego czasu na sprawy organizacyjne związane z konferencją JAVArsovia 2008, ale pora powrócić do pracy – więc oto jestem. Kontynuuję, co zapowiedziałem w artykule "Przygotowania do SCJP czas zacząć". Będzie o wartościach domyślnych nadawanych zmiennym nie zainicjowanym explicite.

Co się dzieje, kiedy deklarujemy zmienną? Czy musimy przypisać jej jakąś wartość? Czy jeśli tego nie zrobimy, zmienna będzie zainicjowana jakąś wartością domyślną? To zależy, zaraz dowiemy się, od czego. Prosta zasada brzmi – zmienne zadeklarowane w klasie są inicjowane wartością domyślną zawsze a zmienne lokalne nigdy. Dodatkowo, elementy tablic (ang. array) są zawsze inicjowane wartością domyślną, niezależnie od tego czy sama tablica została zadeklarowana lokalnie czy w klasie. Zerknijmy na poniższy przykład.

public class Test {
static int x;

public static void main(String[] args) {
System.out.println(x);
}
}

Zmienna ‘x’ została zadeklarowana w klasie i nie została zainicjowana explicite, wobec tego zostanie jej przypisana wartość domyślna. To, że zmienna ta jest statyczna nie ma znaczenia – istotne jest, że nie jest to zmienna lokalna, tj. zadeklarowana w metodzie lub konstruktorze. Wartość domyślna jest różna dla różnych typów. Dla typów całkowitoliczbowych, tj. ‘byte’, ‘short’, ‘int’ i ‘long’ wartością domyślną jest liczba 0. Dla typów liczbowych zmiennopozycyjnych, tj. ‘float’ i ‘double’ wartością domyślną jest liczba 0.0. Dla typu prostego ‘boolean’ wartością domyślną jest ‘false’. Dla typu znakowego ‘char’ jest to wartość ‘\u0000’. Zmienne referencyjne są domyślnie inicjowane wartością ‘null’. Powyższy program skutkuje więc wypisaniem liczby 0. Zerknijmy dla kontrastu na kolejny przykład.

public static void main(String[] args) {
int x;

System.out.println(x); // błąd!
}

Nie tylko kod ten się poprawnie nie wykona, ale nawet się nie skompiluje. Próba użycia niezainicjowanej zmiennej lokalnej jest błędem czasu kompilacji. Co ważne, błąd wynika z próby użycia zmiennej nie zainicjalizowanej, a nie z faktu deklaracji zmiennej bez przypisania wartości. Wartości można przecież przypisać później, aczkolwiek przed pierwszym użyciem. I kolejny przykład.

public static void main(String[] args) {
int x;

if(args.length > 0)
x = 1;

System.out.println(x); // błąd!
}

Co prawda w powyższym kodzie występuje instrukcja inicjalizacji zmiennej ‘x’, ale jest ona wykonywana warunkowo i kompilator nie jest w stanie stwierdzić, czy rzeczywiście zostanie ona zawsze wykonana, a więc kod również się nie skompiluje. Gdyby wartość dozoru instrukcji warunkowej dała się wyliczyć statycznie i było by to ‘true’ (np. ‘1 < 2’) to sprawa wyglądałaby inaczej – kompilator wiedziałby, że zmienna będzie zawsze zainicjalizowana i kod by się skompilował. A teraz zobaczmy jak sprawa się ma z tablicami.

public static void main(String[] args) {
int[] array = new int[16];

for(int x : array)
System.out.println(x);
}

Program się skompiluje i poprawnie wykona, wypisując w rezultacie uruchomienia ciąg szesnastu cyfr 0. Tak jak wcześniej napisałem – elementy tablic są zawsze inicjalizowane na wartości domyślne, niezależnie od miejsca deklaracji samej tablicy. Oczywiście samą tablicę musieliśmy utworzyć explicite. I jeszcze ostatni przykład.

public class Test {
public static void main(String[] args) {
SomeClass sc = new SomeClass();

System.out.println("someFloat: " + sc.someFloat
+ " doubleValue: " + sc.doubleValue);
}
}

class SomeClass {
Float someFloat;

double doubleValue;
}

Uruchomienie powyższego programu spowoduje wyświetlenie napisu "someFloat: null doubleValue: 0.0". Zauważmy jeszcze, że to, iż wartość wyrażenia ‘sc.someFloat’ została przekonwertowana do napisu "null" wynika z implementacji funkcji ‘println(…)’, a właściwie z tego, że zanim wywoła ona operację ‘toString()’ na przekazanej referencji sprawdzane jest, czy referencja ta nie ma właśnie wartości ‘null’.

7 komentarzy:

SmayLee pisze...

Dodałbym jeszcze, ze zmienne instacyjne nie sa inicjowane domyslnymi wartosciami jesli sa finalne:
class A{
final int a;
public void f(){
System.out.println("a: "+a);
}
}
nie skompiluje sie!!!

Mariusz Lipiński pisze...

Na śmierć zapomniałem o zmiennych final! Miałem je w pamięci przez cały czas pisania artykułu... i w końcu zapomniałem. Wielkie dzieki za uzupełnienie. Naturalnie zmienne finalne (a właściwie to stałe) muszą być zainicjowane explicite w momencie deklarowania. Można by powiedzieć, że stałe to troche inna bajka, ale zdecydowanie informacja ta powinna była sie pojawić w tym artykule.

Pozdr. Mariusz Lipiński

Anonimowy pisze...

Ostatni akapit mija się z prawdą.
W czasie kompilacji wypisanie zostanie zamienione na:
System.out.println(new StringBuilder().append("someFloat: ").append(sc.someFloat).append(" doubleValue: ").append(sc.doubleValue).toString());
Sięgając do StringBuilder::append(Object) :
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}

później do String::valueOf :
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}

widać, że to funkcja valueOf z klasy String wstawiła ten 'null'

Mariusz Lipiński pisze...

Widzę, że muszę sobie narzucić więcej dyscypliny jeśli chodzi o formułowanie pewnych myśli. Dzięki za wnikliwą lekturę i bardzo celną uwagę.

Pozdrawiam i zachęcam do dalszego komentowania.

Michał pisze...
Ten komentarz został usunięty przez autora.
Michał pisze...

Witam. Odnosnie stalych (final) - chcialbym zaznaczyc iz nie trzeba inicjalizowac zmiennych finalnych w deklaracji jesli zrobimy to w konstruktorze:

class A{
final int a;
A(){a = 6;}
public void f(){
System.out.println("a: "+a);
}
}

tyczy sie oczywiscie zmiennych NIE statycznych

Anonimowy pisze...

Aha! Jesli chodzi o inicjalizacje zmiennych lokalnych mozna:

int x;
do {
x = 0;
} while (true);
// lub jakikolwiek inny warunek niekonieczny znany w czasie kompilacji

jak i wiele innych, ktore trzeba odkryc samemu.

monsieur.lame