sobota, 24 września 2011

1. Deklaracje, inicjalizacje oraz zakres widoczności

Napisz kod w którym deklarujesz interfejs, implementujesz lub rozszerzasz jeden lub więcej interfejsów. Napisz kod w którym deklarujesz klasę abstrakcyjną oraz rozszerzasz klasę abstrakcyjną.

1. Deklaracje, inicjalizacje oraz zakres widoczności

Napisz kod w którym deklarujesz klasy (włączając klasy abstrakcyjne I wszystkie formy klas zagnieżdżonych), interfejsy, typy wyliczeniowe oraz właściwie wykorzystujesz pakiety i importy (włączając import statyczny)

1. Klasy
W języku Java klasy dzielą się na dwa typy: klasy zewnętrzne oraz wewnętrzne (czyli osadzone w innej klasie, metodzie,  interfejsie lub typie wyliczeniowym). W jednym pliku .java może znajdować się co najwyżej jedna publiczna klasa oraz dowolna ilość klas o zasięgu domyślnym (bez modyfikatora widoczności). Plik ten wówczas musi nazywać się dokładnie tak, jaką nazwę posiada klasa publiczna. Jeśli w pliku nie zostały zdefiniowane żadne publiczne klasy, plik ten może posiadać dowolną nazwę. Możliwe modyfikatory dla klasy zewnętrznej:

  • public. Klasa opatrzona tym modyfikatorem jest widoczna dla wszystkich klas, nawet tych spoza pakietu, zarówna dla dziedziczenia, jak i utworzenia jej instancji jako składowej innej klasy.
  • brak modyfikatora - zakres domyślny. Klasa opatrzona takim modyfikatorem widoczna jest dla innych klas znajdujących się w tym samym pakiecie. Przy tym zakresie widoczności instancje tejże klasy można utworzyć wewnątrz innej klasy tylko wówczas, gdy klasy te znajdują się w jednym pakiecie. 
  • strictfp. Modyfikator ten związany jest z liczbami zmiennoprzecinkowymi. Na potrzeby egzaminu nie potrzebna jest głębsza wiedza na ten temat poza tym, iż może być on stosowany tylko dla klas i metod, nigdy dla składowych klasy.
  • final. Modyfikator ten oznacza, iż klasa ta nie może być rozszerzona przez żadną inną klasę.
  • abstract. Jeśli klasa oznaczone jest tym modyfikatorem, wówczas nie można utworzyć instancji tej klasy. Klasa taka nadaje się wyłącznie do odziedziczenia jej przez inną klasę. Klasa musi zostać oznaczona słówkiem kluczowym abstract, jeśli jakaś metoda wewnątrz klasy jest również abstrakcyjna. Jednak klasa abstrakcyjna nie musi wcale posiadać metod abstrakcyjnych. Więcej szczegółów na temat klas abstrakcyjnych troszkę później.
Jak widać modyfikatory final oraz abstract są sobie całkowicie sprzeczne, przez co nie możliwe jest opatrzenie klasy tymi dwoma modyfikatorami jednocześnie, gdyż nie miałoby to najmniejszego sensu (final zakazuje nam rozszerzenia tejże klasy, natomiast abstract tego oczekuje).

Poprawne deklaracje klas zewnętrznych:

public abstract class A {}
final class B{}
abstract class C{}
abstract strictfp class D{}
final strictfp class E{}
Niepoprawne deklaracje klas zewnętrznych:
private class A{} // klasa zewnętrzna nie może być prywatna
protected class B{} // klasa zewnętrzna nie może być chroniona
static class C{} // klasa zewnętrzna nie może być statyczna
final abstract class D{} // klasa zewnętrzna nie może być jednocześnie opatrzona modyfikatorami final abstract
synchronized class E{} // słowa kluczowego 'synchronized' nie można stosować do klas (zarówno zewnętrznych jak i wewnętrznych)

Klasy wewnętrzne, są to klasy osadzone wewnątrz innej klasy, metody, interfejsu lub typu wyliczeniowego. Klasa taka posiada pełne prawa do wszystkich elementów klasy zewnętrznej, nawet tych oznaczonych zakresem widoczności 'private'. Relacja ta działa w obie strony, jednak aby klasa zewnętrzna mogła odczytać pola klasy wewnętrznej, lub wykonać jej metodę, musi posiadać ona instancję tejże klasy. Jeśli obiekt zostanie utworzony, wówczas posiada ona dostęp również do składowych prywatnych.


