Notatki z Javy (SCJP), cz. 2
-- Sebastian Pawlak, 2006.
Math Pakiet java.lang definiuje fundamentalne dla Javy klasy, z tego względu jest on importowany automatycznie. Pakiet definiuje wrappery dla typów prymitywnych: Boolean, Byte, Character, Double, Float, Integer, Long, Short, Void, Object. java.lang zawiera klasę Math, która jest używana podczas wykonywania podstawowych operacji matematycznych. Klasa Math definiuje m.in.: public final static double Math.PI public final static double Math.E Wszystkie metody klasy Math są statyczne, zatem nie trzeba tworzyć obiektu, aby je wywołać. Nawet gdyby chcieć stworzyć instancję klasy Math, jest to niemożliwe, ponieważ konstruktor jest prywatny. Nie można też dziedziczyć po klasie Math, ponieważ jest ona "final". abs() x = Math.abs(99); // 99 x = Math.abs(-99); // 99 abs() może zwrócić wartość ujemną dla minimalnej wartości: System.out.println(Math.abs(Integer.MIN_VALUE)); // -2147483648 public static int abs(int a); public static long abs(long a); public static float abs(float a); public static double abs(double a); ceil() - zaokrąglanie w górę (sufit) Math.ceil(9.0); // 9.0 Math.ceil(8.8); // 9.0 Math.ceil(8.02); // 9.0 Math.ceil(-9.0); // -9.0 Math.ceil(-9.4); // -9.0 Math.ceil(-9.8); // -9.0 public static double ceil(double a); Działa tylko dla double - nie jest przeciążona. floor() - zaokrąglanie w dół (podłoga) Math.floor(9.0); // 9.0 Math.floor(9.4); // 9.0 Math.floor(9.8); // 9.0 Math.floor(-9.0); // -9.0 Math.floor(-8.8); // -9.0 Math.floor(-8.1); // -9.0 public static double floor(double a); Działa tylko dla double - nie jest przeciążona. max() x = Math.max(1024, -5000); // 1024 public static int max(int a, int b); public static long(long a, long b); public static float max(float a, float b); public static double max(double a, double b); min() x = Math.min(0.5, 0.0); // 0.0 public static int min(int a, int b); public static long min(long a, long b); public static float min(float a, float b); public static double min(double a, double b); ---- public class Main { public static void main(String[] args) { double[] tab = { 10.5, -10.5, Math.PI, 0 }; for (int i = 0; i < tab.length; i++) System.out.println(Math.abs(tab[i]) + " " + Math.ceil(tab[i]) + " " + Math.floor(tab[i])); } } // 10.5 11.0 10.0 // 10.5 -10.0 -11.0 // 3.14 4.0 3.0 // 0.0 0.0 0.0 ---- random() -- zwraca losową wartość 0.0 <= x < 1.0 nie pobiera żadnych argumentów public static double random(); round() Math.round(-10.5); // -10 Math.round(10.5); // 11 Math.round(10.1); // 10 Math.round(10.7); // 11 public static int round(float a); public static long round(double a); sin() Math.sin(Math.toRadians(90.0)); // 1.0 public static double sin(double a); cos() Math.cos(Math.toRadians(0.0)); // 1.0 public static double cos(double a); tan() Math.tan(Math.toRadians(45.0)); // 1.0 public static double tan(double a); sqrt() Math.sqrt(9.0); // 3.0 Math.sqrt(-9.0); // NaN - Not a Number public static double sqrt(double a); toDegrees() -- radiany na kąt w stopniach Math.toDegrees(Math.PI * 2.0); // 360.0 public static double toDegrees(double a); toRadians() -- kąt w stopniach na radiany Math.toRadians(360.0); // 6.283185, czyli 2 * Math.PI public static double toRadians(double a); ---- double d; float pi_i = Float.POSITIVE_INFINITY; double n_i = Double.NEGATIVE_INFINITY; double notanum = Double.NaN; System.out.println(Double.POSITIVE_INFINITY + " " + Double.MAX_VALUE); // Infinity 1.7976931348623157E308 if (notanum != notanum) // NaN nie jest równy niczemu, nawet samemu sobie ! System.out.println("NaNs not equal"); if (Double.isNaN(notanum)) System.out.println("got a NaN"); d = Math.sqrt(n_i); if (Double.isNaN(d)) System.out.println("got sqrt NaN"); // got sqrt NaN System.out.println(Math.sqrt(-16d)); // NaN System.out.println(16d / 0.0); // Infinity System.out.println(16d / -0.0); // -Infinity Dzielenie przez zero działa tylko dla float i double. Dla typów całkowitych powoduje ArithmeticException. System.out.println("abs(-0) = " + Math.abs(-0)); // abs(-0) = 0 Typy prymitywne od najwęższego do najszerszego: byte, short, int, long, float, double. Typ węższy może być rzutowany niejawnie na typ większy. Podobnie jak w przypadku klasy String, obiekty klas opakowujących (wrappers) typy prymitywne są niezmienne. Klasy opakowujące są finalne - nie można po nich dziedziczyć. Typ prymitywny | Wrapper Class | Argumenty konstruktora ---------------------+-------------------------+----------------------- boolean | Boolean | boolean, String byte | Byte | byte, String char | Character | char double | Double | double, String float | Float | float, String int | Integer | int, String long | Long | long, String short | Short | short, String ---------------------+-------------------------+------------------------ Integer i1 = new Integer(42); Integer i2 = new Integer("42"); Float f1 = new Float(3.14f); Float f2 = new Float("3.14f"); Character c1 = new Character('c'); // Character ma jedynie konstruktor pobierający char Boolean b = new Boolean("false"); if (b) // nie skompiluje się, gdyż w tym miejscu powinien być typ boolean, a nie Boolean valueOf() -- metoda statyczna dostarczana przez większość klas opakowujących Integer i2 = Integer.valueOf("101011", 2); // konwertuje 101011 na 43 Float f2 = Float.valueOf("3.14f"); XXXalue() -- każda z sześciu klas opakowujących posiada sześć metod konwersji Integer i2 = new Integer(42); byte b = i2.byteValue(); short s = i2.shortValue(); double d = i2.doubleValue(); Float f2 = new Float(3.14f); short s = f2.shortValue(); System.out.println(s); // 3 parseXXX() -- zwraca typ prymitywny double d4 = Double.parseDouble("3.14"); System.out.println("d4 = " + d4); // d4 = 3.14 Double d5 = Double.valueOf("3.14"); System.out.println(d5 instanceof Double); // true long l2 = Long.parseLong("101010", 2); System.out.println("L2 = " + L2); // L2 = 42 Long l3 = Long.valueOf("101010", 2); System.out.println("L3 value = " + L3); // L3 = 42 toString() Każda klasa opakowująca ma bezargumentową, niestatyczną, instancyjną wersję metody toString(). Double d = new Double("3.14"); System.out.println("d = " + d.toString()); // d = 3.14 Każda numeryczna klasa opakowująca posiada przeciążoną statyczną metodę toString(), która bierze jako argument numeryczny typ prymitywny ( Double.toString(double), Long.toString(long) ). System.out.println("d = " + Double.toString(3.14)); // d = 3.14 Integer i Long posiadają trzecią metodę toString(), która jest statyczna i ma argumenty: wartość typu prymitywnego, radix. Metoda pobiera pierwszy argument (zakłada się, że jest on w systemie dziesiętnym) i konwertuje go do systemu przy podstawie określonej przez radix, a następnie zwraca String. System.out.println("hex = " + Long.toString(254, 16); // hex = fe toBinaryString(), toHexString(), toOctalString() String s3 = Integer.toHexString(254); System.out.println("254 in hex = " + s3); // 254 in hex = fe String s4 = Long.toOctalString(254); System.out.println("254 in octal = " + s4); // 254 in octal = 376 Metoda | Boolean | Byte | Character | Double | Float | Integer | Long | Short s = static | | | | | | | | n = NFE exception | | | | | | | | ---------------------+---------+------+-----------+--------+-------+---------+------+------- byteValue | | + | | + | + | + | + | + | | | | | | | | doubleValue | | + | | + | + | + | + | + | | | | | | | | floatValue | | + | | + | + | + | + | + | | | | | | | | intValue | | + | | + | + | + | + | + | | | | | | | | longValue | | + | | + | + | + | + | + | | | | | | | | shortValue | | + | | + | + | + | + | + ---------------------+---------+------+-----------+--------+-------+---------+------+------- parseXxx s,n | | + | | + | + | + | + | + | | | | | | | | parseXxx s,n | | + | | | | + | + | + (with radix) | | | | | | | | ---------------------+---------+------+-----------+--------+-------+---------+------+------- valueOf s,n | + | + | | + | + | + | + | + | | | | | | | | valueOf s,n | | + | | | | + | + | + (with radix) | | | | | | | | ---------------------+---------+------+-----------+--------+-------+---------+------+------- toString | + | + | + | + | + | + | + | + | | | | | | | | toString s | | + | | + | + | + | + | + (primitive) | | | | | | | | | | | | | | | | toString s | | | | | | + | + | (primitive, radix) | | | | | | | | ---------------------+---------+------+-----------+--------+-------+---------+------+------- toBinaryString s | | | | | | + | + | | | | | | | | | toHexString s | | | | | | + | + | | | | | | | | | toOctalString s | | | | | | + | + | ---------------------+---------+------+-----------+--------+-------+---------+------+------- NFE = Number Format Exception ==, equals() Używając == do porównania dwóch zmiennych referencyjnych, w istocie porównujemy, czy te dwie zmienne wskazują na ten sam obiekt. W Object equals() zachowuje się tak samo jak ==, czyli porównuje referencje. Porównując zmienne referencyjne za pomocą == można porównywać jedynie zmienne referencyjne, które odnoszą się do obiektów, które są tej samej klasy bądź w tej samej hierarchii dziedziczenia klas. Próba porównania obiektów znajdujących się w różnych hierarchiach klas zakończy się błędem kompilacji. String x1 = "abc"; String x2 = "ab"; x2 = x2 + "c"; if (x1 != x2) system.out.println("different objects"); if (x1.equals(x2)) System.out.println("same values"); ************************************************************************************ * Metody equals() zawarte w klasach opakowujących zwracają true wyłącznie wtedy, * * gdy zarówno porównywane wartości prymitywne są równe i klasy opakowujące * * są te same! * ************************************************************************************ Double d1 = new Double("3.0"); Integer i1 = new Integer(3); if (d1.equals(i1)) System.out.println("wraps are equal"); // brak wyjścia ; klasy są różne! Double d2 = d1.valueOf("3.0d"); if (d1.equals(d2)) System.out.println("Doubles are equal"); // Doubles are equal equals(): 1. equals() jest używana wyłącznie do porównywania obiektów. 2. equals() zwraca typ boolean. 3. StringBuffer nie posiada nadpisanej metody equals() - pozostawia ją w postaci, jaka była w Object. Oznacza to, że klasa StringBuffer nie ma wbudowanej metody, która pozwala powównać łańcuchy znaków zawarte w dwóch obiektach typu StringBuffer. 4. String i klasy opakowujące typy prymitywne są final i mają nadpisane metody equals(). StringBuffer s1 = new StringBuffer("ala"); StringBuffer s2 = new StringBuffer("ala"); if (s1.equals(s2)) System.out.print("ok"); // brak wyjścia StringBuffer s1 = new StringBuffer("ala"); StringBuffer s2 = s1; if (s1.equals(s2)) System.out.print("ok"); // ok ---- String s = "42"; try { s = s.concat(".5"); double d = Double.parseDouble(s); s = Double.toString(d); int x = (int)Math.ceil(Double.valueOf(s).doubleValue()); System.out.println(x); // 43 } catch (NumberFormatException e) { System.out.println("bad number"); } ************************** Nadpisywanie metody toString() public class HardToRead { public static void main(String[] args) { HardToRead h = new HardToRead(); System.out.println(h); // HardToRead@a47e0 <- nazwa klasy // i unsigned hex hashcode obiektu } } ---- class Romek { public String toString() { return "Jestem wesoły Romek!"; } } public class Main { public static void main(String[] args) { System.out.println(new Romek()); // Jestem wesoły Romek! } } ---- Należy przeciążyć metodę equals(), aby móc użyć danego obiektu jako klucza w hashtable. Jeśli equals() nie zostanie nadpisana, będzie ona używać operatora == do porównań. Zatem po wstawieniu obiektu np. do HashMap jako klucza, potrzebowalibyśmy dokładnie tego samego obiektu (tej samej instancji), aby wydobyć wartość skojarzoną z kluczem. Z tego względu, należy nadpisać metodę equals(), aby porównywała obiekty na podstawie ich istotnych atrybutów (tak, aby można było stwierdzić, że dwie różne instancje obiektu są takie same), a nie czy referencje wskazują na ten sam obiekt. public class EqualsTest { public static void main(String[] args) { Moof one = new Moof(8); Moof two = new Moof(8); if (one.equals(two)) System.out.println("one and two are equals"); } } class Moof { private int moofValue; Moof(int val) { moofValue = val; } public int getMoofValue() { return moofValue; } public boolean equals(Object o) { if ((o instanceof Moof) && (((Moof)o).getMoofValue() == this.moofValue)) return true; else return false; } } ---- equals(), hashCode(), toString() są public. class Foo { boolean equals(Object o) { } // błąd - equals() powinno być public } class Boo { public boolean equals(Boo b) { } // błąd - mamy do czynienia z przeciążeniem, // a nie nadpisaniem metody } ---- Istnieje zestaw zasad (contract) specyfikujący, jak powinna wyglądać implementacja metody equals(). The equals() Contract: 1. Jest zwrotny (reflexive): x.equals(x) powinno zwracać true. 2. Jest symetryczny: x.equals(y) powinno zwracać true, wyłącznie wtedy, gdy y.equals(x) zwraca true. 3. Jest przechodzni (transitive): jeśli x.equals(y) zwraca true i y.equals(z) zwraca true, to x.equals(z) powinno zwracać true. 4. Jest konsekwentny: wielokrotne wywołanie x.equals(y) powinno konsekwentnie za każdym razem zwracać tę samą wartość - przy założeniu, że w między czasie stan obiektu nie zmienił stanu. 5. dla każdej referencji niebędącej null, x.equals(null) powinno zwracać false. Jeśli dwa obiekty są równe (equals() zwraca true), to muszą one mieć identyczne wartości hashcode. Zasada praktyczna: jeśli następuje przesłonięcie metody equals(), należy przesłonić także metodę hashCode(). Metoda equals() na ogół porównuje wartości zmiennych instancyjnych, aby określić, czy obiekty są równe. Metoda hashCode() powinna posługiwać się tymi samymi zmiennymi. class Hash { private int x; Hash(int x) { this.x = x; } public boolean equals(Object o) { Hash h = (Hash)o; // normalnie powinno następować wcześniej sprawdzenie instanceof if (h.x == this.x) return true; else return false; } public int hashCode() { return x * 17; } } ---- public int hashCode() { return 1492; } Powyższa implementacja zwraca taką samą wartość dla wszystkich instancji, niezależnie od tego, czy są one równe, czy nie. Mimo to, jest to wciąż legalne (legal) i nawet właściwa (appropriate) metoda hashCode(), gdyż nie łamie zasad (contract) implementacji określonych w specyfikacji. Dwa obiekty z "x" równym 8 będą miały takie same hashcode'y. Ta metoda jest ekstremalnie nieefektywna - wszystkie obiekty wylądują w tym samym miejscu. Jakkolwiek jest ona poprawna i nie łamie zasad. Contract wymaga jedynie, aby dwa równe obiekty (które mają np. wartości pewnych zmiennych równe) miały takie same hashcode'y. The hashCode() Contract: 1. Wielokrotne uruchomienie hashCode() dla tego samego obiektu musi zwracać tę samą wartość integer. Wartość musi być stała podczas działania programu, ale może być inna pomiędzy różnymi uruchomieniami. 2. Jeśli dwa obiekty są równe (equals() zwraca true), wtedy hashCode() dla każdego z tych obiektów musi mieć tę samą wartość. 3. Nie jest wymagane, że jeśli dwa obiekty nie są równe, muszą mieć inne wartości zwracane przez hashCode(). Jakkolwiek dla większej efektywności, różne obiekty powinny mieć inne hashcode'y. Stan | Wymagane | Nie wymagane (ale dozwolone) --------------------------------+------------------------------+-------------------------------- x.equals(y) == true | x.hashCode() == y.hashCode() | | | x.hashCode() == y.hashCode() | | x.equals(y) == true | | x.equals(y) == false | | brak wymagań dla hashCode() | | x.hashCode() != y.hashCode() | x.equals(y) == false | --------------------------------+------------------------------+-------------------------------- W hashCode() nie powinno się używać zmiennych "transient", gdyż nie są one zapisywane podczas serializacji i może być problem z odnalezieniem potem obiektu w kolekcji. ---- Kolekcje The core interfaces: Collection, Set, Sorted Set, List, Map, Sorted Map The core concrete implementation classes: Map implementations: HashMap, Hashtable, TreeMap, LinkedHashMap (uwaga: Hashtable, a nie HashTable) Set implementations: HashSet, LinkedHashSet, TreeSet List implementations: ArrayList, Vector, LinkedList Nie wszystkie kolekcje (collections) w Collections Framework implementują interfejs Collection. Żadna z klas i interfejsów Map* nie implementuje Collection - choć SortedMap, Hashtable, HashMap, TreeMap, LinkedHashMap należą do kolekcji (collections), nie dziedziczą po Collection. java.util.Collection - interfejs, który rozszerza Set i List. java.util.Collections - klasa, która posiada statyczne metody operujące na kolekcjach (np. add, remove, contains, size, iterator). dz. - dziedziczenie imp. - implementacja Collection dz. / \ dz. ------------------- ------------------- / \ Set List imp. / | \ dz. / | \ ----------- | ---------- / | \ / | \ / | \ / | SortedSet --- | --- / imp.| | imp./ imp.| \imp. / | |imp. / | \ / | | / | \ HashSet LinkedHashSet TreeSet LinkedList Vector ArrayList Map /| |\ / | | \ dz. ----------- | | ----------- / / \ \ / / \ \ imp./ imp./ \imp. SortedMap / / \ \ / / \ \imp. / / \ \ Hashtable LinkedHashMap HashMap TreeMap List - lista obiektów Set - lista unikalnych obiektów Map - Obiekty z unikalnym ID Każdy z tych trzech typów może być: Sorted, Unsorted, Ordered, Unordered. Klasy implementujące mogą być: unsorted & unordered, ordered but unsorted, ordered & sorted. Klasy implementujące nigdy nie są: sorted but unordered. Ordered ------- Kolekcja jest ordered, jeśli możliwa jest iteracja przez kolekcję w pewnej (nielosowej) kolejności. Hashtable nie jest ordered, gdyż kolejność elementów przy iteracji jest nieprzewidywalna. ArrayList jest ordered, gdyż kolejność oparta jest na indeksie elementów. LinkedHashSet jest ordered, gdyż posiada kolejność zależną od kolejności wstawiania elementów. Są kolekcje, w których kolejność (order) odnosi się do tzw. natural order - takie kolekcje są zarówno ordered i sorted. Natural order: - dla kolekcji obiektów String, natural order to porządek alfabetyczny, - dla kolekcji obiektów Integer, natural order to porządek według wartości numerycznych, - dla kolekcji obiektów Foo, natural order musi zostać zdefiniowany przez programistę. Sorted ------ Kolekcja sorted oznacza kolekcję "sorted by natural order". Natural order is defined by the class of the objects being sorted. ---- // A LinkedHashSet, ordered by order of insertion import java.util.*; public class Ordered { public static void main(String[] args) { LinkedHashSet lhs = new LinkedHashSet(); lhs.add("Chicago"); lhs.add("Detroit"); lhs.add("Atlanta"); lhs.add("Denver"); lhs.add("Denver"); Iterator it = lhs.iterator(); while (it.hasNext()) System.out.println("city " + it.next()); // city Chicago // city Detroit // city Atlanta // city Denver } } ---- // A TreeSet, sorted alphabetically import java.util.*; public class Sorted { public static void main(String[] args) { TreeSet ts = new TreeSet(); ts.add("Chicago"); ts.add("Detroit"); ts.add("Atlanta"); ts.add("Denver"); ts.add("Denver"); Iterator it = ts.iterator(); while (it.hasNext()) System.out.println("city " + it.next()); // city Atlanta // city Chicago // city Denver // city Detroit } } ---- List ---- Lista umożliwia operowanie na indeksach. Posiada m.in. metody: get(int index), indexOf(), add(int index, Object obj). Wszystkie trzy implementacje List są ordered pod względem indeksu. ArrayList --------- O ArrayList można myśleć jako o rosnącej tablicy. ArrayList umożliwia szybką iterację i szybki swobodny dostęp. Jest to kolekcja ordered (poprzez indeks), ale nie jest sorted. Od Javy 1.4 ArrayList implementuje interfejs RandomAccess - tzw. marked interface (bez metod), aby zaznaczyć, że lista posiada szybki (w czasie stałym) dostęp. ArrayList należy wybrać, a odrzucić LinkedList, jeśli potrzbujemy szybkiego iteratora, ale szybkość operacji wstawiania i usuwania elementów jest mniej istotna. Vector ------ Vector jest podobny do ArrayList. Vector posiada synchronizowane metody (bezpieczne dla używania z wątkami), które wpływają negatywnie na wydajność. Klasy Vector i ArrayList są jedynymi, które umożliwiają swobodny (RandomAccess) dostęp do elementów. LinkedList ---------- LinkedList jest ordered pod względem indeksu. Elementy są podwójnie linkowane do innych. To linkowanie oraz nowe metody dodawania i usuwania elementów z początku i końca sprawiają, że LinkedList jest dobrym wyborem do implementacji stosu bądź kolejki. LinkedList ma wolniejszy iterator niż ArrayList, ale szybsze wstawianie i usuwanie elementów. Set --- Set nie pozwala na duplikowanie elementów. Porównywanie elementów odbywa się za pomocą metody equals(). HashSet ------- HashSet jest unsorted i unordered. Używa wartości zwracanej przez hashCode(). Nie pozwala na duplikaty elementów. LinkedHashSet ------------- Jest to wersja ordered klasy HashSet, która posiada podwójne linkowanie do pozostałych elementów. LinkedHashSet pozwala na iterację w kolejności wstawiania elementów, bądź opcjonalnie w kolejności ostatnich dostępów do elementów. LinkedHashSet jest nowością w Javie 1.4. Kolekcja ta używana jest dla celów cache'ujących - FIFO. TreeSet ------- TreeSet i TreeMap są jedynymi dwiema kolekcjami sorted. Używa drzew czerwono-czarnych i gwarantuje, że elementy będą w idącym w górę porządku (order), zgodnym z natural order elementów. Można także wprowadzić własne zasady dla natural order. Map --- Za pomocą Map mapuje się unikatowy klucz (key, ID) na wartość (value). Klucz i wartość są obiektami. Klucze opierają się na hashcode'ach. Można: wyszukiwać wartości bazując na kluczu, zapytać o kolekcję o danej wartości, zapytać o kolekcję o danym kluczu. HashMap ------- HashMap to unsorted i unordered Map. HashMap pozwala mieć jeden klucz null i wiele wartości null w kolekcji. Hashtable --------- Hashtable jest synchronicznym odpowiednikiem HashMap. Hashtable nie pozwala na posiadanie czegokolwiek o wartości null. LinkedHashMap ------------- Podobnie jak LinkedHashSet, LinkedHashMap posiada porządek (order) według kolejności wstawiania elementów (opcjonalnie, access order). LinkedHashMap posiada wolniejsze wstawianie i usuwanie elementów, niż HashMap. LinkedHashMap posiada szybszy iterator niż HashMap. TreeMap ------- TreeMap jest sorted. Można wprowadzić własną metodę porównując, aby określić reguły porządku naturalnego. ---- Map nie może być klasą kolekcji, gdyż Map jest interfejsem, a nie konkretną implementacją klasy. Należy zwrócić uwagę na podstępne pytania w tym zakresie. Klasa Map Set List Ordered Sorted --------------------------------------------------------------------------------------------- HashMap + Nie Nie Hashtable + Nie Nie TreeMap + Sorted Natural order bądź własne reguły LinkedHashMap + insertion order Nie last access order HashSet + Nie Nie TreeSet + Sorted Natural order bądź własne reguły LinkedHashSet + insertion order Nie last access order ArrayList + Przez indeks Nie Vector + Przez indeks Nie LinkedList + Przez indeks Nie ---------------------------------------------------------------------------------------------- ArrayList: szybka iteracja i szybki swobodny dostęp; wolniejsze niż w liście wstawianie i usuwanie elementów. Vector: Wolniejszy niż ArrayList, głównie z powodu metod synchronized. LinkedList: Przydatne, gdy trzeba dodawać elementy na koniec, np. w stosach i kolejkach. HashSet: Brak duplikatów; brak porządku (order). LinkedHashSet: Brak duplikatów; iteracja według kolejności wstawiania bądź ostatniego dostępu (nowość w Java 1.4). TreeSet: Brak duplikatów; iteracja w natural sorted order. HashMap: Najszybszy update (klucz/wartość); zezwala na jeden klucz null i wiele wartości null. Hashtable: Jak wolniejszy HashMap z powodu metod synchronicznych. Nie zezwala na klucze i wartości null. LinkedHashMap: Szybszy iterator; iteracja według porządku wstawiania bądź ostatniego dostępu; zezwala na jeden klucz null i wiele wartości null (nowość w Java 1.4). TreeMap: Sorted map; natural order. Garbage Collector ----------------- Garbage Collector zajmuje się obiektami tworzonymi na stercie (heap, garbage collectible heap). Zadaniem Garbage Collectora jest odnajdowanie i usuwanie obiektów, które nie są osiągalne (cannot be reached), czyli takich, do których nie istnieją referencje. Object eligile for garbage collection - obiekt, który może zostać usunięty przez garbage collector. Obiekt może zostać usunięty przez garbage collector, kiedy żaden żyjący wątek nie ma do niego dostępu. Aplikacja napisana w Javie może mieć stworzonych na tyle dużo żyjących obiektów, że wyczerpią się zasoby pamięci. StringBuffer sb = new StringBuffer("hello"); System.out.println(sb); // The StringBuffer object is not eligible for collection sb = null; // Now the StringBuffer object is eligible for collection To że obiekt nie jest używany, nie oznacza, że jest eligible for collection. Dopiero brak referencji do obiektu oznacza, że jest on eligible for collection. ---- StringBuffer s1 = new StringBuffer("hello"); StringBuffer s2 = new StringBuffer("goodbye"); System.out.println(s1); // At this point the StringBuffer "hello" is not eligible s1 = s2; // Now the StringBuffer "hello" is eligible for collection ---- Isolating a Reference (island of objects) public class Island { Island i; public static void main(String[] args) { Island i2 = new Island(); Island i3 = new Island(); Island i4 = new Island(); i2.i = i3; i3.i = i4; i4.i = i2; i2 = null; i3 = null; i4 = null; } } Obiekty mają zmienne instancyjne wskazujące na siebie, ale nie mają połączenia ze światem zewnętrznym. Te trzy obiekty są elgible for garbage collection. ---- import java.util.Date; public class GarbageFactory { public static void main(String[] args) { Date d = getDate(); doComplicatedStuff(); System.out.println("d = " + d); } public static Date getDate() { Date d2 = new Date(); String now = d2.toString(); // "now" kończy swoje istnienie, wraz z końcem funkcji System.out.println(now); return d2; // "d2" nie jest tracony, mimo zakończenia funkcji } } ---- Zadziałanie garbage collectora nie może być wymuszone, choć istnieją pewne metody, które mogą zgłosić do JVM potrzebę wykonania garbage collectora. Nie ma jednak gwarancji, że JVM zostanie uruchomione i usunie wszystkie nieużywane obiekty. Gwarantowane jest, że gdy jest mało pamięci, garbage collector zostanie uruchomiony zanim zostanie wyrzucony OutOfMemoryException. Klasa Runtime jest specjalną klasą, która posiada pojedynczy obiekt (Singleton) dla każdego głównego programu. Obiekt Runtime posiada mechanizm umożliwiający komunikację z maszyną wirtualną. Aby dostać instancję Runtime, można użyć metody Runtime.getRuntime(), która zwraca Singleton. Można także skorzystać z klasy System, która ma metody statyczne, pośrednio pobierającego Singleton. Najprostsza metoda poproszenia o wykonanie garbage collection jest: System.gc(); Jednak specyfikacja języka pozwala, aby powyższa metoda nie robiła nic. ---- import java.util.Date; public class CheckGC { public static void main(String[] args) { Runtime rt = Runtime.getRuntime(); System.out.println("Total JVM memory: " + rt.totalMemory()); System.out.println("Beore Memory = " + rt.freeMemory()); Date d = null; for (int i = 0; i < 10000; i++) { d = new Date(); d = null; } System.out.println("Afted Memory = " + rt.freeMemory()); rt.gc(); // an alternate to System.gc() System.out.println("After GC Memory = " + rt.freeMemory()); } } Total JVM memory: 1048568 Before Memory = 703008 After Memory = 458048 After GC Memory = 818272 Zachowanie gc() znależy od implementacji JVM. ---- Kod umieszczony w metodzie finalize() zostanie uruchomiony tuż przed usunięciem obiektu przez garbage collector. Nie jest jednak zagwarantowane, że kod umieszczony w finalize() zostanie kiedykolwiek wykonany. Dla każdego obiektu, finalize() zostanie wywołane tylko raz przez garbage collector. Wywołanie finalize() może uchronić obiekt przed usunięciem. Załużmy, że garbage collector wywołał finalize() dla pewnego obiektu. W metodzie finalize() zainicjowano jakąś zewnętrzną referencję na usuwany obiekt - obiekt staje się not eigible for garbage collection i nie zostanie usunięty. Jednak garbage collector pamięta, że finalize() było już raz wykonane dla tego obiektu i nie zostanie ponownie wywołane, gdy obiekt ponownie stanie się eligible for garbage collection. Jak sprawić, żeby zaalokowany obiekt nie był nigdy usunięty? Można stworzyć statyczną zmienną w klasie, będącą referencją do tego obiektu. ---- equals(), hashCode() i toString() są public. finalize() jest protected. Inner Classes ************* Instancja klasy wewnętrznej ma dostęp do wszystkich elementów klasy zewnętrznej, nawet tych prywatnych. class MyOuter { class MyInner { } } % javac MyOuter.java MyOuter.class MyOuter$MyInner.class ---- Klasy wewnętrzne nie mogą mieć żadnych deklaracji static. Dostęp do klasy wewnętrznej jest możliwy jedynie przez żywą instancję klasy zewnętrznej. Aby utworzyć instancję klasy wewnętrznej, musi istnieć instancja klasy zewnętrznej (nie można np. stworzyć obiektu klasy wewnętrznej z poziomu statycznej metody klasy zewnętrznej). ---- class MyOuter { private int x = 7; class MyInner { public void seeOuter() { System.out.println("Outer x is " + X); } } } ---- class MyOuter { private int x = 7; public void makeInner() { MyInner in = new MyInner(); in.seeOuter(); } class MyInner { public void seeOuter() { System.out.println("Outer x is " + x); } } } ---- public static void main(String[] args) { MyOuter mo = new MyOuter(); MyOuter.MyInner inner = mo.new MyInner(); inner.seeOuter(); } ---- public static void main(String[] args) { MyOuter.MyInner inner = new MyOuter().new MyInner(); inner.seeOuter(); } Powyższe dwa przykłady pozwalają na stworzenie obiektu klasy wewnętrznej z zewnątrz klasy zewnętrznej bądź z metody statycznej znajdującej się w klasie zewnętrznej. ---- this może być użyte tylko w kodzie instancyjnym (nie static). public void myMethod() { MyClass mc = new MyClass(); mc.doStuff(this); // pass a ref to object running MyMethod } ---- class MyOuter { private int x = 7; public void makeInner() { MyInner in = new MyInner(); in.seeOuter(); } class MyInner { public void seeOuter() { System.out.println("Outer x is " + x); System.out.println("Inner class ref is " + this); System.out.println("Outer class ref is " + MyOuter.this); } } public static void main(String[] args) { MyOuter.MyInner inner = new MyOuter().new Inner(); inner.seeOuter(); } } Outer x is 7 Inner class ref is MyOuter$MyInner@113708 Outer class ref is MyOuter@33f1d7 ---- Klasy wewnętrzne mogą być zadeklarowane z modyfikatorami: final abstract public private protected static - mamy wtedy top-level nested class, a nie inner class ---- Method-Local Inner Classes class MyOuter2 { private String x = "Outer2"; void doStuff() { class MyInner { public void seeOuter() { System.out.println("Outer x is " + x); } } } } ---- class MyOuter2 { private String x = "Outer2"; void doStuff() { class MyInner { public void seeOuter() { System.out.println("Outer x is " + x); } } MyInner mi = new MyInner(); // Ten wiersz musi być po definicji klasy mi.seeOuter(); } } Obiekt method-local inner class może być utworzony wyłącznie wewnątrz metody, w której klasa wewnętrzna jest zdefiniowana. Method-local inner class ma dostęp do elementów klasy otaczającej. ---- Obiekt klasy wewnętrznej nie może używać zmiennych lokalnych metody (poza zmiennymi final), w której klasa ta została zdefiniowana. class MyOuter2 { private String x = "Outer2"; void doStuff() { String z = "local variable"; final String z2 = "local variable"; class MyInner { public void seeOuter() { System.out.println("Outer x is " + x); System.out.println("Local variable z is " + z); // Błąd kompilacji System.out.println("Local variable z2 is " + z2); // OK } } } } Podobnie jak w przypadku zmiennych lokalnych, klasy wewnętrzne zadeklarowane w metodach nie mogą być: public, private, protected, static, transient, itp. Można zastosować abstract bądź final (ale nie obie naraz). ---- Anonymous Inner Classes class Popcorn { public void pop() { System.out.println("popcorn"); } } class Food { Popcorn p = new Popcorn() { public void pop() { System.out.println("anonymous popcorn"); } }; // ; } ---- class Popcorn { public void pop() { System.out.println("popcorn"); } } class Food { Popcorn p = new Popcorn() { public void sizzle() { System.out.println("anonymous sizzling popcorn"); } public void pop() { System.out.println("anonymous popcorn"); } }; public void popIt() { p.pop(); // OK, Popcorn ma metodę pop() p.sizzle(); // Błąd kompilacji! Popcorn nie ma metody sizzle() } } ---- interface Cookable { public void cook(); } class Food { Cookable c = new Cookable() { public void cook() { System.out.println("anonymous cookable implementer"); } }; } Anonimowy implementator interfejsu może implementować tylko jeden interfejs. Wewnętrzna klasa anonimowa nie może także jednocześnie rozszerzać klas i implementować interfejsu. ---- Runnable r = new Runnable(); // błąd! Runnable to interfejs Runnable r = new Runnable() { public void run() { } }; // OK ---- Argument-Defined Anonymous Inner Class class MyWonderfulClass { void go() { Bar b = new Bar(); b.doStuff(new Foo() { public void foof() { System.out.println("foofy"); } }); // brak średnika po } } } interface Foo { void foof(); } class Bar { void doStuff(Foo f) { } } ---- Static Nested Classes Static nested classes, top-level nested classes, static inner classes. Klasy wewnętrzne statyczne nie są w istocie klasami wewnętrznymi, gdyż ich związek z klasami zewnętrznymi jest inny niż w przypadku innych klas wewnętrznych. class BigOuter { static class Nested { } } static nie oznacza, że klasa jest statyczna (nie ma czegoś takiego jak statyczna klasa), ale oznacza, że jest statycznym elementem klasy zewnętrznej. Do klasy tej można mieć dostęp, jak do innych statycznych elementów, z wnętrza statycznej metody klasy zewnętrznej, bez potrzeby posiadania instancji klasy zewnętrznej. Podobnie jak metody statyczne, statyczne zagnieżdżone klasy nie mają dostępu do instancyjnych zmiennych i metod klasy zewnętrznej. ---- class BigOuter { static class Nested { } } class Broom { public static void main(String[] args) { BigOuter.Nested n = new BigOuter.Nested(); } } ---- public abstract class AbstractTest { public int getNum() { return 45; } public abstract class Bar { public int getNum() { return 38; } } public static void main(String[] args) { AbstractTest t = new AbstractTest() { public int getNum() { return 22; } }; AbstractTest.Bar f = t.new Bar() { public int getNum() { return 57; } }; System.out.println(f.getNum() + " " + t.getNum()); // 57 22 } } ---- Threads ******* Uruchomiony wątek jest indywidualnym lekkim procesem (lightweight process), który posiada własny stos. Metoda main() działa w swoim własnym wątku, nazwanym main. Każdy wątek ma własny stos wywołań. Wątek może być stworzony jako: - subklasa klasy java.lang.Thread, - implementacja interfejsu Runnable W codziennej praktyce programistycznej, bardziej prawdopodobne będzie użycie Runnable. Dziedziczenie po klasie Thread, aby stworzyć wątek kłóci się nieco z paradygmatem OO. Dziedziczenie po Thread powinno się zastosować raczej w przypadku tworzenia bardziej specjalizowanej klasy wątków, a nie po to, aby stworzyć wątek. Implementacja interfejsu Runnable daje także możliwość, aby klasa implementująca mogła dziedziczyć po jakiejś klasie. Dziedziczenie po java.lang.Thread --------------------------------- class MyThread extends Thread { public void run() { System.out.println("..."); } } Minusem takiego rozwiązania jest to, że gdy klasa MyThread dziedziczy po Thread, nie może dziedziczyć po żadnej dodatkowej klasie. Można także przeciążyć metodę run(), ale żadna z metod przeciążonych nie zostanie oczywiście wywołana automatycznie i nie zostanie użyta jako początek nowego stosu wywołań dla wątku. Implementacja java.lang.Runnable -------------------------------- class MyRunnable implements Runnable { public void run() { System.out.println("..."); } } ---- Każdy wątek wymaga instancji klasy Thread (niezależnie od tego, czy dziedziczyliśmy po Thread, czy implementowaliśmy Runnable). Thread ------ MyThread t = new MyThread(); Runnable -------- MyRunnable r = new MyRunnable(); Thread t = new Thread(r); Stworzenie wątku z użyciem konstruktora bezargumentowego powoduje, że wątek wywoła swoją własną metodę run() - jest to zachowanie porządane w przypadku dziedziczenia po Thread. W przypadku Runnable, należy przekazać nowemu wątkowi, aby użył naszej metody run(). Runnable przekazane do konstruktora klasy Thread jest nazywane "target" bądź "target Runnable". Można przekazać pojedynczą instancję Runnable do wielu obiektów klasy Thread, tak że ten sam obiekt Runnable stanie się celem (target) dla wielu wątków. public class TestThreads { public static void main(String[] args) { MyRunnable r = new MyRunnable(); Thread foo = new Thread(r); Thread bar = new Thread(r); Thread bat = new Thread(r); } } ---- Konstruktowy klasy Thread: - Thread(), - Thread(Runnable target), - Thread(Runnable target, String name), - Thread(String name), - Thread(ThreadGroup group, Runnable target), - Thread(ThreadGroup group, Runnable target, String name), - Thread(ThreadGroup group, String name) ---- Jeśli wątek jest stworzony, ale nie wystartowany (nie wywołano metody start()), mówi się, że wątek jest w nowym stanie (new state). W tym stanie wątek nie jest jeszcze żywy (alive, isAlive() zwróci false). Wątek jest żywy (alive) po tym jak został wystartowany (po wywołaniu start(), JVM potrzebuje nieco czasu na uruchomienie wątka). Wątek nie jest żywy (not alive), po tym jak stanie się martwy. Uruchamianie wątku ------------------ t.start(); Po wywołaniu start(), wątek przechodzi do nowego stanu (new state). Nowy stan oznacza, że mamy obiekt klasy Thread, ale nie mamy jeszcze prawdziwego wątku (true thread). Po wywołaniu start(): - startuje nowy wątek (z nowym stosem wywołań), - wątek przechodzi z nowego stanu (new state) do runnable state, - kiedy wątek otrzymuje możliwość wykonania, jego metoda run() (target run() zostanie wykonana. ---- Runnable r = new Runnable(); r.run(); // legalne, ale nie startuje wątku ---- public class NameThread { public static void main(String[] args) { System.out.println("thread is " + Thread.currentThread().getName()); // thread is main } } ---- class NameRunnable implements Runnable { public void run() { for (int x = 1; x < 4; x++) System.out.println("Run by " + Thread.currentThread().getName()); } } public class ManyName { public static void main(String[] args) { NameRunnable nr = new NameRunnable(); Thread one = new Thread(nr); one.setName("Fred"); Thread two = new Thread(nr); two.setName("Lucy"); Thread three = new Thread(nr); three.setName("Ricky"); one.start(); two.start(); three.start(); } } ---- Jeśli wykonywanie metody run() zakończyło się, nie można ponownie uruchomić wątku poprzez t.start() - próba wywołania start() spowoduje wyrzucenie wyjątku. ---- Stany wątku ----------- New - stan, w którym wątek znajduje się, gdy została utworzona instancja klasy Thread, ale nie wywołano start(); W tym stanie wątek określa się jako nieżywy (not alive); Runnable - stan, w którym wątek znajduje się, gdy może zostać wykonany, ale scheduler aktualnie nie wykonuje jego kodu; wątek wchodzi w stan runnable, gdy zostanie wywołana metoda start(); wątek może także wrócić do stanu runnable, ze stanów blocked, waiting, sleeping; w tym stanie wątek określa się jako żywy (alive); Running - stan, w którym wątek znajduje się, gdy scheduller wybrał go z runnable pool, jako aktualnie wykonywany; Waiting/blocked/sleeping - wątki znajdujące się w tych stanach są ciągle żywe (alive), ale nie mogą zostać wykonane (nie są runnable); wątek może być blocked, oczekując na zasoby - jeśli zasób stanie się dostępny, wątek stanie się runnable; wątek zablokowany ciągle jest żywy (alive); wątek może być sleeping, gdy zostanie uśpiony jawnie z poziomu programu - jeśli czas uśpienia minie, wątek stanie się runnable; wątek może być waiting, gdy zostanie ustawiony w taki stan - jeśli inny wątek prześle odpowiednią notyfikację, wątek znów stanie się runnable; Dead - stan, w którym wątek znajduje się, gdy metoda run() zakończy działanie; nieżywy wątek nie może być przywrócony do życia; +-- Waiting/ <--+ | blocking | | | v | New ---> Runnable <---> Running ---> Dead ---- sleep() ------- class NameRunnable implements Runnable { public void run() { for (int x = 1; x < 4; x++) { System.out.println("Run by " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException ex) { } } } } public class ManyNames { public static void main(String[] args) { nameRunnable nr = new NameRunnable(); Thread one = new Thread(nr); one.setName("Fred"); Thread two = new Thread(nr); two.setName("Lucy"); Thread three = new Thread(nr); three.setName("Ricky"); one.start(); two.start(); three.start(); } } ---- Po upływie czasu sleep() wątek budzi się i przechodzi do stanu runnable. Zatem czas ustawiony dla sleep() jest minimalnym czasem, przez których wątek nie będzie działał, a nie dokładnym czasem. Nie można polegać na sleep(), aby uzyskać dokładny zegar. sleep() jest metodą statyczną. Jeden wątek nie może uśpić innego wątku. ---- Wątek zawsze działa z jakimś priorytetem. Priorytety numerowane są od 1 do 10 (choć w niektórych przypadkach zakres jest mniejszy niż 10). Jeśli np. stworzyliśmy wątki z 10 różnymi priorytetami, a program jest uruchomiony na JVM, w której max. priorytet wynosi 5, to niektóre wątki mogą być zmapowane na ten sam priorytet. Poszczególne implementacje JVM mogą inaczej obsługiwać priorytety wątków, a zachowanie schedulera nie jest zagwarantowane. Nowy wątek otrzymuje domyślny priorytet, który jest priorytetem wątku, który go stworzył. Domyślny priorytet wynosi 5. public class TestThreads { public static void main(String[] args) { MyThread t = new MyThread(); } } Wątek, do którego odnosi się "t" będzie miał taki sam priorytet, jak wątek "main". Zmiana priorytetu wątku: FooRunnable r = new FooRunnable(); Thread t = new Thread(r); t.setPriority(8); t.start(); Klasa Thread posiada trzy zmienne static final: Thread.MIN_PRIORITY (1) Thread.NORM_PRIORITY (5) Thread.MAX_PRIORITY (10) yield() ------- Powoduje przejście aktualnie wykonywanego wątku do puli runnable, aby pozwolić innemu wątkowi o tym samym priorytecie, aby został wykonany. W rzeczywistości nie ma gwarancji, że yield() wykona to, co powinna. Nawet jeśli yield() spowoduje przejście wątku do puli runnable, może on zostać wykonany ponownie, przed innymi wątkami. join() ------ Niestatyczna metoda join() pozwala jednemu wątkowi zostać wykonanym, gdy zakończy się działanie innego wątka. Thread t = new Thread(); t.start(); t.join(); Powyższy przykład dołącza aktualny wątek (main) na koniec wątka, do którego odnosi się "t". Blokuje aktualny wątek przed staniem się runnable, dopóki wątek, do którego odnosi się "t" nie będzie dłużej żywy. Występuje także join() z parametrem czasowym - czekaj dopóki wątek "t" nie będzie zakończony, ale jeśli oczekiwanie trwa dłużej niż np. 5000 milisekund, przestań czekać i uruchom się mimo wszystko. A is running A is running A is running Thread b = new Thread(aRunnable); A is running -- b.start(); A is running A is running B is running B is running A is running B is running A is running A is running B is running A is running -- b.join(); // A podłącza się na koniec B B is running B is running B is running B is running B is running B is running B is running B is running B is running B is running -- // B zakończył się A is running -- // A znów działa A is running A is running A is running A is running ---- Przypadki, w których wątek będący w stanie running może zmienić stan: sleep() - gwarantuje zatrzymanie wykonywania aktualnego wątku na przynajmniej ustawiony czas (choć może zostać przerwany przed upływem tego czasu); yield() - nie gwarantuje się wykonania czegokolwiek, choć typowo powinno spowodować przesunięcie aktualnego wątku do puli runnable; join() - gwarantuje zatrzymanie aktualnego wątku, dopóki wątek, do którego się podłączono nie zakończy się. Inne scenariusze, w których wątek może opuścić stan running: - metoda run() zakończyła się, - wywołanie metody wait() na rzecz obiektu (metody wait() nie wywołuje się na rzecz wątku), - wątek nie może pozyskać lock na rzecz obiektu, którego kod metody chce wykonać, ---- Nie można zagwarantować, że pojedynczy wątek będzie cały czas w stanie running w trakcie operacji atomowej. Można jednak zagwarantować, że nawet jeśli wątek wykonujący operację atomową zmieni stan z running na inny, żaden inny pracujący wątek nie będzie miał dostępu do tych samych danych. --- Synchronizacja i lock - tylko metody mogą być synchronized, a nie zmienne; - każdy obiekt ma tylko jeden lock; - nie wszystkie metody w klasie muszą być synchronized; - jeśli dwie metody w klasie są synchronized, tylko jeden wątek może mieć dostęp do jednej z dwóch metod; - jeśli klasa ma zarówno metody synchronized, jak i nie-synchronized, wiele wątków może mieć wciąż dostęp do metod nie-synchronized; nie należy oznaczać jako synchronized metod, które nie mają dostępu do danych, które chcemy chronić; - jeśli wątek wywołuje sleep, wciąż posiada lock; - wątek może pozyskać więcej niż jeden lock; np. wątek może wejść do metody synchronized (pozyskać lock), a następnie wywołać metodę synchronized innego obiektu; wątek, który posiada lock może także wywołać metodę synchronized tego samego obiektu - JVM wie, że ten wątek ma już lock dla tego obiektu; - można synchronizować blok instrukcji (synchronized block), a nie koniecznie całą metodę: class SyncTest { public void doStuff() { System.out.println("not synchronized"); synchronized(this) { System.out.println("synchronized"); } } } Kiedy wątek uruchamia kod z poziomu synchronizowanego bloku (włączając w to dowolny kod metody wywołanej z synchronizowanego bloku), mówimy, że kod został uruchomiony w synchronizowanym kontekście (executing in a synchronized context). W przypadku metod synchronized, obiekt używany do wywołania metody jest obiektem, którego lock musi być pozyskany. W przypadku synchronizowania bloku kodu, programista specyfikuje, lock którego obiektu ma zostać użyty jako lock - można zatem użyć niepowiązanego obiektu, jako lock dla fragmentu kodu. Daje to możliwość posiadania więcej niż jednego locka dla kodu synchronizowanego, wewnątrz jednego obiektu. Metody statyczne mogą być synchronizowane. Ponieważ jest tylko jedna kopia statycznych danych, które chcemy ochronić, więc wystarczy tylko jeden lock na klasę, aby synchronizować metody statyczne. Każda klasa załadowana ma odpowiadającą instancję java.lang.Class reprezentującą tę klasę. Lock instancji java.lang.Class jest używany, żeby zabezpieczyć metodę statyczną klasy (jeśli jest synchronized). public static synchronized void getCount() { } ---- class MyThread extends Thread { StringBuffer sb; MyThread(StringBuffer sb) { this.sb = sb; } public void run() { synchronized(sb) { for (int i = 0; i < 10; i++) { System.out.print(sb); try { sleep(100); } catch (InterruptedException e) { } } sb.setCharAt(0, (char)(sb.charAt(0) + 1)); } } } public class Main { public static void main(String[] args) { StringBuffer sb = new StringBuffer("A"); MyThread t1 = new MyThread(sb); MyThread t2 = new MyThread(sb); MyThread t3 = new MyThread(sb); t1.start(); t2.start(); t3.start(); // AAAAAAAAAABBBBBBBBBBCCCCCCCCCC } } ---- Oddaje Lock Trzyma Lock Class Defining the Method ------------------------------------------------------------------------------------------------ wait() notify() (chociaż wątek prawdopodobnie opuści java.lang.Object synchronizowany kod wkrótce po tym wywołaniu i podda swój lock) join() java.lang.Thread sleep() java.lang.Thread yield() java.lang.Thread ---- wait(), notify(), notifyAll() muszą być wywołane z synchronizowanego kontekstu. Wątek nie może wywołać wait() lub notify() na rzecz obiektu, chyba że posiada lock tego obiektu. wait() i notify() są metodami instancyjnymi klasy Object. Każdy obiekt ma lock. Każdy obiekt może mieć listę wątków, które oczekują na sygnał (notification) od obiektu. Wątek dostaje się na tę listę poprzez wywołanie metody wait() obiektu docelowego (target object). Od tego momentu, wątek nie wykonuje żadnej instrukcji dopóki metoda notify() obiektu docelowego nie zostanie wywołana. Jeśli wiele wątków oczekuje na ten sam obiekt, tylko jeden będzie wybrany (bez zagwarantowanej kolejności) do kontynuacji wykonywania się. Jeśli nie czeka żaden wątek, żadna akcja nie zostanie podjęta. Wywołanie wait() powoduje zwolnienie locka, na czas działania wait(). wait(), notify() i notifyAll() są metodami java.lang.Object. sleep() i yield() są metodami statycznymi - jeden obiekt nie może powiedzieć innemu, aby usnął. join() i start() nie są metodami statycznymi. Class Object Class Thread Interface Runnable -------------------------------------------------------- wait() start() run() notify() yield() notifyAll() sleep() join() -------------------------------------------------------- public class Main extends Thread { public static void main(String[] args) { Main m = new Main(); Thread t = new Thread(m); // OK t.start(); } public void run() { System.out.println("aaa"); } } Klasa Thread implementuje interfejs Runnable - Thread może wziąć obiekt typu Thread, jako argument konstruktora.