Cześć! Na dzisiejszym wykładzie zapoznamy się z pojęciem „ modyfikatorów dostępu ” i przyjrzymy się przykładom pracy z nimi. Chociaż słowo „poznajmy się” nie będzie do końca trafne: większość z nich znacie już z poprzednich wykładów. Na wszelki wypadek odświeżmy pamięć o najważniejszej sprawie. Modyfikatory dostępu to najczęściej słowa kluczowe, które regulują poziom dostępu do różnych części Twojego kodu. Dlaczego „najczęściej”? Ponieważ jeden z nich jest ustawiony domyślnie i nie jest oznaczony słowem kluczowym :) W Javie są w sumie cztery modyfikatory dostępu. Podajemy je w kolejności od najbardziej rygorystycznej do najbardziej „miękkiej”:
- prywatny;
- chroniony;
- domyślny (pakiet widoczny);
- publiczny.
Modyfikator prywatny
Private
— najbardziej restrykcyjny modyfikator dostępu. Ogranicza widoczność danych i metod w ramach jednej klasy. Znasz ten modyfikator z wykładu o getterach i setterach. Czy pamiętasz ten przykład?
public class Cat {
public String name;
public int age;
public int weight;
public Cat(String name, int age, int weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
public Cat() {
}
public void sayMeow() {
System.out.println("Miauczeć!");
}
}
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "";
cat.age = -1000;
cat.weight = 0;
}
}
Pisaliśmy już o tym w jednym z artykułów wcześniej. Tutaj popełniliśmy poważny błąd: otworzyliśmy nasze dane, w wyniku czego koledzy programiści mieli bezpośredni dostęp do pól klas i zmianę ich wartości. Co więcej, wartości te zostały przypisane bez kontroli, dzięki czemu w naszym programie możliwe jest stworzenie kota w wieku -1000 lat, o imieniu „” i wadze 0. Aby rozwiązać ten problem, możemy używane metody pobierające i ustawiające , a także ograniczony dostęp do danych za pomocą modyfikatora private
.
public class Cat {
private String name;
private int age;
private int weight;
public Cat(String name, int age, int weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
public Cat() {
}
public void sayMeow() {
System.out.println("Miauczeć!");
}
public String getName() {
return name;
}
public void setName(String name) {
// sprawdzenie parametru wejściowego
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
// sprawdzenie parametru wejściowego
this.age = age;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
// sprawdzenie parametru wejściowego
this.weight = weight;
}
}
Właściwie ograniczenie dostępu do pól i wdrożenie modułów pobierających-setujących jest najczęstszym przykładem użycia private
w prawdziwej pracy. Oznacza to, że głównym celem tego modyfikatora jest implementacja enkapsulacji w programie. Nawiasem mówiąc, dotyczy to nie tylko pól. Wyobraź sobie, że w Twoim programie znajduje się metoda implementująca BARDZO złożoną funkcjonalność. Oto przykład... Załóżmy, że Twoja metoda readDataFromCollider()
pobiera adres z danymi jako dane wejściowe, odczytuje dane z Wielkiego Zderzacza Hadronów w formacie bajtowym, konwertuje te dane na tekst, zapisuje je do pliku i drukuje. Już sam opis metody wygląda przerażająco, a co dopiero kod :) Aby zwiększyć czytelność kodu dobrze byłoby nie pisać złożonej logiki metody w jednym miejscu, a wręcz przeciwnie, zepsuć funkcjonalność na osobne metody. Metoda readByteData()
odpowiada np. za odczyt danych, convertBytesToSymbols()
konwersję danych odczytanych ze zderzacza na tekst, saveToFile()
zapisanie powstałego tekstu do pliku i printColliderData()
wydrukowanie naszego pliku z danymi. Metoda readDataFromCollider()
byłaby znacznie prostsza:
public class ColliderUtil {
public void readDataFromCollider(Path pathToData) {
byte[] colliderData = readByteData(pathToData);
String[] textData = convertBytesToSymbols(colliderData);
File fileWithData = saveToFile(textData);
printColliderData(fileWithData);
}
public byte[] readByteData(Path pathToData) {
// odczytuje dane w bajtach
}
public String[] convertBytesToSymbols(byte[] colliderDataInBytes) {
// konwertuje bajty na znaki
}
public File saveToFile(String[] colliderData) {
// zapisz odczytane dane do pliku
}
public void printColliderData(File fileWithColliderData) {
// wydrukuj dane z pliku
}
}
Jak jednak pamiętacie z wykładu o interfejsach, użytkownik otrzymuje dostęp jedynie do finalnego interfejsu. Nasze 4 metody nie są tego częścią. Są pomocnicze : stworzyliśmy je, aby poprawić czytelność kodu i uniknąć wtłaczania czterech różnych zadań w jedną metodę. Nie ma potrzeby udostępniania użytkownikowi tych metod. Jeśli użytkownik ma dostęp do metody podczas pracy ze zderzaczem convertBytesToSymbols()
, najprawdopodobniej po prostu nie zrozumie, czym jest ta metoda i dlaczego jest potrzebna. Jakie bajty są konwertowane? Skąd oni przyszli? Po co konwertować je na tekst? Logika działająca w tej metodzie nie jest częścią interfejsu użytkownika. Tylko metoda readDataFromCollider()
jest częścią interfejsu. Co zrobić z tymi czterema „wewnętrznymi” metodami? Prawidłowy! Ogranicz dostęp do nich za pomocą modyfikatora private
. W ten sposób mogą łatwo wykonywać swoją pracę wewnątrz klasy i nie dezorientować użytkownika, który nie potrzebuje logiki każdego z nich z osobna.
public class ColliderUtil {
public void readDataFromCollider(Path pathToData) {
byte[] colliderData = readByteData(pathToData);
String[] textData = convertBytesToSymbols(colliderData);
File fileWithData = saveToFile(textData);
printColliderData(fileWithData);
}
private byte[] readByteData(Path pathToData) {
// odczytuje dane w bajtach
}
private String[] convertBytesToSymbols(byte[] colliderDataInBytes) {
// konwertuje bajty na znaki
}
private File saveToFile(String[] colliderData) {
// zapisz odczytane dane do pliku
}
private void printColliderData(File fileWithColliderData) {
// wydrukuj dane z pliku
}
}
Modyfikator chroniony
Następnym najbardziej restrykcyjnym modyfikatorem dostępu jestprotected
. Widoczne będą pola i metody oznaczone modyfikatorem dostępu :protected
- we wszystkich klasach znajdujących się w tym samym pakiecie co nasz;
- we wszystkich klasach następczych po naszej klasie.
protected
przypadków zastosowania jest znacznie mniej niż private
, a są one specyficzne. Wyobraźmy sobie, że mamy klasę abstrakcyjną AbstractSecretAgent
oznaczającą tajnego agenta jakiejś agencji wywiadowczej, a także pakiet top_secret
zawierający tę klasę i jej potomków. Klasy konkretne - FBISecretAgent
, MI6SecretAgent
, MossadSecretAgent
itp. - są z niego dziedziczone. Wewnątrz klasy abstrakcyjnej chcemy zaimplementować licznik agentów. Kiedy w programie zostanie utworzony nowy obiekt agenta, jego liczba wzrośnie.
package top_secret;
public abstract class AbstractSecretAgent {
public static int agentCount = 0;
}
Ale nasi agenci są tajni! Oznacza to, że tylko oni i nikt inny nie powinien wiedzieć o ich numerze. Możemy łatwo dodać modyfikator protected
do pola agentCount
i wówczas albo obiekty innych klas tajnych agentów, albo te klasy, które znajdują się w naszym „tajnym” pakiecie, mogą uzyskać swoją wartość top_secret
.
public abstract class AbstractSecretAgent {
protected static int agentCount = 0;
}
Właśnie do tak konkretnych zadań potrzebny jest modyfikator protected
:)
modyfikator widoczny w pakiecie
Następny na naszej liście jest modyfikatordefault
lub, jak to się nazywa, package visible
. Nie jest to oznaczone słowem kluczowym, ponieważ jest ono domyślnie ustawione w Javie dla wszystkich pól i metod. Jeśli napiszesz w swoim kodzie -
int x = 10;
... zmienna x
będzie miała taki sam package visible
dostęp. Jeśli metoda (lub zmienna) nie jest oznaczona żadnym modyfikatorem, uważa się, że jest oznaczona „modyfikatorem domyślnym”. Zmienne lub metody z takim modyfikatorem (czyli w ogóle bez niego) są widoczne dla wszystkich klas pakietu, w którym są zadeklarowane. I tylko do nich. Jego zastosowanie jest ograniczone, podobnie jak modyfikatora protected
. Najczęściej default
opcja -access jest używana w pakiecie, w którym istnieją klasy narzędziowe, które nie implementują funkcjonalności wszystkich innych klas w tym pakiecie. Podajmy przykład. Wyobraź sobie, że mamy pakiet „ usług ”. Wewnątrz znajdują się różne klasy współpracujące z bazą danych. Przykładowo istnieje klasa UserService
odczytująca dane użytkownika z bazy danych, klasa CarService
czytająca dane o samochodach z tej samej bazy danych oraz inne klasy, z których każda pracuje z własnym typem obiektów i odczytuje dane o nich z bazy danych.
package services;
public class UserService {
}
package services;
public class CarService {
}
Jednak łatwo może dojść do sytuacji, gdy dane w bazie danych są w jednym formacie, a my potrzebujemy ich w innym. Wyobraź sobie, że data urodzenia użytkownika w bazie danych jest przechowywana w formacie TIMESTAMP ZE STREFĄ CZASOWĄ...
2014-04-04 20:32:59.390583+02
...zamiast tego potrzebujemy najprostszego obiektu - java.util.Date
. W tym celu możemy stworzyć wewnątrz pakietu services
specjalną klasę Mapper
. Będzie odpowiedzialny za konwersję danych z bazy danych na znane nam obiekty Java. Prosta klasa pomocnicza. Zwykle tworzymy wszystkie klasy jako public class ClassName
, ale nie jest to konieczne. Możemy zadeklarować naszą klasę pomocniczą po prostu jako class Mapper
. W tym przypadku nadal spełnia swoje zadanie, ale nie jest widoczny dla nikogo poza pakietem services
!
package services;
class Mapper {
}
package services;
public class CarService {
Mapper mapper;
}
I to jest w istocie poprawna logika: dlaczego ktoś spoza pakietu miałby widzieć klasę pomocniczą, która działa tylko z klasami z tego samego pakietu?
modyfikator publiczny
I ostatni na liście, ale nie mniej ważny - modyfikatorpublic
! Poznałeś go pierwszego dnia studiów w JavaRush, uruchamiając public static void main(String[] args)
. Teraz, gdy przestudiowałeś wykłady o interfejsach, jego cel jest dla ciebie oczywisty :) W końcu public
został stworzony, aby dać coś użytkownikom. Na przykład interfejs twojego programu. Załóżmy, że napisałeś program tłumaczący, który może przetłumaczyć tekst rosyjski na angielski. Utworzyłeś metodę translate(String textInRussian)
, w ramach której zaimplementowana jest niezbędna logika. Oznaczyłeś tę metodę słowem public
i teraz stanie się ona częścią interfejsu:
public class Translator {
public String translate(String textInRussian) {
// tłumaczy tekst z rosyjskiego na angielski
}
}
Wywołanie tej metody możesz powiązać z przyciskiem „tłumacz” na ekranie programu – i gotowe! Każdy może z niego skorzystać. Części kodu oznaczone modyfikatorem public
przeznaczone są dla użytkownika końcowego. Dla przykładu z życia private
są to wszystkie procesy zachodzące wewnątrz telewizora podczas jego pracy i public
są to przyciski na pilocie telewizora, za pomocą których użytkownik może nim sterować. Jednocześnie nie musi wiedzieć, jak działa telewizor i jak działa. Pilot zdalnego sterowania to zestaw public
metod: on()
, off()
, nextChannel()
, previousChannel()
, increaseVolume()
, decreaseVolume()
itp.
GO TO FULL VERSION