public class OuterClass {
private int a = 1;

public static void main(String... args) {
OuterClass oc = new OuterClass();
OuterClass.InnerClass ic = oc.new InnerClass();
System.out.println("b: " + ic.b);
ic.b++;
ic.innerMethod();
}

public class InnerClass {
private int b = 2;

public void innerMethod() {
System.out.println("a: " + a + " b: " + b);
}
}
}



Jak widać zarówno klasa wewnętrzna jak i zewnętrzna mają do siebie pełne zaufanie, i pozwalają sobie nawzajem na dostęp do swych prywatnych składowych. Jeśli obiekt klasy wewnętrznej tworzony jest bezpośrednio w klasie zewnętrznej ( tak jak ma to miejsce w przykładzie) wówczas przy tworzeniu obiektu

OuterClass.InnerClass ic = oc.new InnerClass();

można nie podawać nazwy klasy zewnętrznej:

InnerClass ic = oc.new InnerClass();

Aby utworzyć instancję klasy wewnętrznej, zawsze musi istnieć obiekt klasy zewnętrznej.  Możliwe są dwa sposoby utworzenia instancji klasy wewnętrznej:

OuterClass oc = new OuterClass();
OuterClass.InnerClass ic = oc.new InnerClass();


lub

OuterClass.InnerClass ic2 = new OuterClass().new InnerClass();


Klasa wewnętrzna nie może posiadać metod statycznych, chyba że sama klasa jest klasą statyczną !
Słowo kluczowe 'this' wskazuje zawsze na aktualny obiekt, więc jeśli użyte jest w metodzie klasy wewnętrznej, to za jego pomocą nie możemy odwołać się do pola lub metody klasy zewnętrznej.

this.a;

 Aby odwołać się do aktualnego obiektu klasy zewnętrznej, należy słowo 'this' poprzedzić nazwą klasy, a więc:

OuterClass.this.a;

Słowa 'this' nie można używać w metodach statycznych (błąd kompilacji).

Możliwe modyfikatory klas wewnętrznych:
  • public. 
  • private. 
  • protected. 
  • domyślny.
  • final.
  • abstract.
  • static.
  • strictfp.

Przypadek 1.
Klasa wewnętrzna  o domyślnym zasięgu widoczności. Kod działa poprawnie, ponieważ obie klasy znajdują się w tym samym pakiecie. Gdyby jednak klasy znajdowały się w różnych pakietach, wówczas kod nie skompilowałby się.

package pl.lukasz.ocjp.packA;
public class B {
class C{}
}


package pl.lukasz.ocjp.packA;
import pl.lukasz.ocjp.packA.B.C;
public class A {
void method() {
B b = new B();
C c = b.new C();
}
}


Przypadek 2.
Klasa wewnętrzna o zasięgu widoczności 'protected'. Klasy znajdują się w różnych pakietach, jednak klasa A rozszerza klasę B, dlatego też możliwe jest utworzenie obiektu klasy wewnętrznej C. Wymagany jest jednak publiczny konstruktor dla klasy wewnętrznej.


package pl.lukasz.ocjp.packB;
public class B {
protected class C {
public C() {
}
}
}

package pl.lukasz.ocjp.packA;
import pl.lukasz.ocjp.packB.B;
public class A extends B{
void method() {
A a = new A();
C c = a.new C();
}
}


Przypadek 3.
Kod ten nie kompiluje się. Powodem tego jest to, iż klasa wewnętrzna C opatrzona jest modyfikatorem widoczności 'protected', natomiast klasa A nie dziedziczy po klasie B. Błąd kompilacji występuje już w linijce:
"import pl.lukasz.ocjp.packB.B.C;" ponieważ nie jest widoczna dla klasy A, i dlatego nie może zostać zaimportowana.

package pl.lukasz.ocjp.packB;
public class B {
protected class C {
public C() {
}
}
}


package pl.lukasz.ocjp.packA;
import pl.lukasz.ocjp.packB.B;
import pl.lukasz.ocjp.packB.B.C;
public class A {
void method() {
B b = new B();
C c = b.new C();
}
}



Przypadek 4.
Kod ten nie kompiluje się, ponieważ klasa wewnętrzna C oznaczona jest jako prywatna, dlatego też nie ma możliwości utworzenia jej instancji w innym miejscu niż w klasie zewnętrznej.

package pl.lukasz.ocjp.packB;
public class B {
private class C {
public C() {
}
}
}

package pl.lukasz.ocjp.packA;
import pl.lukasz.ocjp.packB.B;
public class A {
void method() {
B b = new B();
C c = b.new C();
}
}


