Rozdział 7. Wątki

start() nie powoduje wystartowania wątku, lecz jego dodanie do kolejki gotowych do wykonania. Możliwośći stworzenia wątku:
  1. Implementacja klasy dziedziczącej po Thread.
  2. Implementacja klasy implementującej interface Runnable, stworzenie klasy Thread podając wcześniej zaimplementowaną klasę w konstruktorze.

Raz zakończonego wątku nie można jeszcze raz uruchomić, trzeba go stworzyć na nowo i znowu start(). Ponownie wywołanie start() lub run() spowoduje wywołanie zwyczajnej metody. Wątek można przerwać metodą interrupt().

Nadanie priorytetu nie daje pewności co do natychmiastowego zastosowania zmian. Priorytet wątku mówi o statystycznym ogólnym zasotsowaniu. Wątek dziedziczy swój priorytet po jego rodzicu (wątku).

Daeon Threads - setDaemon(boolean) (standardowy false).

Yielding - zaprzestanie wykonywania i umieszczenie wątku w kolejce Ready. Jeśli jest tylko jeden wątek oczekujący, to yield() nic nie daje. yield() nie powoduje także zaprzestania wątku ze względu na wątki o niższym priorytecie.

wait(), notify() i notifiAll() mogą być wywołane tylko z metody synchronized.

Gdy wywołuje się metodę synchronized - klasa musi dostać lock na dany obiekt (lub całą klasę jeśli metoda jest static).

Rozdział 6. Classes

Overloading Methods
  1. Ta sama nazwa, inna liczba lub typ argumentów.
  2. Różna może być dostępność, zwracany typ, lista wyjątków.
  3. Metoda jest unikalna jeśli chodzi o klasę, w której sie znjaduje, nazwę, typ zwracany, kolejność (typ) i liczbę argumentów.
Metodę definiuje jej nazwa, liczba i rodzaj (kolejność, typ) argumentów.

Overriding Methods
  1. Overloaded muszą mieć inną listę argumentów; Overriding - ten same typy i kolejność argumentów.
  2. Typ zwracany Overloaded method może być dowolny; Overriding - identyczny, lub klasa pochodna typu zwracanego przez parenta.
  3. Overloaded - dowolna liczba wyjątków; Overriding - muszą dziedziczyć po parencie? Albo może nie rzucać wyjątków! Wpp błąd.
  4. Overloaded - dowolny access mofifier; Overloaded - przynajmnije taki jak parent. Wpp błąd.
Konstruktory
super(params), this(params) - możliwość wołania konstruktora klasy nadrzędnej i obecnej. Instrukcje te muszą pojawić się jako pierwsze w konstruktorze. Jeśli w konstruktorze klasy pochodnej nie ma super(params), to kompilator automatycznie wstawi tam wywołanie domyślnego konstruktora klasy nadrzędnej. Jeśli wołamy this(params), kompilator nie wstawia super, lecz odwleka to do wołanego konstruktora. Jeśli klasa dostarcza konstruktor z parametrami, Java nie dostarcza już konstruktora bezparametrowego. Jeśli zaś klasa nie ma zdefiniowanych konstruktorów, Java dostarcza domyślny bezargumentowy.

Klasy wewnętrzne
Klasy wewnętrzne mają dostęp do wszystkich atrybutów i metod (private) klasy zewnętrznej.

public static void main(String args[]) {
OuterOne.InnerOne i = new OuterOne().new InnerOne();
i.innerMethod();
}

jest tym samym co:

public static void main(String args[]) {
OuterOne o = new OuterOne();
OuterOne.InnerOne i = o.new InnerOne();
i.innerMethod();
}

W przypadku definicji statycznej klasy wewnętrznej, nie ma ona dostępu do nie-statycznych pól klasy obudowującej. Stworzenie klasy wewnętrznej nie wymaga w taki wypadku tworzenia klasy obudowującej:
MyInner aMyInner = new MyOuter.MyInner();

Klasy wewnątrz metod
  1. Klasy zadeklarowanie wewnątrz meotdy są prywatne i nie mogą mieć żadnego modyfikatora zakresu.
  2. Meotdy te mogą odwoływać się do zmiennych klasy (też prywatnych) oraz finalnych zmiennych używanych wewnątrz metody.

