6 marca 2008

SCJP - Deklaracja interfejsu

Deklaracja interfejsu, czyli streszczenie sekcji "Declaring an Interface" oraz "Declaring Interface Constants" – kontynuacja działań zapowiedzianych w artykule "Przygotowania do SCJP czas zacząć". Pisząc te rozdziały Książki autor miał najwyraźniej słaby dzień. Napisane są dosyć chaotycznie i wyjątkowo nie precyzyjnie, będę się więc posiłkował specyfikacją języka Java - "The Java Language Specification". Zaznaczę jeszcze, czego zapomniałem zrobić ostatnio pisząc o klasach, że pomijamy póki co interfejsy zagnieżdżone (ang. non top-level), do nich dojdziemy później. Pomijamy także adnotacje, które również są specjalnym rodzajem interfejsów.

Podobnie jak w przypadku klas, interfejsy (nie zagnieżdżone) możemy oznaczać modyfikatorem widoczności public, lub nie używać w ogóle modyfikatora widoczności – interfejs będzie wówczas widoczny tylko w swoim pakiecie. Modyfikatory private oraz protected nie są dozwolone, bo też nie mają w tym kontekście sensu.

Interfejs możemy także oznaczyć słówkiem kluczowym abstract, jednak - choć jest poprawne - nie ma to żadnego efektu i nie powinniśmy tego robić. Każdy interfejs i tak jest abstrakcyjny.

Ostatnim modyfikatorem, jaki możemy zastosować dla interfejsu nie zagnieżdżonego jest strictfp. Stałe zmiennoprzecinkowe wyliczane w czasie kompilacji są zawsze wyliczane zgodnie z zasadami strictfp, tak więc modyfikator ten ma bodaj tylko ten skutek, że propaguje się na klasy zdefiniowane wewnątrz interfejsu.

Dodam jeszcze, choć chyba jest to jasne dla tych, którzy programują w Javie, a nie tylko przygotowują się do SCJP, że interfejsy są sposobem, aby skorzystać z wielodziedziczenia. Zarówno interfejs może dziedziczyć (słówko kluczowe extends) z wielu interfejsów jak i klasa może implementować (słówko kluczowe implements) wiele interfejsów. Zdaje się zresztą, że właśnie po to wprowadzono interfejsy do języka Java, aby umożliwić bezproblemowe użycie wielodziedziczenia (choć pewne problemy nadal pozostają).

Interfejs może zawierać deklaracje metod abstrakcyjnych, stałych, klas oraz innych interfejsów. Deklarując w interfejsie metodę nie podajemy żadnych modyfikatorów. Co prawda kod skompiluje się jeśli podamy explicite modyfikatory public albo abstract, ale nie mają one żadnego skutku, jako że tak czy inaczej wszystkie metody zadeklarowane w interfejsie są oznaczone tymi modyfikatorami implicite. Specyfikowanie tych modyfikatorów jest w złym stylu i nie należy tego robić – tak mówi specyfikacja języka Java 5. Deklarując metodę w interfejsie nie możemy zastosować żadnego innego modyfikatora, ale już implementując zadeklarowane metody w klasie możemy dodać modyfikatory final, strictfp lub native, dotyczą one bowiem implementacji a nie samego kontraktu.

Każda zmienna zadeklarowana w interfejsie musi być de facto stałą i jest implicite public, static oraz final (chociaż wydaje mi się, że stała nie publiczna też mogłaby mieć tu sens). Możemy podać dowolną kombinację tych modyfikatorów explicite, jednak nie ma to żadnego skutku – tak czy inaczej one tam są i nie da się tego zmienić. Nie są dozwolone żadne inne modyfikatory.

Klasy oraz interfejsy zadeklarowane wewnątrz deklaracji interfejsu są implicite oznaczone jako static i public. Typy zagnieżdżone są jednak poza zakresem dyskusji tego artykułu – dojdziemy do nich później.

6 komentarzy:

Łukasz Lenart pisze...

Zostałem trochę zaskoczony, gdyż umkło mojej uwadze, że interfejsy mogą dziedziczyć z kilku innych interfejsów, kiedy czytałem książkę do SCJP. Chyba jeszcze raz ją przejrzę w tym kontekście ;-)

Mariusz Lipiński pisze...

