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

----
w3cw3c
automatyka przemysłowa