public void go(int x, final int y) {
int a = x + y;
final int b = x - y;
class MInner {
public void method() {
System.out.println(“m is “ + m);
// System.out.println(“x is “ + x); //Illegal!
System.out.println(“y is “ + y);
// System.out.println(“a is “ + a); //Illegal!
System.out.println(“b is “ + b);
}
}
MInner that = new MInner();
that.method();
}


Klasa anonimowa
  1. Może extends lub implements (nie oba na raz i tylko 1 interface) - nie używa się nawet słów kluczowych implements czy extends.
  2. Klasa anonimowa nie może posiadać zaimplementowanego przez siebie konstruktora, bo nie ma nazwy. Można jednak użyć nie domyślnego konstruktora:

Button b = new Button(“Anonymous Button“) {
...
};


Enumerations
  1. Można deklarować wtedy co klasy.
  2. Podczas kompilacji generowany jest plik .class z enum'em.
  3. Dziedziczy po Object.
  4. Może zawierać main().
  5. Jeśli enum posiada atrybut bez wartości w (), musi dostarczyć domyślny konstruktor.

enum Suit {
DIAMOND(true), HEART(true), CLUB(false), SPADE(false);

private boolean red;

Suit(boolean b) {
red = b;
}

public boolean isRed() {
return red;
}

public String toString() {
String s = name();
s += red ? ":red" : ":black";
return s;
}
}

Rozdział 5. Flow control, Exceptions, Assertions

Pętla for składa się z trzech części. Pierwsza - deklaracja albo przypisanie (nie 2 w jednym momencie). Deklaracja lub przypisanie może odtyczyć kilku zmiennych oddzielonych przecinkiem. Przecinkiem oddzielone mogą być także składniki trzeciej części pętli. Każda część pętli for jest opcjonalna.

mainLoop: for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
if (array[i][j] == ‘\u0000’) {
continue mainLoop;
}
}
}

mainLoop: for (i = 0, j = 0; i < 3; ++i, ++j) {
System.out.println("main loop");
for (int x = 0; x < 1; ++x) {
if (x == 0) {
System.out.println("x = 0");
break mainLoop;
}
}
}

W przypadku switch zmienna warunkowa musi być "assignment compatible" z int (czyli. byte, short, char lub int). Wyrażenie po slowie kluczowym "case" musi być wartością lub stałą.

Wyjątki

Jeśli metoda ma w deklaracji "throws", jej wywołanie musi być otoczone odpowiednim blokiem try-catch. Inaczej nastąpi błąd kompilacji. Gdy nie ma bloku catch dla danego wyjątku, JVM wypisuje stack trace i kończy działanie. Nie trzeba łapać, ani obsługiwać (throws) wyjątków dziedziczących po RuntimeException! Przy przeciązaniu funkcji, zadeklarowane wyjątki muszą dziedziczyć po wyjątku zadeklarowanym w funkcji pierwotnej.

Funkcje rozróżniane są przez nazwę oraz parametry, nie przez wartość zwracaną.

Blok finally jest zawsze wykonywany, nawet jak zaden z catch nie obsłużył wyjątku. Finally może się nie wykonać w przypadku śmierci wątku, System.exit() lub wyłączeniu komuptera.

Asercje

assert Expression1
assert Expression1:Expression2

Expression1 musi być boolowskie. Asercje trzeba włączyć w JVM (java -ea MyApp), jeśli są wyłączone nic nie robią. Jeśli Expression1 == true, żadna dalsza akcja nie jest wykonywana, jeśli jest false - rzucany jest wyjątek AssertionExcpetion oraz opcjonalnie dodawane do niego Expression2 jako wiadomość (toString).

Rozdział 4. Converting and Casting

Konwersja typów prostych:
  1. boolean nie może być rzutowany na inny typ.
  2. typ mniejszy może być niejawnie rzutowany na typ większy, np. pod zmienną double podstawiamy zmienna int.
  3. typ większy nie może być niejawnie rzutowany na typ mniejszy, np. double na int.
Legalne przypisanie: ((char)||(byte->short))->int->long->float->double
W Javie zmienne liczbowe są explicite tylko int albo double (float f = 1.234; // błąd kompilacji). Ale przypisanie liczby całkowitej do byte, short albo char nie powoduje błędu.
Przy przekazywaniu parametrów do wywołania metody następuje promocja argumentu. Jeśli typ wymagany jest mniejszy - błąd kompilacji.

Przy rzutowanie na typ większy (numeryczny) nie traci się wartości.

Promocja w przypadku działań:
1. Operatory unarne:
a) Jeżeli operand jest byte, short lub char, a operator to nie ++ ani --, to następuje promocja do int.
b) wpp nic się nie dzieje.
2. Operatory binarne (2 argumentowe)
a) Jeżeli jeden z operandów jest double, drugi konwertowany jest do double.
b) Jeśli jeden z opeandów to float, drugi konwersja do float.
c) long, long...
d) wpp oba do int.