Nie tylko mogą, ale jest to, tj. wielodziedziczenie jednym z głównych uzasadnień dla ich istnienia w języku Java. I chodzi tu o wiele więcej niż tylko o proste stwierdzenie, że interfejs może dziedziczyć z wielu interfejsów. Widzę, że błędnie założyłem, że są to zagadnienia powszechnie znane. Naprawię ten błąd w jakimś przyszłym artykule.

przemelek pisze...

Piszesz, że stałe niepubliczne miały by sens. IMHO niewielki, a miejscami kłóciło by się to nawet z filozofią języka.
Interfejs powinien dostarczać zamkniętą specyfikację punktów styku między wnętrzem klasy, a światem zewnętrznym. Dlatego stałe dostarczane przez interfejs muszą być publiczne, bo są częścią tej specyfikacji - np. stałymi wykorzystywanymi w wywołaniach metod.

Czasem wykorzystuje się interfejs inaczej, czyli jako składowisko stałych, ale to raczej niezbyt dobry pomysł. Prowadzi do np. takich kuriozów jak poniżej.
public interface AppConstants {
String APP_NAME = "Aplikacja";
}

public class Application implements AppConstants {
...
}

public class ApplicationUtil implements AppConstants {
....
}

Z punktu widzenia Java'y tak Application jak i ApplicationUtil są AppConstants'ami, przez co legalne jest np:
AppConstants constants = new Application();
AppConstants constants2 = new ApplicationUtil();

Tylko do czego użyć takiego czegoś?

Taki interfejs do trzymania stałych jest udawanym interfejsem ;-)

Nie zgodziłbym się też co do tego, że chodzi o wielodziedziczenie. Z praktyki widzę, że wielodziedziczenie jest dość rzadko potrzebne i używane.
IMHO głównym celem istnienia interfejsów jest podniesienie poziomu abstrakcji kodu.
Np. mamy śliczny kod wyświetlający coś, który bardzo ładnie pracuje z Iterator'em, mamy też klasę, która przechowuje znaki, zachowuje się jak String czy StringBuilder/StringBuffer (implementuje więc CharacterSequence) i chcemy wyświetlić ją tak jak potrafi to nasz śliczny kod. Bez interfejsów trzeba by było skopiować ten kod i przystosować dla CharacterSequence, ale dzięki interfejsom implementujemy Iterator, dopisujemy brakujące metody i voila!

Mariusz Lipiński pisze...

A co powiesz o stałych zadeklarowanych w interfejsie jako pewne działanie arytmetyczne na innych stałych tegoż interfejsu? Naturalnie chcemy aby tylko wynik tego działania był publiczny, ale chcemy nadal zachować w zapisie formę działania arytmetycznego dla czytelności. Nie mam dobrego przykładu na podorędziu, ale chodzi o coś w tym stylu:

interface AnyInterface {
(private) int X = 2;

(private) int Y = 3;

int Z = X * (Y + 1);

...
}

Poza tym, nie twierdzę, że powinniśmy mieć możliwość deklarowania zmiennych prywatnych w interfejsie. Twierdzę tylko, że wcale nie jest to oczywiste, że taka stała zdefiniowana w interfejsie musi być publiczna.

Pozdr. Mariusz

przemelek pisze...

IMHO odpowiedź czemu tylko publiczne jest prosta gdy rozpatrzymy taki hipotetyczny przykład:

package pl.a;

public interface A {
void a(); // w naszym hipotetycznym przypadku oznaczałoby to widoczność default
}

---
package pl.b;

public class B implements A {
void a() {
// coś robi
}
}

I teraz dość trudno byłoby powiedzieć czy metoda a() z B powinna być widoczna tylko w pl.b, czy może tylko w pl.a.

Tu kłania się to co można wyczytać w specyfikacji języka - modyfikatory dostępu tworzone były z myślą o API pakietów, a nie poszczególnych klas.

Btw. przyznam szczerze, że tak się "mądrząc" dopiero to dostrzegłem co dowodzi, że SCJP pomaga w zrozumieniu języka :-) [nawet jak się je już ma ;-)]

Mariusz Lipiński pisze...

Zgoda,

tyle, że ja pisałem o stałych, a nie o metodach - "chociaż wydaje mi się, że stała nie publiczna też mogłaby mieć tu sens".

Pozdr. Mariusz