Przypadek 5.
Kod ten nie kompiluje się, ponieważ klasa wewnętrzna C jest prywatna, wiec nie możliwy jest jej import. Błąd uwidoczni się już w linijce: "import pl.lukasz.ocjp.packB.B.C;"

package pl.lukasz.ocjp.packB;
public class B {
private class C {}

public C getC() {
return new C();
}
}

package pl.lukasz.ocjp.packA;
import pl.lukasz.ocjp.packB.B;
import pl.lukasz.ocjp.packB.B.C;
public class A {
void method() {
B b = new B();
C c = b.getC();
}
}




Przypadek 6.
Kod ten działa poprawnie, ponieważ instancja prywatnej klasy wewnętrznej C tworzona jest wewnątrz jej klasy zewnętrznej. Następnie za pomocą metody klasy B zwracana jest ona do metody w klasie A, gdzie przypisana zostaje do Object. Przypisanie do typu 'C' spowodowałoby błąd kompilacji.

package pl.lukasz.ocjp.packB;
public class B {
private class C {}

public C getC() {
return new C();
}
}

package pl.lukasz.ocjp.packA;
import pl.lukasz.ocjp.packB.B;

public class A {
void method() {
B b = new B();
Object c = b.getC();
}
}




Klasa wewnętrzna może zostać zdefiniowana wewnątrz metody, jednak wówczas może zostać użyta tylko wewnątrz tejże metody. Wewnątrz takiej klasy, nie można korzystać z zmiennych metody, chyba że  oznaczone są jako 'final'.


package pl.lukasz.ocjp;
public class A {
int a  = 6;

public void method() {
int b = 1;
final int c = 3;
class Inner {
void m1() {
// Wszystko OK, możliwy dostęp do składowej klasy
System.out.println(a);
// Błąd kompilacji, mie można odwołać sie do zmiennej metody
System.out.println(b);
// Wszystko OK, zmienna metody c oznaczona jest jako final
System.out.println(c);
}
}
}
}


Przy tworzeniu obiektu klasy wewnętrznej zdefiniowanej w metodzie, ważna jest kolejność:


public void method() {
  // Bład kompilacji. Co to B? Nigdzie jeszcze nie zostało zadeklarowane
  B b1 = new B();

     class B{}
  // Wszystko OK, defininicja klasy B znajduje się powyżej
  B b2 = new B();
}



Klasa zdefiniowana wewnątrz metody oznaczona może zostać tylko jako: abstract, final lub strictfp. Niedozwolone są modyfikatory: public, private, protected, static, transient.
Metoda nie może zwracać obiektu zadeklarowanego jako klasa wewnątrz metody:


public A method() {
class A{}
return new A();
}



Poprawne jest natomiast:


public Object method() {
class A{}
return new A();
}



Klasy anonimowe.
Klasy anonimowe to klasy, których zawartość definiowana jest tuż przed otworzeniem obiektu. W klasach takich można przedefiniować metody, dokładać także nowe, jednak nie ma możliwości odwołania się do nowych metod z zewnątrz.


Runnable r = new Runnable() {

@Override
public void run() {
}


// Można zdefiniować nową motodę, jednak nie ma możliwości 

// wywołania jej z poziomu obiektu 'r'.   
public void method() {}

 }; // Po zamknieciu wymagany jest średnik ! Jego brak oznacza bład         
    //kompilacji.


Klasa anonimowa nie może implementować interfejsów ani rozszerzać klas. Natomiast dozwolone jest, aby po słówku 'new' znajdował się interfejs, a nie klasa, jednak wówczas pomiędzy nawiasami klamrowymi należy zdefiniować wszystkie metody interfejsu (jak zostało pokazane na powyższym przykładzie).

Klasy statyczne.
Klasa statyczna osadzona może zostać tylko wewnątrz innej klasy (nigdy wewnątrz metody). Ma ona dostęp tylko do zmiennych i metod statycznych klasy (nawet tych prywatnych).


public class OuterClass {

private static int a = 1;
private final int b = 2;

public static void main(String... args) {
InnerClass.m4();

InnerClass ic = new InnerClass();
ic.method1();
}

public static void m2() {
};

public void m3() {}

private static class InnerClass {
public InnerClass() {} // Konstruktor klasy statycznej

  public static void m4() {} // Klasa statyczna może posiadać metody statyczne
public void method1() {
System.out.println(a); // OK, a jest static
System.out.println(b); // Błąd kompilacji, b nie jest static
m2(); // OK, metoda statyczna
m3(); // Błąd kompilacji, m3() nie jest static
}
}
}