Aby zawęzić wartość zmiennej, należy zastosować explicite cast. Nie można rzutować boolean na nic, ani nic na boolean.

Konwersja referencji:
  1. Konwersji podlegają referencje na obiekty, interfejsy i tablice.
  2. Interfejs może ulec konwersji na interfejs lub klasę. Jeśli jest to interfejs, to musi być to interfejs bazowy danego interfejsu.
  3. Klasa może ulec konwersji na klasę oraz interfejs. Klasa musi być klasą bazową, a interfejs musi być implementowany.
  4. Tablica może uleca konwersji na klasę Object, interfejs Cloneable lub Serializable, lub na tsblicę. Tylko konwersja tablicy zawierającej referencje do obiektów, na inną tablicę, gdzie typy obu obiektów moża konwertować, jest dozwolone.
Wszystko można przypisać do Object, nawet tablice.

TODO: Tabelka na stronie 148.

Nie można rzutować klasy finalnej na interfejs, ale każdą inną można - nie ma błędu kompilacji, lecz będzie Exception podczas runtime.

Rozdział 3. Modifiers

The Access Modifiers
  1. Klasy nie wewnętrzne mogą być tylko public.
  2. Metody public mogą być przeciążone przez klasy pochodne.
  3. Klasy pochodne nie mają dostępu do prywatnych zmiennych, metod, czy klas wewnętrznych.
  4. Dostęp domyślny - każdy obiekt znajdujący się w tym samym pakiecie ma dostęp do danego atrybutu.
  5. Tylko zmienne i metody mogą być protected. Do obiektów protected mają dostęp wszystkie obiekty z tego samego pakietu, jak i klasy, które dziedziczą po danym obiekcie, a są w innym pakiecie.
  6. Obiekty o niższej dostępności mogą być przeciążane przez klasy pochodne, jeśli ten nadają taką samą dostępność lub większą. Dla przykładu, metoda private void xxx(), może zostać przeciążona w klasie pochodnej na public void xxx().
Final powoduje, że metod nie można przeciążać, a referencja (nie obiekt) musi pozostać stała. W szczególności finalny typ prosty nie może zostać zmieniony. Finalna metoda musi posiadać ciało.

Klasa musi zostać zadeklarowana jako abstrakcyjna gdy posiada jedną lub więcej abstrakcyjnych metod, klasa dziedzicz jakąś abstrakcyjną metodą z innej klasy, dla której nie udostępnia implementacji; lub klasa implementuje interfejs, lecznie nie zawiera implementacji dla każdej z jego metod. Klasa nie może być abstrakcyjna i finalna, ale abstrakcyjna klasa może posiadać finalne metody. Metoda nie może być jednocześnie finalna i abstrakcyjna.

W metodach static nie można uzywać this. Statycznych metod nie można przeciążyć jako nonstatic. Kod wewnątrz static {} wywoływany jest dokładnie raz kiedy klasa jest ładowana. Zmienne public, protected i default mogą być static import do klas w obrębie tego samego pakietu. Do innych pakietów, mogą być tylko pubic.

Native może dotyczyc tylko metody.

Volatile - zmienna może być dostępna asynchronicznie, istotne w środowiskach wieloprocesorowych.

TODO: Nauczyć tabelki na stronie 124.

Rozdział 2. Operators and assignments

Java Operators

Kolejność operatorów (od największego, do najniższego priorytetu):
  1. Unarne: ++, --, +, -, !, ~, (type)
  2. Arytmetyczne (1): *, /, %
  3. Arytmetyczne (2): +, -
  4. Shift: <<, >>, >>>
  5. Porównania (1): <, <=, >, >=, instanceof
  6. Porównania (2): ==, !=
  7. Bitowe: &, ^, |
  8. Logiczne: &&, ||
  9. Warunkowe: ?:
  10. Przypisania: = op=
