Notatki z Javy (SCJP), cz. 1
-- Sebastian Pawlak, 2006.
Słowa kluczowe: strictfp, transient, volatile, native Słowa zarezerwowane, ale nie używane: const (odpowiada public static final w Javie), goto Słowa kluczowe include, overload, unsigned, virtual, fiend nie należą do Javy null, true, false są literałami, a nie słowami kluczowymi, niemniej nie można ich użyć np. jako nazwę metody protected - metoda bądź zmienna dostępna tylko dla klas w tym samym pakiecie bądź w klasach pochodnych abstract - nie można stworzyć obiektu klasy; metoda musi być zaimplementowana w nieabstrakcyjnej klasie pochodnej final - nie można dziedziczyć po klasie, przesłonić metod bądź zmienić wartości zmiennej native - wskazuje, że metoda jest napisana w języku zależnym od platformy (np. C) static - matoda bądź zmienna należy do metody, a nie do obiektu strictfp - użyte przed metodą bądź klasą wskazuje, że liczby zmiennopozycyjne będą przestrzegać zasad FP-strict we wszystkich wyrażeniach snychronized - wskazuje, że metoda może być dostępna tylko przez jeden wątek w tym samym czasie transient - zabrania serializacji danego pola (pole jest pomijane podczas serializacji obiektu) volatile - wskazuje, że zmienna może być zmieniona asynchronicznie przed wątek instanceof - pozwala określić, czy obiekt jest instancją klasy, superklasy bądź interfejsu. float f = 13.1415; // błąd kompilacji float f = 13.1415f; // OK char c = (char)-100; // OK char c = -100; // błąd kompilacji char c = 80000; // błąd kompilacji char c = '\n'; // OK int[] a; int a[]; int[][][] a; String[] ss[]; // tablica dwuwymiarowa int[5] a; // błąd kompilacji Thread[] threads = new Thread[5]; // tworzy na stercie jeden obiekt, // który przechowuje 5 referencji, // ale obiekty Thread nie są tworzone int[] a = new int[]; // błąd kompilacji - należy podać rozmiar int[][] a = new int[3][]; // OK int[] x = new int[5]; int[8] = 20; // Runtime exception int[][] d2 = new int[2][]; d2[0] = new int[5]; d2[1] = new int[3]; int a = 5; int[] d = { 1, 2, 3, 4, a }; Figure fig = new Trojkat(); Figure[] figs = { new Kwadrat(), fig }; int[][] d2 = { { 1, 2, 3 }, { 7, 3 }, { 4, 3 } }; // 4 obiekty na stercie int[] d; d = new int[] { 1, 2, 3 }; // anonimowa tablica przykład użycia: f.jakasmetoda(new int[] { 1, 2, 3 }); W tego rodzaju deklaracjach nie można podać rozmiaru tablicy w nawiasach kwadratowych!!! int[] ii; char[] cc = new char[5]; ii = cc; // błąd (różne typy) Figure[] figures; Triangle[] triangles = new Triangle[3]; figures = triangles; // OK; sytuacja odwrotna nie jest możliwa Aby można było przypisać tablicę do uprzednio stworzonej referencji do tablicy, tablica i referencja muszą być tego samego wymiaru. Przykładowo, nie można przypisać dwuwymiarowej tablicy do referencji do jednowymiarowej tablicy. int[][] d2 = new int[4][]; int[] h = new int[7]; d2[0] = 5; // błąd d2[0] = h; // OK String name = new String[] { "Ala", "Ola", "Majka", "Joanna" } [1]; // name = "Ola" ---- Zmienne lokalne to te zadeklarowane wewnątrz metod bądź jako argumenty metody. Zmienne lokalne są także nazywane stack, temporary, automatic, method. Zmienne lokalne mogą nie być zainicjalizowane w miejscu deklaracji. Niemniej muszą zostać im przypisane wartości przed pierwszym użyciem. Instance variables (member variables) są zdefiniowane na poziomie klasy (nie w ciele metody, konstruktora bądź innych blokach inicjalizacyjnych). Są one inicjalizowane do wartości domyślnych, gdy tworzona jest instancja klasy. Wartości elementów tablicy mają zawsze wartości domyślne, niezależnie od tego, gdzie tablica została zadeklarowana! Niemniej lokalna referencja do tablicy musi zostać przypisana, zanim zostanie użyta. ---- Może być tylko jedna klasa public w pliku. Nazwa pliku musi odpowiadać nazwie klasy publicznej. Jeśli klasa jest częścią pakietu, "package" musi być pierwszą linią w pliku (oprócz komentarzy). "import" musi być między "package" (bądź na początku pliku, jeśli nie ma "package") a deklaracją klasy. "import" i "package" odnoszą się do całego pliku źródłowego. Modyfikatory deklaracji klas: access modifiers: public, protected, private nonaccess modifiers: strictfp, final, abstract Do klas używa się tylko "public" bądź dostępu pakietowego (brak słowa kluczowego). Default Access -------------- package cert; class Beverage { } ---- package exam.stuff; import cert.Beverage; class Tea extends Beverage { } ---- >javac Tea.java Tea.java:1: Can't access class cert.Beverage. Class or interface must be public, in same package, or an accessible member class. Public Access ------------- Wszystkie klasy mają dostęp do klas publicznych. Nonaccess Class Mofigiers ------------------------- "strictfp" może być użyte wyłącznie w stosunku do klasy bądź metody, ale nigdy do zmiennej. Dozwolone: public final strictfp final strictfp abstract final synchronized protected abstract static final synchronized public final private native void static final synchronized protected void ... Zabronione: final abstract - błąd kompilacji (po klasie abstrakcyjnej powinno się dziedziczyć, a final zabrania dziedziczenia) private abstract abstract synchronized abstract strictfp abstract native abstract static transient private native void Klasa finalna - nie można z tej klasy dziedziczyć Klasa abstrakcyjna - nie można tworzyć obiektów klasy abstrakcyjnej Modyfikator "protected" i domyślny (pakietowy) są prawie identyczne, poza jedną krytyczną właściwością. Element z modyfikatorem domyślnym może być dostępny tylko wtedy, gdy znajduje się w tym samym pakiecie. "protected" może być dostępny (poprzez dziedziczenie) z podklas, nawet wtedy gdy znajduje się w innym pakiecie. package certification; public class OtherClass { void testIt() { } } ---- package somethingElse; import certification.OtherClass; class AccessClass { static public void main(String[] args) { OtherClass o = new OtherClass(); o.testIt(); } } ---- javac AccessClass.java AccessClass.java:5: No method matching testIt() found in class certification.OtherClass. o.testIt(); 1 error Jeśli klasa B dziedziczy po klasie A, to elementy "protected" klasy A stają się "private" w klasie B. Zatem dziedzicząc po klasie B, nie ma dostępu do elementów, które były "protected" w klasie A, gdyż stały się one prywatne w klasie B. Modyfikatory dostępu nie mogą się odnosić do zmiennych lokalnych! Zmienne lokalne mogą być conajwyżej "final". ----------------------------------------------------------------------------------- | protected | domyślny ----------------------------------------------------------------------------------- | dziedziczenie | dostęp przez | dziedziczenie | dostęp przez | | referencje | | referencje ----------------------------------------------------------------------------------- pakiet | + | + | + | + ----------------------------------------------------------------------------------- poza pakietem| + | - | - | - ----------------------------------------------------------------------------------- Dostępność do elementów klasy public protected default private ---------------------------------------------------------------------------------- Z tej samej klasy + + + + Z każdej klasy w tym samym pakiecie + + + - Z każdej nie-podklasy w innym pakiecie + - - - Z podklasy w tym samym pakiecie + + + - Z podklasy w innym pakiecie + + - - Metody finalne nie mogą być przesłonięte w podklasach. public f(final int i) { } // zmienna "i" nie może ulec zmianie ---- public abstract class A { abstract void f(); } class B extends A { void f(int i) { }; } błąd kompilacji ---- synchronized, native - mogą odnosić się tylko do metod strictfp - standard IEEE754 Instance variables (field, property, attribute) - można użyć public, private, protected, domyślny; może być final, może być transient nie może być: abstract, synchronized, strictfp, native Local (automatic, stack, method) variables Local Non-local Method variables variables ---------------------------------------------- final final final public public protected protected private private static static transient abstract synchronized strictfp native ---- Shadowing class A { int count = 5; public void f() { int count = 10; } } Zmienne instancyjne final muszą być zainicjalizowane najpóźniej w konstruktorze class A { final int x; // blank final public A() { x = 6; } } ---- interface Foo { Integer x = new Integer(5); // domyślnie public static final } class B implements Foo { void f() { x = new Integer(5); // błąd - "x" jest final } } ---- Po finalnej klasie nie można dziedziczyć. Finalnej metody nie można przesłonić. Finalnej zmiennej nie można zmienić. ---- Transient może być użyte wyłącznie do zmiennych instancyjnych. Oznacza pominięcie zmiennej przy serializacji. Volatile może być użyte wyłącznie do zmiennych instancyjnych. static ------ Zmienne i metody statyczne klasy mogą być dostępne bez tworzenia obiektu tej klasy. Jeśli są instancje klasy, statyczne zmienne są wspólne dla wszystkich obiektów. ---- class Frog { static int frogCount = 0; public Frog() { frogCount += 1; } public static void main (String[] args) { new Frog(); new Frog(); new Frog(); System.out.println(frogCount); // 3 } } ---- class Frog { int frogCount = 0; public Frog() { frogCount += 1; } public static void main (String[] args) { new Frog(); new Frog(); new Frog(); System.out.println(frogCount); } } Frog.java:11: non-static variable frogCount cannot be referenced from a static context System.out.println(frogCount); Metoda statyczna nie ma dostępu do niestatycznych (instancyjnych) zmiennych. Statyczne metody nie mogą bezpośrednio wywołać niestatycznych metod. class Frog { static int frogCount = 0; public Frog() { frogCount++; } } class Main { public static void main(String[] args) { new Frog(); new Frog(); new Frog(); System.out.println(Frog.frogCount); // OK Frog f = new Frog(); int frog = f.frogCount; // OK } } ---- class A { static int i = 2; void f() { i = 7; } } public class Main { public static void main(String[] args) { new A().f(); System.out.println(A.i + " " + new A().i); } } ---- Element statyczny może być dostępny używając referencji: obiekt.metodaStatyczna(); niemniej jest to trik składniowy. Statyczna metoda nie wie nic o instancji. Kompilator używa typu klasy referencji, a nie samej referencji. Zatem metoda statyczna wywołana w powyższy sposób nadal nie ma dostępu do zmiennych instancyjnych niestatycznych. Statyczna metoda nie może być przesłonięta (podobnie jak metoda prywatna). Metoda o tej samej nazwie i liście parametrów w klasie potomnej jest inną metodą. ---- Metody statyczne nie mogą być przesłonięte. Mogą być zredefiniowane w klasie pochodnej. Statyczne mogą być metody, zmienne, top-level nested classes. Statyczne nie mogą być: konstruktory, klasy, interfejsy, klasy wewnętrzne (poza top-level nested classes), inner class methods and instance variables, zmienne lokalne. int i = 10; static void f() { int x = i; // błąd } void g() { } static void h() { g(); // błąd } static int i; static void u() { } static void k() { u(); // OK int x = i; // OK } ---- import java.util.ArrayList.*; // błąd (ArrayList to klasa) import java.util; // błąd - explicit class import ---- public static void main (String[] identyfikator) Jeśli "main" ma inną postać (bądź jest w ogóle brak "main") to mamy do czynienia z wyjątkiem NoSuchMethodError, a nie błędem kompilacji. W klasie może być kilka przeciążonych main, ale musi być main zgodny ze specyfikacją. static public void main (String costam []) { } // OK ---- Implementacja interfejsu "java.lang.Runnable" Interfejs Runnable ma tylko jedną metodę postaci: public void run() { } ---- Metody w interfejsie są domyślnie publiczne i abstrakcyjne. Metody interfejsu nie mogą być statyczne. Zmienne w interfejsie muszą być public static final (ale nie trzeba tego pisać). Ponieważ metody interfejsu są abstrakcyjne, to nie mogą być: final, native, strictfp, synchronized. Interfejs może rozszerzać jeden bądź więcej interfejsów. public abstract interface A { } // OK public interface A { } // OK public interface A { public abstract void f(); // public abstract są nadmiarowe, ale OK } ---- Poprawne i tożsame: void a(); public void a(); abstract void a(); public abstract void a(); abstract public void a(); Niepoprawne: final void a(); static void a(); private void a(); protected void a(); synchronized void a(); native void a(); strictfp void a(); ---- interface A { int AAA = 65; // tożsame z poniższym } interface A { public static final int AAA = 65; } interface B { public int a = 1; // OK int b = 1; static int c = 1; final int d = 1; public static int e = 1; public final int f = 1; static final int g = 1; public static final int h = 1; } ---- Nieabstrakcyjna implementacja interfejsu musi: - dostarczyć nieabstrakcyjnej implementacji wszystkich metod interfejsu, - przestrzegać wszystkich zasad legalnego przesłaniania, - nie deklarować wyjątków sprawdzanych innych niż te zadeklarowane w metodzie interfejsu; nie może deklarować żadnych sprawdzanych wyjątków, które są szersze niż wyjątki zadeklarowane w metodzie interfejsu; może deklarować RuntimeExceptions niezależnie od tego, jak było zadeklarowane w interfejsie, - zachować nazwę metody interfejsu i ten sam typ zwracany (ale nie musi deklarować wyjątków zadeklarowanych w deklaracji metody interfejsu) Klasa implementująca może być abstrakcyjna: interface A { void f(); } abstract class B implements A { } Klasa może dziedziczyć po więcej niż jednym interfejsie i tylko po jednej klasie: public class A implements B, C, D { } Interfejs może dziedziczyć po innych interfejsach, ale nie może niczego implementować. interface A { } interface B { } interface C extends A, B { } Pierwsza nieabstrakcyjna klasa musi zaimplementować metody z A i z B i z C. ---- interface A { void f(); // domyślnie public } class B implements A { void f() { } // błąd - powinno być public void f(); } ---- byte b = 27; // OK byte b = (byte)27 // OK Działanie, które ma element "int" bądź mniejszy, ma wynik typu "int". byte + byte = int int * short = int short / byte = int ---- byte b = 3; // OK byte c = 8; // OK byte d = b + c; // błąd (possible loss of precision) byte e = (byte)(b + c); // OK float f = 3.1415; // błąd float f = (float)3.1415; // OK float f = 3.1415f; // OK float f = 3.1415F; // OK byte a = 128; // błąd (possible loss of precision), max 127 byte a = (byte)128; // OK byte b = 3; b += 7; // OK byte b = 3; b = (byte)(b + 7); // OK ---- import java.awt.Dimension; class ReferenceTest { public static void main(String[] args) { Dimension a = new Dimension(5, 10); System.out.println("a.height = " + a.height); Dimension b = a; b.height = 30; System.out.println("a.height = " + a.height + " after change to b"); } } %java ReferenceTest a.height = 10 a.height = 30 after change to b ---- Wyjątkiem jest referencja na obiekty typu String. W Javie obiekty String są specjalnie traktowane. Nie można zmienić wartości obiektu String. class Strings { public static void main(String[] args) { String x = "Java"; String y = x; System.out.println("y string = " + y); x = x + " Bean"; System.out.println("y string = " + y); } } %java String y string = Java y string = Java Kiedy używam się operatora "+" dla obiektu String, VM tworzy nowy obiekt String. ---- boolean b = 100 > 99; // OK ---- public static void main(String[] args) { String s = new String("foo"); if (s instanceof String) { System.out.print("s is a String"); } } ---- class A { } class B extends A { public static void main(String[] args) { B b = new B(); if (b instanceof A) System.out.print("b is an A"); // program wypisze ten tekst } } ---- B b = new B(); if (b instanceof Object) System.out.print("OK"); // program wypisze ten tekst ---- interface Foo { } class A implements Foo { } class B extends A { } A a = new A(); B b = new B(); Prawdziwe są stwierdzenia: a instanceof A a instanceof Foo b instanceof A b instanceof B b instanceof Foo ---- String a = null; boolean b = null instanceof String; // OK (false) boolean c = a instanceof String; // OK (false) ---- public class Main { public static void main(String[] args) { Integer x = new Integer(5); if (x instanceof String) // błąd kompilacji (inconvertible types) System.out.print("ok"); } } ---- int [] nums = new int[3]; if (nums instanceof Object) { } // true Każda tablica jest zawsze instancją Object. interface Face { } class Bar implements Face { } class Foo extends Bar { } Foo[] instanceof Foo,Bar,Face <- false Foo[] instanceof Object <- true Foo[1] instanceof Foor,Bar,Face,Object <- true ---- boolean b = false; if (b = true) { // =, a nie == System.out.println("b is true"); int x = 1; if (x = 0) { } // błąd kompilacji if (x == 0) { } // OK ---- Dzielenie przez zero Liczba całkowita podzielona przez zero powoduje zgłoszenie ArithmeticException. Liczba zmiennoprzecinkowa podzielona przez zero daje w wyniku + bądź - nieskończoność. zmienna % 0 <- ArithmeticException zmienna(float) % 0 <- nie powoduje ArithmeticException ---- String a = "String"; int b = 3; int c = 7; System.out.println(a + b + c); // String37 System.out.println(a + (b + c)); // String10 int b = 2; int c = 3; System.out.println("" + b + c); // 23 System.out.println(b + c); // 5 ---- final int x = 5; int y = x++; // błąd kompilacji ---- int x = 0x80000000; System.out.println(x); // -2147483648 x = x << 1; system.out.println(x); // 0 ---- 1000 0000 0000 0000 0000 0000 0000 0000 >> 1 1100 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 >> 4 1111 1000 0000 0000 0000 0000 0000 0000 0100 0000 0000 0000 0000 0000 0000 0000 >> 1 0010 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 >>> 4 0000 1000 0000 0000 0000 0000 0000 0000 Operandy operacji przesuwania bitów są promowane do int bądź long. 1111 1111 1111 1111 1111 1111 1111 1111 = -1 Bitwise Complement Operator int x = 5; x = ~x; Conditional Operator someVariable = (boolean expression) ? value to assign if true: value to assign if false String ss = (x<15)?"small":(x<22)?"tiny":"huge"; // OK ---- Operatory logiczne & i | mogą być użyte ze zmiennymi boolean i integer. Nie są to operatory shortcircuit. Zawsze obliczają obydwa operandy. && i || mogą być użyte tylko ze zmiennymi boolean. shortcircuit. ---- +=, *=, /=, ... powodują automatyczne rzutowanie. ---- if (5 > 3) if (b == true) System.out.println("OK"); else // tak naprawdę "else" odnosi się do drugiego "if", // mimo mylącego wcięcia System.out.ptintln("BAD"); ----- "switch" wymaga zmiennej "int". Można użyć byte, short, char, bo automatycznie zostaną rzutowane na int. Nie można użyć long, float, double, bez jawnego rzutowania. case - można użyć tylko literałów liczbowych bądź zmiennych final. final int one = 1; int x = 1; switch (x) { case one: break; case 2: break; } ---- byte g = 2; switch(g) { case 23: case 128: // błąd kompilacji (possible loss of precision) } ---- Nie może być dwóch takich samych case'ów w switchu. ---- Integer in = new Integer(4); switch (in) { // błąd kompilacji ---- int x = 7; switch (x) { case 2: System.out.println("2"); default: System.out.println("def"); case 3: System.out.println("3"); } def 3 ---- public class Main { static boolean isX() { System.out.println("X"); return false; } static boolean isY() { System.out.println("Y"); return true; } static boolean isZ() { System.out.println("Z"); return true; } public static void main(String[] args) { int x = 1, y = 1, z = 1; if ((isX()) || (isY()) && (isZ())) // X Y Z System.out.println("OK"); } } ---- static boolean isX() { System.out.println("X"); return false; } static boolean isY() { System.out.println("Y"); return false; } static boolean isZ() { System.out.println("Z"); return false; } public static void main(String[] args) { int x = 1, y = 1, z = 1; if ((isX()) || (isY()) & (isZ())) // X Y Z System.out.println("OK"); } } for (int i = 0; i < 10; i++) { } System.out.println(i); // błąd - "i" widoczne tylko w "for" ---- for (int a = 1; b != 1; System.out.println("iterate")) b = b - a; iterate iterate ---- "continue" musi być w pętli, inaczej wystąpi błąd kompilacji. "break" musi być w pętli bądź "switch". ---- aaa: for (int i = 0; i < 10; i++) { for (int j = 0; j < 5; j++) { System.out.print("A"); continue aaa; } System.out.print("B"); } System.out.print("C"); AAAAAAAAAAC ---- Etykietowane "continue" i "break" muszą być wewnątrz pętli, które mają te same nazwy etykiet. W przeciwnym razie wystąpi błąd kompilacji. ---- finally ------- Nawet jeśli w "try" jest "return", to blok "finally" jest wywoływany zaraz po wyrażeniu "return". class A { void f() { try { System.out.println("1"); return; } finally { System.out.println("2"); } } } public class Main { public static void main(String[] args) { new A().f(); System.out.println("3"); // 1 // 2 // 3 } ---- class A { void f() { try { System.out.println("1"); throw new Exception(); } catch (Exception e) { System.out.println("2"); } finally { System.out.println("3"); } } } public class Main { public static void main(String[] args) { new A().f(); System.out.println("4"); // 1 // 2 // 3 // 4 } } ---- class A { int g() { System.out.println("g()"); return 0; } int f() { try { System.out.println("1"); throw new Exception(); } catch (Exception e) { System.out.println("2"); return g(); } finally { System.out.println("3"); } } } public class Main { public static void main(String[] args) { new A().f(); System.out.println("4"); // 1 // 2 // g() // 3 // 4 } } ---- OK: try { } finally { } Błąd: try { } Błąd: try { } funkcja(); // nie można wstawić kodu pomiędzy try, a catch catch (Exception ex) { } Nie można użyć samego "try". Można użyć "try" z "catch", "try" z "finally" bez "catch". ---- Object -> Throwable -> Exception -> Wyjątki Object -> Throwable -> Exception -> RuntimeException -> Wyjątki Object -> Throwable -> Error - wyjątki, których nie trzeba przechwytywać (np. JVM skończyła się pamięć). Obiekty klas Exception, Error, RuntimeException i Throwable mogą być rzucone jako wyjątki. ---- try { } catch (IOException e) { } catch (FileNotFoundException e) { } Błąd kompilacji. Bardziej szczegółowy exception musi być przed mniej szczegółowym. ---- Wyjątki, które może rzucać metoda muszą zostać wyszczególnione w jej deklaracji (poza wyjątkami, które są dziedziczone po klasie RuntimeException). ---- Błąd: void f() { g(); } void g() { throw new IOException(); } OK: void f() throws IOException { g(); } void g() throws IOException { throw new IOException(); } OK: void f() { try { g(); } catch (IOException e) { } } void g() throws IOException { throw new IOException(); } OK: void f() { try { g(); } catch (IOException e) { } } void g() throws IOException { throw new IOException(); } OK: void f() throws Exception { g(); } void g() throws Exception { throw new IOException(); } ---- Nawet jeśli metoda deklaruje, że może rzucać RuntimeException bądź Error, metoda wywołująca nie jest zobligowana do przechytywania bądź deklaracji rzucania tych wyjątków. Wyjątki będące podklasami Error bądź RuntimeException są wyjątkami niesprawdzanymi i jako takie nie muszą być obsługiwane poprzez try/catch bądź zgłaszane w deklaracji metody (throws). public void f() { throws new NullPointerException(); // OK, bo pochodzi od RuntimeException } "finally" nie zostanie wykonane, jeśli JVM zostaje zamknięta (np. poprzez wywołanie System.exit()). ---- public class MyProgram { public static void throwit() { throw new RuntimeException(); } public static void main(String[] args) { try { System.out.println("Hello world "); throwit(); System.out.println("Done with try block "); } finally { System.out.println("Finally executing "); } } } Program wypisze: Hello world, Finally executing, a potem RuntimeException Asercje ------- Asercje są typowo aktywne, gdy program jest testowany i debuggowany, ale są nieaktywne do czasu ich włączenia (enable) w innym razie. Asercje rzucają AssertionError. private void f() { assert (y > x); // kod, który zakłada, że y jest większe od x } ---- private void g() { assert (y > x): "y is " + y + " x is " + x; } assert(wyrażenie, które zwraca boolean): wyrażenie, które może być skonwertowane na String Poprawne Niepoprawne ----------------------------------------------------------------------------------------- : "x is " + x : void public int go() { return 1; } public void go() { } : go (); : go(); : new Foo(); : Foo f; Od Javy 1.4 identyfikator "assert" jest słowem zarezerwowanym. Dla wstecznej kompatybilności, można wybrać, czy kompilowany program ma traktować assert w nowy sposób, czy nie. W Javie 1.4 assercje są domyślnie zablokowane. javac -source 1.4 Main -- kompiluje z włączonymi asercjami java -ea Main -- uaktywnia asercje podczas wykonywania programu java -enableassertions Main -- to samo co wyżej java -da Main -- dezaktywuje asercje podczas wykonywania programu java -disableeassertions Main -- to samo co wyżej Jest to zachowanie domyślne, więc nie trzeba używać powyższych flag. Za pomocą "da" i "ea" można selektywnie włączać i wyłączać asercje dla różnych klas bądź pakietów. java -ea -da:com.geeksanonymous.Foo -- uruchamia asercje wszędzie, blokuje w klasie "Foo" java -ea -da:com.geeksanonymous -- uruchamia asercje wszędzie, blokuje w pakiecie com.geekanonymous i podpakietach pakietu com.geekanonymous java -ea -dsa -- uruchamia asercje wszędzie, oprócz klas systemowych Flagi są interpretowane od lewej do prawej. Nie ma tu priorytetów. AssertionError jest podklasą Throwable. Nie powinno się przechwytywać wyrzuconej asercji poprzez try/catch. AssertionError nie dostarcza dostępu do obiektu, który wygenerował go. Wszystko co dostajemy, to String message. Appropriate/correct use of assertion - właściwe użycie asercji; nie mylić z legal. Prawidłowa kompilacja programu z asercją nie oznacza, że została ona użyta zgodnie z zaleceniami firmy Sun. Niewłaściwe (niezalecane przez Sun) użycie asercji: 1. Nie używaj asercji żeby walidować argumenty metod publicznych. public void f(int x) { assert (x > 0); } Metody publiczne mogą być wywoływane z poziomu kodu, którego nie kontrolujemy. Domyślnie asercje są wyłączone, więc nie wiadomo, czy zostaną sprawdzone. Do walidacji argumentów metod publicznych nie powinno się używać asercji. 2. Można używać asercji do walidacji argumentów metod prywatnych. privat void f(int x) { assert (x > 0); } 3. Nie używaj asercji do walidacji argumentów linii komend. 4. Używaj asercji, nawet w metodach publicznych, do sprawdzania case'ów, o których wiesz, że nigdy się nie wydarzą. switch (x) { case 2: y = 3; break; case 3: y = 17; break; default: assert false; // Nie spodziewamy się tu kiedykolwiek dojść } 5. Nie używaj asercji, które mogą powodować efekt uboczny public void f() { assert (g()); } public boolean g() { x++; return true; } Wyrażenia asercji powinny zostawiać program w takim samym stanie, w jakim był przed wykonaniem tego wyrażenia. Czasem dobrą praktyką jest wyrzucenie AssertionError własnoręcznie. ********************************************************************************* Enkapsulacja class A { public int left = 9; public int right = 3; public void setLeft(int leftNum) { left = leftNum; right = leftNum / 3; } } Czy wartość "right" będzie zawsze stanowiła 1/3 wartości "left"? Odpowiedź: Nie, gdyż zmienne "right" i "left" są publiczne i ktoś może nadać im dowolną wartość, omijając metodę setLeft(). IS-A ---- Dziedziczenie. Mustang jest typem konia, więc mówimy "Mustang IS-A Horse". public class Vehicle { } public class Car extends Vehicle { } public class Subaru extends Car { } "Car extends Vehicle" = "Car IS-A Vehicle" "Subaru extends Car" = "Subaru IS-A Car" "Subaru IS-A Vehicle" jeżeli A instanceof B, wtedy można powiedzieć "A IS-A B", nawet jeśli A bezpośrednio nie rozszerza B. HAS-A ----- A HAS-A B, jeśli w kodzie klasy A zawarto referencję do instancji klasy B. public class Animal { } public class Horse extends Animal { private Halter myHalter; } A Horse IS-A Animal. A Horse HAS-A Halter. Overridden and Overloaded Methods --------------------------------- Methods can be overloaded or overridden, but constructors can be only overloaded. Overloaded methods and constructors let you use the same method name (or constructor) but with different argument lists. Overriding lets you redefine a method in a subclass, when you need new subclass-specific behavior. Zasady dotyczące przesłaniania (override) metod: - lista argumentów metody przesłanianej i przesłaniającej muszą być takie same, - typy zwracane muszą być takie same, - access level (public, ...) nie mogą być bardziej restrykcyjne w metodzie przesłaniającej - mogą być za to mniej restrykcyjne, - metoda przesłaniająca nie może rzucać nowych bądź szerszych wyjątków sprawdzanych niż te, które zostały zadeklarowane w metodzie przesłanianej, - metoda przesłaniająca może wyrzucać węższe bądź mniej wyjątków, - nie można przesłonić metody finalnej, - jeśli metoda nie może być dziedziczona, nie można jej nadpisać. Przykłady nieprawidłowego przesłaniania (override): public class Animal { public void eat() { } } błędny kod | przyczyny ------------------------------------+--------------------------------------------------- private void eat() { } | Bardziej restrykcyjny access modifier | public void eat() throws | Deklaruje sprawdzane wyjątki niezadeklarowane IOException { } | w wersji metody w superklasie | public void eat(String food) { } | Legalne przeciążenie (overload), ale nie override | ponieważ zmieniono listę argumentów | public String eat() { } | Ani override (inny typ zwracany), | ani overload (brak zmiany w liście argumentów) ------------------------------------+---------------------------------------------------- Zasady dotyczące przeciążania (overloading): - przeciążone metody muszą mieć inne listy argumentów, - przeciążone metody mogą mieć zmieniony typ zwracany, - przeciążone metody mogą mieć zmieniony access modifier (public, ...), - przeciążone metody mogą deklarować nowe bądź szersze sprawdzane wyjątki, - metoda może być przeciążona w tej samej klasie bądź w podklasie. Legalne przeciążenia metody: public void changeSize(int size, String name, float pattern) { } ---- public void changeSize(int size, String name) { } public int changeSize(int size, float pattern) { } public void changeSize(float pattern, String name) throws IOException { } ---- class Animal { } class Horse extends Animal { } class UseAnimals { public void doStuff(Animal a) { System.out.println("Animal"); } public void doStuff(Horse h) { System.out.println("Horse"); } public static void main (String[] args) { UseAnimals ua = new UseAnimals(); Animals animalObj = new Animal(); Horse horseObj = new Horse(); ua.doStuff(animalObj); ua.doStuff(horseObj); Animal animalRefToHorse = new Horse(); ua.doStuff(animalRefToHorse); } } // Animal // Horse // Animal Typ referencji (nie typ obiektu) decyduje, która przeciążona metoda zostanie wywołana. Którą overriden metodę wywołać decydowane jest podczas wykonywania programu. Którą overloaded metodę wywołać decydowane jest podczas kompilacji na podstawie typu referencji. Polimorfizm nie określa, która overloaded metodę wywołać. Polimorfizm wchodzi w grę, podczas podejmowania decyzji, którą overriden metodę wywołać. public class Animal { public void eat() { System.out.println("Animal"); } } public class Horse extends Animal { public void eat() { system.out.println("Horse"); } public void eat(String s) { System.out.println("Horse " + s); } } Metoda wywołania | Rezultat -----------------------------------+------------------------------------------------------------ Animal a = new Animal(); | Animal a.eat(); | | Horse h = new Horse(); | Horse h.eat(); | | Animal ah = new Horse(); | Horse <- polimorfizm pracuje (analizowany jest typ obiektu, ah.eat(); | a nie typ referencji) | Horse he = new Horse(); | Horse Apples he.eat("Apples"); | | Animal a2 = new Animal(); | Błąd kompilacji a2.eat("treats"); | | Animal ah2 = new Horse(); | Błąd kompilacji (kompilator nie patrzy na to, że aktualny | obiekt może być Horse w trakcie ah2.eat("Carrots"); | działania programu) | -----------------------------------+----------------------------------------------------------- Overloaded Method Overriden Method -----------------------+---------------------------------------+------------------------------- argument list | Must change | Must not change | | return type | Can change | Must not change | | exceptions | Can change | Can reduce or eliminate | | Must not throw new or broader | | checked exceptions | | access | Can change | Must not make more restrictive | | (can be less) | | invocation | Reference type determines which | Object type determines which | | method is selected. | overloaded version is selected. | Happens at runtime. | Happens at compile time. | -----------------------+---------------------------------------+------------------------------- public class Foo { void doStuff() { } } class Bar extends Foo { void doStuff(String s) { } } Kod z referencją do Foo może wywołać tylko bezargumentową wersję doStuff. Kod z referencją do Bar może wywołać obie wersje doStuff. ---- Każda klasa (nawet abstrakcyjna) musi mieć konstruktor. Nie oznacza to, że konstruktor musi być jawnie zadeklarowany. ---- Horse h = new Horses(); powoduje: 1. Wywołanie konstruktora klasy Horse. 2. Wywołanie konstruktora klasy Animal. 3. Wywołanie konstruktora klasy Object. 4. Zmienne instancyjne Object dostają swoje przypisane wartości (jeśli jakieś mają). 5. Konstruktor Object kończy działanie. 6. Zmienne instancyjne Animal dostają swoje przypisane wartości (jeśli jakieś mają). 7. Konstruktor Animal kończy działanie. 8. Zmienne instancyjne Horse dostają swoje przypisane wartości (jeśli jakieś mają). 9. Konstruktor Horse kończy działanie. Tak wygląda to na stosie: 4. Object() 3. Animal() calls super() 2. Horse() calls super() 1. main() calls new Horse() ---- Zasady dotyczące konstruktorów: - Konstruktor może mieć dowolny access modifier, włączając w to private (konstruktor prywatny oznacza, że tylko kod wewnątrz klasy może utworzyć obiekt tej klasy - klasa taka musi mieć metodę statyczną bądź zmienną, która umożliwi dostęp do instancji stworzonej z wnętrza klasy). - Nazwa konstruktora musi być zgodna z nazwą klasy. - Konstruktor nie może mieć typu zwracanego. - Jest dozwolone (chociaż niemądre), żeby mieć metodę o takiej samej nazwie, jak nazwa klasy, ale która nie jest konstruktorem. Jeśli widzisz zwracany typ, masz do czynienia z metodą, a nie konstruktorem. - Jeśli nie zadeklarujesz jawnie konstruktora w klasie, kompilator automatycznie stworzy konstruktor domyślny. - Jeśli potrzebujesz konstruktor bezargumentowy, a stworzyłeś jakieś konstruktory argumentowe w swojej klasie, kompilator nie stworzy automatycznie konstruktora bezargumentowego. - Każdy konstruktor musi mieć jako pierwsze wyrażenie wywołanie przeciążonego konstruktora (this() bądź wywołanie konstruktora superklasy (super()). - Jeśli jawnie zdefiniujesz konstruktor i nie wywołasz w nim super(), kompilator wstawi automatycznie wywołanie super(). - Wywołanie super() może być bezargumentowe bądź może zawierać argumenty przekazane do super konstruktora. - Nie można wywoływać metod instancyjnych bądź mieć dostępu do zmiennych instancyjnych, dopóki nie uruchomi się super konstruktor. - Możesz użyć statycznych zmiennych i metod, chociaż można ich użyć wyłącznie jako część wywołania super() bądź this(). Przykład: super(Animal.DoThings())). - Klasa abstakcyjna ma konstruktor, który jest zawsze wywoływany, kiedy tworzona jest instancja konkretnej podklasy. - Interfejsy nie mają konstruktorów. Interfejsy nie są częścią drzewa dziedziczenia obiektu. - Konstruktor może być wywołany wyłącznie z innego konstruktora. Domyślny konstruktor: - ma taki sam access modifier jak klasa, - nie ma argumentów, - ma bezargumentowe wywołanie super konstruktora (super()). Kod klasy | Kod wygenerowany przez kompilator ------------------------------+---------------------------------------------------------------- class Foo { } | class Foo { | Foo() { // kompilator | super(); // kompilator | } // kompilator | } | | class Foo { | class Foo { Foo() { } | Foo() { } | super(); // kompilator | | public class Foo { } | class Foo { | public Foo() { // kompilator | super(); // kompilator | } // kompilator | } | | class Foo { | class Foo { Foo(String s) { } | Foo(String s) { } | super(); // kompilator | } | } | | class Foo { | Kompilator nic nie musi dodawać Foo(String s) { | super(); | } | } | | | class Foo { | class Foo { void Foo() { } | void Foo() { } // to metoda, a nie konstruktor } | Foo() { // kompilator | super(); // kompilator | } // kompilator | } ------------------------------+----------------------------------------------------------------- Jeśli w superklasie nie ma bezargumentowego konstruktora, wtedy w podklasie nie ma możliwości użycia domyślnego konstruktora dostarczanego przez kompilator. class Clothing { Clothing(String s) { } } class TShirt extends Clothing { } // nie można skompilować ---- Z konstruktora można wywołać jedynie metody statyczne. Nie można wywoływać metod instancyjnych, dopóki nie zakończy się działanie superkonstruktora. Wywołanie super() bądź this() musi być pierwszym poleceniem w konstruktorze. Nie można zatem użyć jednocześnie i super() i this(). Jeśli jawnie użyjemy this(), to kompilator nie dostawi automatycznie super(). ---- public class Foo { void go() { } } public class Bar extends Foo { String go() { // błąd - zmieniono tylko typ zwracany, // a nie zmieniono listy argumentów return null; } } public class Foo { void go() { } } public class Bar extends Foo { String go(int x) { // OK return null; } } Metody overloaded mogą zmieniać typ zwracany, ale metody overriding nie mogą. Protected jest mniej restrykcyjny niż domyślny. Zasady dotyczące wartości zwracanych: 1. Można zwrócić null, które mają typ zwracany będący referencją do obiektu. public Button doStuff() { return null; } 2. Tablica może być zwracana. public String[] go () { return new String[] { "Ala", "Ola", "Majka" }; } 3. W metodzie z prymitywnym typem zwracanym, można zwrócić każdą wartość bądź zmienną, która może być niejawnie skonwertowana do zadeklarowanego typu zwracanego. public int foo() { char c = 'c'; return c; } 4. W metodzie z prymitywnym typem zwracanym, można zwrócić każdą wartość bądź zmienną, która może być jawnie rzutowana do zadeklarowanego typu zwracanego. public int foo() { float f = 32.5f; return (int)f; } 5. Nie można zwracać niczego z metody void public void bar() { return "ala ma kota"; // błąd } 6. W metodzie z obiektowym typem zwracanym, można zwrócić każdy obiekt, który może być niejawnie rzutowany do zadeklarowanego typu zwracanego. public Animal getAnimal() { return new Horse(); // Horse extends Animal } public Object getObject() { int[] nums = {1,2,3}; return nums; // zwraca tablicę intów, która jest obiektem Object } public interface Chewable { } public class Gum implements Chewable { } public class TestChewable { public Chewable getChewable { // metoda z interfejsowym typem zwracanym return new Gum(); // zwraca implementatora interfejsu } } ---- Jeśli klasa ma zadeklarowany jako zwracany typ klasy abstrakcyjnej bądź interfejsu, może ona zwrócić każdy obiekt, który spełnia relację IS-A (instanceof zwraca true): public abstract class Animal { } public class Bear extends Animal { } public class Test { public Animal go() { return new Bear(); // OK, Bear IS-A Animal } } ---- Rzutowanie public short foo() { return (short) 90 + 900000; // błąd kompilacji } public short goo() { return (short) (90 + 900000); // OK } *************************************************** String s = new String(); s = "abcdef"; String s = new String("abcdef"); String s = "abcdef"; Immutable - niezmienny. s = s.concat(" more stuff"); ---- String s = "abcdef"; // tworzy nowy obiekt String, który ma wartość "abcdef"; // "s" odnosi się do tego obiektu String s2 = s; // tworzy drugą zmienną referencyjną odnoszącą się // do tego samego obiektu String s = s.concat(" more"); // tworzy nowy obiekt String, który ma wartość "abcdef more"; // "s" wskazuje na niego // (zmienna referencyjna "s" wskazuje teraz na ten nowy obiekt; // "s2" wciąż wskazuje na // obiekt "abcdef") ---- String x = "Java"; x.concat(" Rules!"); System.out.println("x = " + x); // Java String x = "Java"; x = x.concat(" Rules!"); System.out.println("x = " + x); // Java Rules! ---- String s1 = "spring "; String s2 = s1 + "summer "; s1.concat("fall "); s2.concat(s1); s1 += "winter "; System.out.println("s1 + " " + s2); // spring winter spring summer ---- Aby zaoszczędzić pamięć, Stringi umieszczane są w obszarze pamięci zwanym "String constant pool". Jeśli nowy string jest taki sam jak string znajdujący się już w pamięci, to nie przydziela się mu nowego miejsca - referencja do tego nowego obiektu wskazuje na miejsce ze starym stringiem w pool. Wykonywane jest to na etapie kompilacji. Taki sposób postępowania pozwala zaoszczędzić pamięć. String s = "abc"; // tworzy nowy obiekt String i jedną zmienną referencyjną // "abc" wędruje to "pool", a "s" wskazuje na niego String s = new String("abc"); // tworzy dwa obiekty i jedną referencję // Java tworzy nowy obiekt String w normalnej // (nie "pool") pamięci i "s" // odnosi się do niego; literał "abc" jest umieszczany w "pool" ---- String x = new String("xyz"); y = "abc"; x = x + y; Powyższy kod tworzy 4 obiekty. ---- public char charAt(int index); String x = "airplane"; System.out.println(x.charAt(2)); // r public String concat(String s); String x = "taxi"; System.out.println(x.concat(" cab")); // taxi cab += = String x = "library"; System.out.println(x + " card"); // library card String x = "Atlantic"; x += " ocean"; System.out.println(x); // Atlantic ocean public Boolean equalsIgnoreCase(String s); String x = "Exit"; System.out.println(x.equalsIgnoreCase("EXIT")); // true System.our.println(x.equalsIgnoreCase("tixe")); // false public int length(); String x = "01234567"; System.out.println(x.length()); // 8 Stringi mają metodę length(). Tablice mają atrybut length. public String replace(char old, char new); String x = "oxoxoxoxox"; System.out.println(x.replace('x', 'X')); // oXoXoXoXoX public String substring(int begin); public String substring(int begin, int end); // uwaga: "end" liczony jest od 1, a nie od zera !!! // Uwaga: nazwa metody to substring(), // a nie subString() String x = "0123456789"; System.out.println(x.substring(5)); // 56789 System.out.println(x.substring(5, 8); // 567 public String toLowerCase(); String x = "A New Moon"; System.out.println(x.toLowerCase()); // a new moon public String toString(); Wszystkie obiekty w Javie muszą mieć metodę toString(). String x = "big surprise"; System.out.println(x.toString()); public String toUpperCase(); String x = "A New Moon"; System.out.println(x.toUpperCase()); // A NEW MOON public String trim(); String x = " hi "; System.out.println(x + "x"); // hi x System.out.println(x.trim() + "x"); // hix ---- StringBuffer String x = "abc"; x.concat("def"); System.out.println("x = " + x); // x = abc String x = "abc"; x = x.concat("def"); System.out.println("x = " + x); // x = abcdef StringBuffer sb = new StringBuffer("abc"); sb.append("def"); System.out.println("sb = " + sb); // sb = abcdef StringBuffer sb = new StringBuffer("abc"); sb.append("def").reverse().in public synchronized StringBuffer append(String s); StringBuffer sb = new StringBuffer("set "); sb.append("point"); System.out.println(sb); // set point StringBuffer sb = new StringBuffer("pi = "); sb.append(3.14159f); System.out.println(sb); // pi = 3.14159 public synchronized StringBuffer insert(int offset, String s); StringBuffer sb = new StringBuffer("01234567"); sb.insert(4, "---"); System.out.println(sb); // 0123---4567 public synchronized StringBuffer reverse(); StringBuffer sb = new StringBuffer("A man a plan a canal Panama"); sb.reverse(); System.out.println(sb); // amanaP lanac a nalp a nam A public String toString(); StringBuffer sb = new StringBuffer("test string"); System.out.println(sb.toString()); // test string W przeciwieństwie do obiektów String, obiekty StringBuffer mogą być zmieniane. ---- String x = "abc"; String y = x.concat("def").toUpperCase().replace('C', 'x'); // chained methods; wynikiem // jest typ metody // po prawej stronie System.out.println("y = " + y); // ABxDEF ----