"Ik ga je vertellen over « toegangsmodificatoren ». Ik heb er al eens eerder over verteld, maar herhaling is een pijler van leren."

U kunt de toegang (zichtbaarheid) die andere klassen hebben tot de methoden en variabelen van uw klasse regelen. Een toegangsmodificator beantwoordt de vraag «Wie heeft toegang tot deze methode/variabele?». U kunt voor elke methode of variabele slechts één modifier specificeren.

1) " publieke " modifier.

Een variabele, methode of klasse gemarkeerd met de public modifier is overal in het programma toegankelijk. Dit is de hoogste graad van openheid: er zijn geen beperkingen.

2) « privé » modifier.

Een variabele, methode of klasse gemarkeerd met de private modifier is alleen toegankelijk in de klasse waarin deze is gedeclareerd. De gemarkeerde methode of variabele is verborgen voor alle andere klassen. Dit is de hoogste graad van privacy: alleen toegankelijk voor jouw klas. Dergelijke methoden worden niet overgeërfd en kunnen niet worden opgeheven. Bovendien zijn ze niet toegankelijk in een afstammelingenklasse.

3)  « Standaard modifier».

Als een variabele of methode niet is gemarkeerd met een modifier, wordt deze beschouwd als gemarkeerd met de "standaard" modifier. Variabelen en methoden met deze modifier zijn zichtbaar voor alle klassen in het pakket waarin ze zijn gedeclareerd, en alleen voor die klassen. Deze modifier wordt ook wel " pakket " of " pakket privé " toegang genoemd , waarmee wordt aangegeven dat toegang tot variabelen en methoden open staat voor het hele pakket dat de klasse bevat.

4) « beschermde » modifier.

Dit toegangsniveau is iets ruimer dan pakket . Een variabele, methode of klasse gemarkeerd met de beschermde modifier kan worden benaderd vanuit zijn pakket (zoals "pakket"), en vanuit alle overgeërfde klassen.

Deze tabel legt het allemaal uit:

Soort zichtbaarheid Trefwoord Toegang
Jouw klas Jouw pakket Afstammeling Alle klassen
Privaat privaat Ja Nee Nee Nee
Pakket (geen modificatie) Ja Ja Nee Nee
Beschermd beschermd Ja Ja Ja Nee
Openbaar openbaar Ja Ja Ja Ja

Er is een manier om deze tabel gemakkelijk te onthouden. Stel je voor dat je een testament schrijft. Je verdeelt al je spullen in vier categorieën. Wie mag jouw spullen gebruiken?

Wie heeft toegang Modificator Voorbeeld
Alleen  ik privaat Persoonlijk dagboek
Familie (geen modificatie) Familiefoto's
Familie en erfgenamen beschermd Familie landgoed
Iedereen openbaar Memoires

"Het lijkt veel op het voorstellen dat klassen in hetzelfde pakket deel uitmaken van één familie."

"Ik wil je ook enkele interessante nuances vertellen over overheersende methoden."

1) Impliciete implementatie van een abstracte methode.

Laten we zeggen dat je de volgende code hebt:

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

En je hebt besloten om een ​​Tiger-klasse te maken die deze klasse overerft, en een interface toe te voegen aan de nieuwe klasse

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

}

Als u gewoon alle ontbrekende methoden implementeert die IntelliJ IDEA u vertelt te implementeren, zou u later misschien lang moeten zoeken naar een bug.

Het blijkt dat de Tiger-klasse een getName-methode heeft die is geërfd van Cat, die zal worden gebruikt als de implementatie van de getName-methode voor de HasName-interface.

"Daar zie ik niets vreselijks aan."

"Het is niet zo erg, het is een waarschijnlijke plaats voor fouten om binnen te sluipen."

Maar het kan nog erger:

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

Het blijkt dat je niet altijd kunt erven van meerdere interfaces. Nauwkeuriger gezegd, u kunt ze erven, maar u kunt ze niet correct implementeren. Kijk naar het voorbeeld. Beide interfaces vereisen dat u de methode getValue() implementeert, maar het is niet duidelijk wat deze moet retourneren: het gewicht of de grootte? Dit is heel onaangenaam om mee om te gaan.

"Ik ben het ermee eens. Je wilt een methode implementeren, maar dat lukt niet. Je hebt al een methode geërfd met dezelfde naam van de basisklasse. Die is defect."

"Maar er is goed nieuws."

2) Zichtbaarheid uitbreiden. Wanneer u een type overerft, kunt u de zichtbaarheid van een methode vergroten. Zo ziet het eruit:

Java-code Beschrijving
class Cat
{
 protected String getName()
 {
  return "Oscar";
 }
}
class Tiger extends Cat
{
 public String getName()
 {
  return "Oscar Tiggerman";
 }
}
We hebben de zichtbaarheid van de methode uitgebreid van protectednaar public.
Code Waarom dit "legaal" is
public static void main(String[] args)
{
 Cat cat = new Cat();
 cat.getName();
}
Alles is geweldig. Hier weten we niet eens dat de zichtbaarheid is uitgebreid in een afstammelingenklasse.
public static void main(String[] args)
{
 Tiger tiger = new Tiger();
 tiger.getName();
}
Hier noemen we de methode waarvan de zichtbaarheid is vergroot.

Als dit niet mogelijk was, zouden we altijd een methode in Tiger kunnen declareren:
public String getPublicName()
{
super.getName(); //roep de beschermde methode aan
}

Met andere woorden, we hebben het niet over een beveiligingsschending.

public static void main(String[] args)
{
 Cat catTiger = new Tiger();
 catTiger.getName();
}
Als aan alle voorwaarden is voldaan die nodig zijn om een ​​methode in een basisklasse ( Cat ) aan te roepen, dan is dat zeker ook het geval voor het aanroepen van de methode op het afstammende type ( Tijger ). Omdat de beperkingen op de methodeaanroep zwak waren, niet sterk.

"Ik weet niet zeker of ik het helemaal begrepen heb, maar ik zal onthouden dat dit mogelijk is."

3) Vernauwing van het retourtype.

In een overschreven methode kunnen we het retourtype wijzigen in een versmald referentietype.

Java-code Beschrijving
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;
 }
}
We hebben de methode overschreven getMyParenten nu retourneert het een Tigerobject.
Code Waarom dit "legaal" is
public static void main(String[] args)
{
 Cat parent = new Cat();

 Cat me = new Cat();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
Alles is geweldig. Hier weten we niet eens dat het retourtype van de getMyParent-methode is verbreed in de afstammingsklasse.

Hoe de «oude code» werkte en werkt.

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

 Tiger me = new Tiger();
 me.setMyParent(parent);
 Tiger myParent = me.getMyParent();
}
Hier noemen we de methode waarvan het retourtype is versmald.

Als dit niet mogelijk was, zouden we altijd een methode in Tiger kunnen declareren:
public Tiger getMyTigerParent()
{
return (Tiger) this.parent;
}

Met andere woorden, er zijn geen beveiligingsschendingen en/of type casting-schendingen.

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

 Cat me = new Tiger();
 me.setMyParent(parent);
 Cat myParent = me.getMyParent();
}
En alles werkt hier prima, hoewel we het type variabelen hebben verbreed naar de basisklasse (Cat).

Vanwege overschrijven wordt de juiste methode setMyParent aangeroepen.

En u hoeft zich nergens zorgen over te maken als u de getMyParent-methode aanroept , omdat de geretourneerde waarde, hoewel van de Tiger-klasse, nog steeds probleemloos kan worden toegewezen aan de myParent-variabele van de basisklasse (Cat) .

Tiger-objecten kunnen veilig worden opgeslagen in zowel Tiger-variabelen als Cat-variabelen.

"Ja. Begrepen. Bij het overschrijven van methoden moet je weten hoe dit allemaal werkt als we onze objecten doorgeven aan code die alleen de basisklasse aankan en niets weet over onze klasse. "

"Precies! Dan is de grote vraag waarom we het type van de geretourneerde waarde niet kunnen verfijnen als we een methode overschrijven?"

"Het is duidelijk dat in dit geval de code in de basisklasse niet meer werkt:"

Java-code Uitleg van het probleem
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";
 }
}
We hebben de getMyParent-methode overbelast en het type retourwaarde verkleind.

Hier is alles goed.

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

 Cat me = new Tiger();
 Cat myParent = me.getMyParent();
}
Dan werkt deze code niet meer.

De methode getMyParent kan elke instantie van een object retourneren, omdat deze in feite wordt aangeroepen voor een Tiger-object.

En we hebben geen controle voor de opdracht. Het is dus heel goed mogelijk dat de myParent-variabele van het Cat-type een String-referentie opslaat.

"Prachtig voorbeeld, Amigo!"

In Java wordt er, voordat een methode wordt aangeroepen, niet gecontroleerd of het object zo'n methode heeft. Alle controles vinden plaats tijdens runtime. En een [hypothetische] aanroep van een ontbrekende methode zou er hoogstwaarschijnlijk toe leiden dat het programma probeert niet-bestaande bytecode uit te voeren. Dit zou uiteindelijk leiden tot een fatale fout en het besturingssysteem zou het programma met geweld sluiten.

"Ho. Nu weet ik het."