Każdy punkt zawiera zbiór operatorów o tym samym priorytecie. Ewaluacja wyrażenia następuje od lewej do prawej, lecz kolejność wykonywania działań zależy od ich priorytetu. Przykład:

int [] a = { 4, 4 };
int b = 1; a[b] = b = 0;

Jako wynik ewaluacji pierwszego członu ostatniego wyrażenia, a[b] wskazywać będzie na ostatni element w tabeli a. Kolejność poszczególnych przypisań wykonywana będzie od prawej do lewej. Zmiennej b przypisana zostanie wartość 0, a następnie ostatniemu elementowi tablicy a aktualna wartość zmiennej b (0).

Maszyna wirtualna Javy przechowuje bitową reprezentację zmiennych w takiej samej postaci na architekturach Little i Big Endian. Operator ~ polega na zamianie wszystkich bitów o wartości 0 na 1, a 1 na 0.

Operatory arytmetyczne

Operatory * i / można stosować dla typów numerycznych oraz char. Zawsze lepiej najpierw mnożyć, a potem dzielić (overflow i underflow - strona 44). Zastosowanie najpierw dzielenia, a następnie mnożenia daje inny wynik, zazwyczaj kompletnie błędny. Dzielenie przez 0 daje ArithmeticException.

Operator modulo

Jeśli drugim argumentem operatora % jest 0, operacja rzuca ArithmeticException. Postępowanie w przypadku operacji modulo jest następujące:
  1. Redukuj wartość pierwszego argumentu o drugi, tak żeby dążyła do 0. W przypadku dodatniej wartości pierwszego argumentu - odejmuj, w przypadku ujemnej - dodawaj.
  2. Jeśli warotść bezwzględna otrzymanej liczby jest mniejsza od wartości drugiego argumentu - jest to wynik. O znaku wyniku decyduje punkt 3.
  3. Wynik jest ujemny jeśli pierwszy argument jest ujemny. Znak drugiego argumentu nie ma znaczenia.
Operator +

Błędny wynik operacji arytmetycznych (overflow i underflow) nie skutkuje żadnym wyjątkiem.
Operator + zastosowany do typów numerycznych daje wynik, który:
1. Jest typem numerycznym.
2. Jest przynajmniej typu int, co wynika z promocji.
3. Jest typu przynajmniej tak dużego jak większy z operandów.
4. Wynik obliczany jest po ówczesnej promocji operandów.

Operator + dla typów nie numerycznych:
1. Przynajmniej jeden z operandów musi być String'iem lub literałem. W przeciwnym wypadku działanie jest nielegalne.
2. Nie String'i konwertowane są do String'u za pomocą metody toString().

Błędy arytmetyczne

Typ numeryczny podzielony lub modulo 0 skutkuje błędem ArithmeticException. Wszystkie porónania z Float lub Double NaN dają fałsz. Należy skorzystać z metody isNaN().

Promocja operandów arytmetycznych

Przed wykonaniem działania operandy uzyskają promocje przynajmniej do typu int.

Operatory logiczne

Podczas porównania mniejszy typ uzyskuje promocje do większego, np. char do float.

Operator Instanceof

Lewy operand może być referencją do obiektu, prawy zaś klasą, interfejsem lub typem tablicowym. Przy typie tablicowym sprawdzane jest czy dana referencja jest tablicą, jak i czy obiekty są prawidłwoego typu, np.:
x instanceof Employee[]
Jeśli lewy operand instanceof jest null, zawsze zwracany jest fałsz.

Inne fakty

1. Na int można robić operacje na bajtach, np. ~
2. Nie można porównywać obiektów różnych typów, np. int ze String.
3. W przypadku operatora ?: także występuje promocja operandów. xxx ? 99.0 : 9 to tak naprawdę xxx ? 99.0 : 9.0

Rozdział 1. Language Fundaments.

Plik źródłowy

Każdy plik źródłowy z rozszerzeniem *.java może zawierać najwyżej jedną definicje klasy publicznej, której nazwa powinna być taka sama jak nazwa pliku. Plik źródłowy składa się z (kolejno) definicji pakietu, importowanych pakietów oraz definicji klas, interfejsów czy obiektów enum. Plik źródłowy musi obowiązkowo znajdować się w strukturze katalogowej odpowiadającej jego pakiecie. Nazwa pakietu składa się tylko ze znaków alfanumerycznych. W przypadku kilku tak samo nazywających się klas, próba użycia w kodzie niejednoznacznej nazwy generuje błąd kompilacji.