1. Interfejsy
W skład interfejsu mogą wchodzić:

  • stałe
  • klasy
  • metody abstrakcyjne
  • typy wyliczeniowe
  • inne interfejsy
Interfejs może być oznaczony jako:

  • public
  • zakres widoczności domyślny 
  • abstract
  • strictfp
Wszystkie metody w interfejsie domyślnie wewnątrz interfejsu domyślnie oznaczone są jako 'public abstract'.
Klasy zadeklarowane wewnątrz interfejsu domyślnie oznaczone są jako 'public static'.
Zmienne wewnątrz interfejsu są de facto stałymi, i muszą mieć modyfikatory 'public static final' (dołączane są automatycznie). Wartość dla tych stałych musi zostać zdefiniowana w momencie deklaracji.

Poprawne deklaracje wewnątrz interfejsu:

import java.io.IOException;
import java.io.Serializable;

public interface MyInterface {
void m1();
public void m2();
abstract void m3();
public abstract void m4() throws IOException;

int a = 1;
final int b = 2;
static int c = 3;
final static int d = 4;
public final static int e = 5;

public enum Animal {
DOG, CAT
}

class A extends Throwable implements Serializable{};

 interface D{}
}


Niepoprawne deklaracje wewnątrz interfejsu:


public interface MyInterface {
private void m1(); // Metoda nie może być prywatna.
protected void m2(); // Metoda nie może być chroniona.
void m3() {} // Metoda nie może posiadać ciała.
public MyInterface(); // Interfejs nie może mieć konstruktora
int a; // Wartość zmiennej musi być podana odrazu.

private class A{}; // Klasa nie może być prywatna
private interface B{}; // Interfejs nie może być prywatny
}



1. Typy wyliczeniowe
Typ wyliczeniowy zadeklarowany może być bezpośrednio w pliku ( na zewnątrz klasy), w klasie, interfejsie lub wewnątrz innego typu wyliczeniowego. Wewnątrz enuma mogą znajdować się:
  • konstruktory
  • zmienne
  • metody
  • klasy wewnętrzne
  • inne typy wyliczeniowe
Mając na uwadze to, iż w pliku może znajdować się tylko jedna rzecz z modyfikatorem 'public', niedozwolone jest:

public enum A { DOG, CAT}
public class B {}


Enum wewnątrz pliku (na zewnątrz innego typu) może posiadać modyfikatory:
  • public
  • domyślny
  • strictfp
Enum wewnątrz innego typu może posiadać modyfikatory:
  • public
  • domyślny
  • private
  • protected
  • static
  • strictfp
Enum nie może być zdefiniowany jako 'abstact', nawet jeśli ma w sobie abstrakcyjną metodę. W tym przypadku każdy obiekt typu wyliczeniowego musi zdefiniować metody abstrakcyjne. Jeśli enum posiada metodę abstrakcyjną, to musi istnieć w nim przynajmniej jedna stała wyliczeniowa.

public enum Animal {
 DOG {
@Override
public void voice() {
System.out.println("hauu");
}
},
CAT {
@Override
public void voice() {
System.out.println("mialllll");
}
};
        // Abstrakcyjna metoda wewnątrz typu wyliczeniowego
public abstract void voice();
}


Poprawne jest także:

enum B {
}

Jednak to już nie:

enum B {
public abstract void method();
}

ponieważ enum posiada metodę abstrakcyjną, natomiast nie posiada żadnej stałej wyliczeniowej.

Średnik po ostatnim typie wyliczeniowym jest obowiązkowy, gdy w enumie znajduje się coś jeszcze (np. metody). Stałe wyliczeniowe wewnątrz typu wyliczeniowego muszą być napisane jako pierwsze, dopiero poniżej metody. Typ wyliczeniowy może posiadać jedynie konstruktor opatrzony modyfikatorem private lub bez modyfikatora !
Jeśli utworzono konstruktor konstruktor z argumentami, to niepoprawne będzie:

public enum Animal {
DOG, CAT;
Animal(int number) {
}
}


Poprawnie zdefiniowany typ wyliczeniowy wyglądałby następująco:

public enum Animal {
DOG(1), CAT(2);
Animal(int number) {
}
}


Każdy typ wyliczeniowy dziedziczy z Enum<E> a ten z kolei z Object, dzięki temu w każdym typie wyliczeniowym dostępne są metody:

public static E[] values(); - zwraca tablice stałych wyliczeniowych
public static E valueOf(String name); - zwraca instancję typu wyliczeniowego E o podanej nazwie (np. DOG). Jeśli element nie zostanie znaleziony, rzucany jest wyjątek IllegalArgumentException.