Jednostka leksykalna

Zasób słów w języku polskim nazwiemy słownictwem. Albo leksykiem. Stąd też mamy takie pojęcie jak jednostka leksykalna. Leksykalna, czyli powiązana ze słownictwem. Jednostka leksykalna to najmniejsza, samodzielna znaczeniowo jednostka językowa. Np.: wyraz lub liczba.

Analiza leksykalna

Dupa. Dupa jest wyrazem. Wyraz to jak już wiemy jednostka leksykalna. Wartością naszej przykładowej jednostki leksykalnej jest dupa. Narząd na którym się siada. Dupę zakwalifikujemy jako wyraz, bo widzimy, że składa się ze znaków znanych z alfabetu. Znamy więc pewien wzorzec, który pozwala nam zakwalifikować ten ciąg znaków jako wyraz (a nie np.: jako liczbę). Tym wzorcem będą wszystkie litery alfabetu.

W programowaniu taka analiza leksykalna również jest dokonywana. Taki analizator leksykalny widzi jakiś ciąg znaków, np.:

int myNumber = 10;

i próbuje coś z tym zrobić. Jednostkę leksykalną int zakwalifikuje jako np.: typ zmiennej według zdefiniowanego wzorca, podobnie zrobi z myNumber i zakwalifikuje go jako identyfikator, = zakwalifikuje jako operator, a 10 jako literał całkowity. Jeśli chcesz dowiedzieć się jak to dokładnie odbywa się w Javie, to zajrzyj tu.

Czyli literałem będzie wartość, którą przypisujemy do zmiennej.

Eh, się napisałem, a wystarczyło proste zdanie. Ale może nieraz fajniej znać szerszy kontekst.

Poniżej definicja ze specyfikacji Javy:

A literal is the source code representation of a value of a primitive type, the String type or the null type.

No, czyli już co nieco wiemy. Dla upewnienia się:

String myString = "w prostych słowach";

A string literal is always of type String“. Czyli mamy tu literał łańcuchowy, którego wartością jest w prostych słowach.

Literał całkowity

Co wiemy o typie całkowitym w Javie. Na przykład to:

/**
 * A constant holding the maximum value an {@code int} can
 * have, 2^31-1.
 */

Czyli maksymalna wartość jaką może przechowywać to 2147483647. Sprawdźmy.

int maxIntValue = 2147483647; // compiles
int tooBigIntValue = 2147483648; // compiler error

No i okej. Zgadza się. Faktycznie, jak próbuję wpisać wartość o 1 za dużą, to kompilator nie pozwala. No to co. Skoro w typie int się ta wartość nie mieści, to przypiszę ją do typu long. Proste jak słońce.

long myLongValue =  2147483648; // compiler error

Wtf. Czemu się nadal świeci. Zrestartuję komputer. Nadal to samo :<

Okazuje się, że literały liczbowe w Javie są zawsze traktowane jako int! Zatem kompilatora nie obchodzi to, że próbujemy przypisać tę za dużą wartość do longa. On liczbę 21474183648 w chwili kompilacji traktuje jako int, int nie może być większy niż 2^31-1.

Żeby móc nadać literałowi liczbowemu typ long musimy napisać:

long myLongValue =  2147483648L; // compiles

Czyli dodać literkę “l” lub “L” na końcu.

Literał byte (!?)

Compile-time constant

final byte a = 1;

Gdy patrzysz na zmienną ‘a’ widzisz, że ma przypisaną wartość 1 i nie będzie inaczej, nie zmienimy tej wartości, bo zmienna ‘a’ jest oznaczona jako final. Zmienna ‘a’ jest tzw., compile-time constant. Możemy mówić, że zmienna jest compile-time constant jeśli jest finalna i jest zainicjowana przy pomocy compile-time constant expression. Więcej możesz o tym przeczytać tu. W skrócie:

final byte a = 1; // compile-time constant
final byte b = getRandomByteValue(); // NOT compile-time constant 

Wartość zmiennej compile-time constant musi być znana w czasie kompilacji. Metoda getRandomByteValue nie stanowi compile-time constant expression. Nie wiadomo co zwróci.

Poruszyłem pewną zagwozdkę z autorem bloga programming.guide. Oto ona:

byte byteOverflow = Byte.MAX_VALUE+1; // compiler error
int intOverflow = Integer.MAX_VALUE+1 // compiles!

Najpierw pierwszy przypadek, byteOverflow. Pamiętamy, że każdy literał liczbowy jest intem.

Rozłóżmy poniższy zapis na czynniki pierwsze

Byte.MAX_VALUE + 1

Byte.MAX_VALUE jest compile-time constant (jest final i przypisana wartość liczbowa). Kompilator, zanim rozwiąże to arytmetyczne działanie, dokona promocji Byte.MAX_VALUE do inta. Dopiero wówczas doda cyfrę 1 (int). Ponieważ wynik tego działania (int) wychodzi poza dopuszczalny zakres typu byte, konwersja z int na byte (byteOverflow jest typu byte) nie jest wykonywana i dostajemy błąd kompilatora, że nie można przypisać wartości int do typu byte. Zachowanie to można wyjaśnić za pomocą assignment context, opisanego tu.

Na dowód, że tak to działa, możemy spróbować napisać:

byte myByte = Byte.MAX_VALUE + 0; // compiles

Następuje tu promocja Byte.MAX_VALUE do inta, potem kompilator wykonuje działanie (int + int = int), sprawdza, że wynik mieści się w typie byte i dlatego na koniec dokonuje konwersji (narrowing) inta do byte.

Powyższe wyjaśnienie oparłem na korespondencji z Andreasem oraz na podstawie jego artykułu, który powstał na bazie mojej zagwozdki. Dostępny jest on tu.

Nie rozgryziona pozostaje kwestia int overflow:

int intOverflow = Integer.MAX_VALUE+1 // compiles!

Póki co zganiamy to na jakąś zaszłość. Jeśli wiesz coś więcej, napisz 🙂

Zatem

Możemy stwierdzić, że o ile mamy literały łańcuchowe, integer, long, to nie mamy literałów typu byte (z short ta sama sytuacja). Tutaj kompilator ogarnia sprawę zgodnie z takimi terminami jak compile-time constant, compile-time constant expression oraz assignment context.

Leave a Reply

Your email address will not be published. Required fields are marked *