Słowa kluczowe

Lista słów zarezerwowanych przez język Java: abstract, assert, boolean, break, byte, case, catch, char, class, const, continue, default, do, double, else, enum, extends, false, final, finally, float, for, goto, if, implements, import, instanceof, int, interface, long, native, new, null, package, private, protected, public, return, short, static, srtictfp, super, switch, synchronized, this, throw, throws, transient, true, try, void, volatile, while.

Nazwa (rozróżniane wielkością liter) musi zaczynać się od litery, dolara ($) lub podkreślenia (_), a potem mogą występować także cyfry. Klasy Float oraz Double zawierają stałe NaN (Not a Number), NEGATIVE_INFINITY oraz POSITIVE_INFINITY. Każde porównanie zawierające NaN zwraca fałsz: http://www.concentric.net/~Ttwang/tech/javafloat.htm.

Literały

Zmiennej typu char można przypisać wartość z klawiatury, lub z systemu Unicode poprzedzony prefixem \u, np. char c = '\u4567';. Zmienne numeryczne mogą być inicjowane w kodzie dziesiętnie, ósemkowo (prefiks 0) oraz szesnastkowo (prefiks 0x, dowolna wielkość x oraz liter). Zwyczajowo zmienna liczbowa jest typu int. Jeśli chcemy użyć long, należy dodać sufiks L lub l. Domyślny typ zmiennoprzecinkowy to double. Aby stosować typ zmiennoprzecinkowy powinien on przyjmować jedną z dostępnych reprezentacji: 1.23, 1.23E+10, 1.23F lub 1.23D (E, F i D mogą być pisane z małej litery).

Tablice

Wszystkie elementy tablicy muszą być tego samego typu (możliwość przechowywania klas pochodnych). Nawiasy mogą występować zarówno przed jak i po nazwie zmiennej tablicowej czy deklaracji argumentu funkcji lub wartości zwracanej. Poniższe deklaracje są poprawne:

  • int tab[];
  • int[] tab;
  • myMethod(int tab[])
  • int myMethod()[]

Podczas tworzenia tablicy, jest ona wypełniana wartościami zerowymi (byte 0, char '\u0000', obiekt null, boolean false). Poprawna inicjalizacja: double tab[] = {0, 9.8, 7d};. Wielowymiarowe tablice mogą mieć różną wielkość. Najbardziej zewnętrzna tablica zawiera jedynie wskaźniki na resztę tablic.

Import

Java 5.0 wprowadza statyczne importowanie (import static). Przykład: import java.awt.Color powoduje konieczność odwołania się przez Color.RED, a import static java.awt.Color.RED umożliwia po prostu RED. Przy statycznym imporcie można używać też '*'. Można importować statyczne metody: import static edu.lantoniak.myMethod(), ale nie podaje się listy argumentów (importowane są wszystkie funkcje o danej nazwie). Import wydłuża czas kompilacji, ale nie ma wpływu na czas ładowania klasy.

Klasy

class Test { int x = 1; public Test() {} } W tym przypadku zmienna 'x' przyjmuje wartość 1 przed uruchomieniem konstruktora. Statyczne zmienne klas inicjalizowane są przy ładowaniu klasy. Każda zmienna automatyczna (nie tablica) musi zostać zainicjowana przed jej użyciem, w innym wypadku otrzymamy błąd kompilacji, np.:

public int zle1() {
int i;
return i+1;
}

public int zle2(int x) {
int res;
if (x == 0) {
res = 1;
}
return res;
}

Domyślna inicjalizacja wykonywana jest w przypadku member variables, class variables i tablic. Zmienne proste przekazywane są "przez wartość". Tablice oraz obiekty przez referencje. Zmiana obiektu referowanego wewnątrz metody wołanej ma skutki na dany obiekt.

Button btn;
btn = new Button("Pink");
replacerBad(btn);
System.out.println(btn.getLabel()); // Pink!
replacerGood(btn);
System.out.println(btn.getLabel()); // Blue!

public void replacerBad(Button replaceMe) {
replaceMe = new Button("Blue");
}

public void replacerGood(Button replaceMe) {
replaceMe.setLabel("Blue");
}

finalize() destruktor klasy. Wszystkie argumenty przekazywane są przez wartość (adres referencji, podwójna referencja). Gdy wartość referencji wynosi null dany obiekt w pamięci widoczny jest dla GC.