Sama nazwa klasy nie identyfikuje jej w sposób jednoznaczny; musimy jeszcze określić pakiet w obrębie którego została ona zdefiniowana. Aby to zrozumieć odwołajmy się do analogii systemu organizacji plików i katalogów na dysku naszego komputera. Sama nazwa klasy nie identyfikuje jej jednoznacznie, podobnie jak sama nazwa nie identyfikuje jednoznacznie pliku na naszym dysku. Trzeba podać pełną ścieżkę dostępu, a więc także katalog w którym ten plik się znajduje. Podobnie, oprócz nazwy klasy trzeba jeszcze określić pakiet do którego należy.
Przykładowo, klasa File reprezentująca zbiór dyskowy, zdefiniowana w bibliotece standardowej Java SE, należy do pakietu java.io. Aby więc w sposób jednoznaczny określić, że chodzi nam o tę właśnie klasę, należy powiedzieć, że chodzi nam o klasę java.io.File.
Klasa o nazwie File już istnieje, ale mimo to nic nie stoi na przeszkodzie, żeby zdefiniować swoją własną klasę File. Musimy tylko umieścić ją w innym pakiecie. W jaki sposób upewnić się, że wymyślona przez nas nazwa pakietu jest unikalna? W pełni skutecznej metody nie ma, ale w większości przypadków wystarczy stosowanie schematu odwróconej domeny internetowej, niekoniecznie istniejącej w rzeczywistości. Przykładowo, jeśli pracujemy w polskim oddziale firmy ABC i implementujemy aplikację o nazwie SuperKonto dla banku XYZ, to powinniśmy wszystkie klasy tej aplikacji umieszczać w pakiecie pl.abc.xyz.superkonto lub w podpakietach tego pakietu. Zamiast nazwy firmy możemy naturalnie użyć dowolnej innej nazwy, chociażby swojego imienia i nazwiska. Ważne jest, aby nazwę pakietu budować hierarchicznie, zawężając stopniowo możliwość konfliktu z innymi programistami.
Posługiwanie się każdorazowo w pełni kwalifikowaną (tj. uwzględniającą pakiet) nazwą klasy byłoby uciążliwe, zatem wymyślono instrukcję importu. Instrukcję tę umieszczamy na początku pliku kodu źródłowego, tuż za instrukcją deklaracji pakietu, ale jeszcze przed definicją klasy. Importując jakąś klasę, np. klasę java.io.File, informujemy kompilator, że ilekroć w tym pliku kodu źródłowego użyjemy samej nazwy klasy File, bez podania pakietu, to będzie nam chodziło właśnie o klasę java.io.File. Zerknijmy na poniższy program wyświetlający bieżącą datę i czas, nieużywający jeszcze instrukcji importu:
package pl.naukajavy.kurs; public class TestClass { public static void main(String[] args) { java.util.Date date = new java.util.Date(); java.lang.System.out.println(date.toString()); } }
A teraz zobaczmy, jak wygląda ten sam program, tyle że importujący klasy których używa, a konkretnie klasę java.util.Date:
package pl.naukajavy.kurs; import java.util.Date; public class TestClass { public static void main(String[] args) { Date date = new Date(); System.out.println(date.toString()); } }
Importując klasę java.util.Date mówimy, że ilekroć w danym pliku zostanie użyty typ Date będzie to typ Date z pakietu java.util.
Powyższy przykład rodzi pewne pytanie – jak to możliwe, że kod ten jest poprawny skoro klasa System nie jest ani poprzedzona nazwą pakietu, ani też nie jest zaimportowana? Otóż jest zaimportowana! Wszystkie klasy z pakietu java.lang są automatycznie zaimportowane do każdego pliku kodu źródłowego, zupełnie jakbyśmy w każdym pliku napisali instrukcję importu:
import java.lang.*;
tyle, że nie musimy tego robić, bo robi to za nas kompilator. Nie musimy także importować klas znajdujących się w tym samym pakiecie co klasa którą implementujemy. Klasy umieszczone w tym samym pakiecie są wzajemnie widoczne bezpośrednio. Przy okazji zauważamy, że klasy można importować grupami, tzn. można zaimportować na raz wszystkie klasy z danego pakietu, posługując się znakiem * (gwiazdka). Uwaga! Symbolem * można zastąpić nazwy klas ale nie nazwy podpakietów. Instrukcja import java.lang.* dla przykładu nie powoduje zaimportowania klasy java.lang.reflect.Proxy czy interfejsu java.lang.annotation.Annotation.