„O să vă povestesc despre „ modificatorii de acces ”. Am mai povestit despre ei o dată, dar repetarea este un pilon al învățării.”

Puteți controla accesul (vizibilitatea) pe care îl au alte clase la metodele și variabilele clasei dvs. Un modificator de acces răspunde la întrebarea „Cine poate accesa această metodă/variabilă?”. Puteți specifica un singur modificator pentru fiecare metodă sau variabilă.

1) modificator „ public ”.

O variabilă, o metodă sau o clasă marcată cu modificatorul public poate fi accesată de oriunde în program. Acesta este cel mai înalt grad de deschidere: nu există restricții.

2) modificatorul „ privat ”.

O variabilă, o metodă sau o clasă marcată cu modificatorul privat poate fi accesată numai în clasa în care este declarată. Metoda sau variabila marcată este ascunsă de toate celelalte clase. Acesta este cel mai înalt grad de confidențialitate: accesibil doar de către clasa dvs. Astfel de metode nu sunt moștenite și nu pot fi înlocuite. În plus, acestea nu pot fi accesate într-o clasă descendentă.

3)  « Modificator implicit ».

Dacă o variabilă sau o metodă nu este marcată cu niciun modificator, atunci este considerată a fi marcată cu modificatorul „implicit”. Variabilele și metodele cu acest modificator sunt vizibile pentru toate clasele din pachetul în care sunt declarate și numai pentru acele clase. Acest modificator se mai numește acces „ pachet ” sau „ pachet privat ”, indicând faptul că accesul la variabile și metode este deschis întregului pachet care conține clasa.

4) modificator „ protejat ”.

Acest nivel de acces este puțin mai larg decât pachetul . O variabilă, metodă sau clasă marcată cu modificatorul protejat poate fi accesată din pachetul său (cum ar fi „pachet”) și din toate clasele moștenite.

Acest tabel explică totul:

Tip de vizibilitate Cuvânt cheie Acces
Clasa ta Pachetul dvs Descendent Toate clasele
Privat privat da Nu Nu Nu
Pachet (fără modificator) da da Nu Nu
Protejat protejat da da da Nu
Public public da da da da

Există o modalitate de a vă aminti cu ușurință acest tabel. Imaginează-ți că scrii un testament. Împărți toate lucrurile în patru categorii. Cine poate să-ți folosească lucrurile?

Cine are acces Modificator Exemplu
Doar  eu privat Jurnal personal
Familie (fără modificator) Fotografii de familie
Familia și moștenitorii protejat Moșie de familie
Toata lumea public Memorii

„Este mult ca să-ți imaginezi că clasele din același pachet fac parte dintr-o singură familie.”

„Vreau să vă spun și câteva nuanțe interesante despre metodele de suprapunere.”

1) Implementarea implicită a unei metode abstracte.

Să presupunem că aveți următorul cod:

Cod
class Cat
{
 public String getName()
 {
  return "Oscar";
 }
}

Și ați decis să creați o clasă Tiger care moștenește această clasă și să adăugați o interfață noii clase

Cod
class Cat
{
 public String getName()
 {
   return "Oscar";
 }
}
interface HasName
{
 String getName();
 int getWeight();
}
class Tiger extends Cat implements HasName
{
 public int getWeight()
 {
  return 115;
 }

}

Dacă doar implementați toate metodele lipsă pe care IntelliJ IDEA vă spune să le implementați, mai târziu s-ar putea să petreceți mult timp căutând o eroare.

Se pare că clasa Tiger are o metodă getName moștenită de la Cat, care va fi luată ca implementare a metodei getName pentru interfața HasName.

— Nu văd nimic groaznic în asta.

„Nu este prea rău, este un loc probabil în care să se strecoare greșeli.”

Dar poate fi și mai rău:

Cod
interface HasWeight
{
 int getValue();
}
interface HasSize
{
 int getValue();
}
class Tiger extends Cat implements HasWeight, HasSize
{
 public int getValue()
 {
  return 115;
 }
}

Se pare că nu puteți moșteni întotdeauna de la mai multe interfețe. Mai exact, le poți moșteni, dar nu le poți implementa corect. Uita-te la exemplu. Ambele interfețe necesită să implementați metoda getValue(), dar nu este clar ce ar trebui să returneze: greutatea sau dimensiunea? Este destul de neplăcut să ai de-a face cu asta.

"Sunt de acord. Doriți să implementați o metodă, dar nu puteți. Ați moștenit deja o metodă cu același nume din clasa de bază. Este ruptă."

— Dar sunt vești bune.

2) Extinderea vizibilității. Când moșteniți un tip, puteți extinde vizibilitatea unei metode. Așa arată:

Cod Java Descriere
class Cat
{
 protected String getName()
 {
  return "Oscar";
 }
}
class Tiger extends Cat
{
 public String getName()
 {
  return "Oscar Tiggerman";
 }
}
Am extins vizibilitatea metodei de protectedla la public.
Cod De ce este „legal”
public static void main(String[] args)
{
 Cat cat = new Cat();
 cat.getName();
}
Totul e grozav. Aici nici nu știm că vizibilitatea a fost extinsă într-o clasă descendentă.
public static void main(String[] args)
{
 Tiger tiger = new Tiger();
 tiger.getName();
}
Aici numim metoda a cărei vizibilitate a fost extinsă.

Dacă acest lucru nu ar fi posibil, am putea declara întotdeauna o metodă în Tiger:
public String getPublicName()
{
super.getName(); //apelați metoda protejată
}

Cu alte cuvinte, nu vorbim despre nicio încălcare a securității.

public static void main(String[] args)
{
 Cat catTiger = new Tiger();
 catTiger.getName();
}
Dacă sunt îndeplinite toate condițiile necesare pentru apelarea unei metode într-o clasă de bază ( Cat ), atunci cu siguranță sunt îndeplinite pentru apelarea metodei pe tipul descendent ( Tiger ) . Pentru că restricțiile privind apelul metodei au fost slabe, nu puternice.

„Nu sunt sigur că am înțeles complet, dar îmi voi aminti că acest lucru este posibil”.

3) Restrângerea tipului de returnare.

Într-o metodă suprascrisă, putem schimba tipul de returnare la un tip de referință restrâns.

Cod Java Descriere
class Cat
{
 public Cat parent;
 public Cat getMyParent()
 {
  return this.parent;
 }
 public void setMyParent(Cat cat)
 {
  this.parent = cat;
 }
}
class Tiger extends Cat
{
 public Tiger getMyParent()
 {
  return (Tiger) this.parent;
 }
}
Am suprasolicitat metoda getMyParentși acum returnează un Tigerobiect.
Cod De ce este „legal”
public static void main(String[] args)
{
 Cat parent = new Cat();

 Cat me = new Cat();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
Totul e grozav. Aici nici măcar nu știm că tipul de returnare al metodei getMyParent a fost extins în clasa descendentă.

Cum a funcționat și cum funcționează „codul vechi”.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Tiger me = new Tiger();
 me.setMyParent(parent);
 Tiger myParent = me.getMyParent();
}
Aici numim metoda al cărei tip de returnare a fost restrâns.

Dacă acest lucru nu ar fi posibil, am putea declara întotdeauna o metodă în Tiger:
public Tiger getMyTigerParent()
{
return (Tiger) this.parent;
}

Cu alte cuvinte, nu există încălcări de securitate și/sau încălcări de tip casting.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Cat me = new Tiger();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
Și totul funcționează bine aici, deși am extins tipul variabilelor la clasa de bază (Cat).

Din cauza suprascrierii, este apelată metoda corectă setMyParent.

Și nu este nimic de care să vă faceți griji atunci când apelați metoda getMyParent , deoarece valoarea returnată, deși a clasei Tiger, poate fi totuși atribuită variabilei myParent a clasei de bază (Cat) fără probleme.

Obiectele Tiger pot fi stocate în siguranță atât în ​​variabilele Tiger, cât și în variabilele Cat.

„Da. Am înțeles. Când suprascrieți metodele, trebuie să fiți conștienți de cum funcționează toate acestea dacă ne transmitem obiectele unui cod care poate gestiona doar clasa de bază și nu știe nimic despre clasa noastră.

"Exact! Atunci marea întrebare este de ce nu putem restrânge tipul valorii returnate atunci când suprascriem o metodă?"

„Este evident că în acest caz codul din clasa de bază ar înceta să funcționeze:”

Cod Java Explicația problemei
class Cat
{
 public Cat parent;
 public Cat getMyParent()
 {
  return this.parent;
 }
 public void setMyParent(Cat cat)
 {
  this.parent = cat;
 }
}
class Tiger extends Cat
{
 public Object getMyParent()
 {
  if (this.parent != null)
   return this.parent;
  else
   return "I'm an orphan";
 }
}
Am supraîncărcat metoda getMyParent și am restrâns tipul valorii returnate.

Totul este bine aici.

public static void main(String[] args)
{
 Tiger parent = new Tiger();

 Cat me = new Tiger();
 Cat myParent = me.getMyParent();
}
Atunci acest cod nu va mai funcționa.

Metoda getMyParent poate returna orice instanță a unui Object, deoarece este de fapt apelată pe un obiect Tiger.

Și nu avem o verificare înainte de misiune. Astfel, este cu totul posibil ca variabila myParent de tip Cat să stocheze o referință String.

— Exemplu minunat, Amigo!

În Java, înainte de apelarea unei metode, nu se verifică dacă obiectul are o astfel de metodă. Toate verificările au loc în timpul execuției. Și un apel [ipotetic] la o metodă lipsă ar determina cel mai probabil programul să încerce să execute bytecode inexistent. Acest lucru ar duce în cele din urmă la o eroare fatală, iar sistemul de operare ar închide forțat programul.

"Uau. Acum știu."