Liigu peamise sisu juurde

Nähtavuse modifikaatorid

Õppevideo antud teemal:

Sissejuhatus

Klasse kirjutades on vajalik kontrollida, millistele osadele sellest koodist on võimalik ligi pääseda. Nähtavuse modifikaatorid (visibility modifiers/access modifiers) on märksõnad, mis määravad ära, kust ja kuidas on võimalik antud väljale, meetodile, konstruktorile või klassile ligi pääseda.

Nähtavuse määramine võimaldab:

  • Peita mõne funktsionaalsuse implementatsiooni, millest muud koodiosad ei tohiks sõltuda.
  • Selgelt määratleda detailide eesmärki
  • Vältida andmete või meetodite ebamäärast kasutust.

See teema on tugevalt seotud ühe OOP põhiprintsiibiga, milleks on kapseldamine. Kapseldamise kohta saad lähemalt uurida siit.

Nähtavuse modifikaatorid

Javas on neli nähtavuse modifikaatorit. Nendeks on:

ModifikaatorLigipääs samast klassistSamast pakistAlamklassistKõikjalt
private+---
(none)++--
protected+++-
public++++

(none) tähendab, et modifikaator puudub ehk seda eraldi välja ei kirjutata. Sellise modifikaatori tegelik nimi on package-private nähtavus.

Uurime igat modifikaatorit lähemalt, alustades kõige piiravamast ning lõpetades kõige leebemast.

private

private modifikaatoriga tähistatud liikmetele (klassid, meetodid, väljad, konstruktorid) on võimalik ligi pääseda ainult samast klassist. Mujalt koodist (kaasaarvatud alamklassit) ei ole võimalik sellega tähistatud koodile ligi pääseda.
Näiteks:

class BankAccount {
private double balance; // Only BankAccount itself can access this
private String pin; // Same here

public BankAccount(double initialBalance, String pin) {
this.balance = initialBalance;
this.pin = pin;
}

public double getBalance() {
return balance; // The class accesses its own private field - OK
}
}

public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount(1000.0, "1234");

System.out.println(account.getBalance()); // 1000.0 - OK, public method
System.out.println(account.balance); // Error: 'balance' has private access in 'BankAccount'
System.out.println(account.pin); // Error: 'pin' has private access in 'BankAccount'
}
}

private on kõige piiravam ligipääs ning vaikimisi peaks seda eelistama kõikide väljade puhul. Väljade puhul võiks kasutada teisi modifikaatoreid ainult siis, kui selleks on põhjus. Meetodid kirjutatakse ka võimalusel private modifikaatoriga. private aitab vältida klassisiseste andmete väärkasutust ning otsest muutmist.

Package-private (ilma modifikaatorita)

Ilma modifikaatorita liikmetele on võimalik ligi pääseda kõikidest klassidest, mis asuvad antud klassiga samas pakis. Sellise modifikaatori nimi on package-private access või default access.
Näiteks:

package university;

class Student {
String name; // package-private - no modifier written
int age; // package-private

void displayInfo() {
System.out.println(name + ", age " + age);
}
}
package university;     // Same package - access is allowed

class Enrollment {
void enroll(Student student) {
System.out.println("Enrolling: " + student.name); // OK - same package
}
}
package administration;  // Different package - access is NOT allowed

import university.Student;

class AdminOffice {
void process(Student student) {
System.out.println(student.name); // Error: 'name' is not public in 'Student'
}
}

Package-private modifikaator võib tulla kasuks olukorras, kus klassid ühe paki all on tihedalt omavahel seotud, kuid ei ole mõeldud väliseks kasutuseks. Praktikas eelistatakse antud modifikaatori asemel kas private või public modifikaatorit.

protected

protected modifikaatoriga liikmetele pääseb ligi samast klassist, pakist ja alamklassist. Viimane kehtib ka juhul, kui klassid on erinevates pakkides.
Näiteks:

package animals;

public class Animal {
protected String name;
protected int age;

public Animal(String name, int age) {
this.name = name;
this.age = age;
}

protected void breathe() {
System.out.println(name + " is breathing");
}
}
package animals.types;  // Different package

public class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}

public void bark() {
System.out.println(name + " says: Woof!"); // OK - Dog is a subclass of Animal
breathe(); // OK - subclass can call protected methods
}
}

protected modifikaator on kasulik pärilikkusega tegelemisel. Antud juhul me tahame, et alamklassid saaksid ülemklassi elemente kas kasutada või üle kirjutada, kuid mis peaks ikkagi muusosas peidetud olema. Nende teemadega tutvume rohkem pärilikusega seotud peatükkides.

public

public modifikaatoriga liikmetele pääseb ligi kõikjalt. See on kõige leebema ligipääsuga modifikaator.
Näiteks:

public class Calculator {
public int add(int a, int b) {
return a + b;
}

public int subtract(int a, int b) {
return a - b;
}
}

public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 3)); // 8
System.out.println(calc.subtract(10, 4)); // 6
}
}

public modifikaator on sobilik meetoditele, mis moodustavad nii-öelda klassi avaliku liidese ehk asjad, mida muud koodiosad võivad sellest klassist kasutada. Väljasid reeglina ei kirjutada otse public modifikaatoriga, vaid tehakse privaatseks ning ligipääs nendele väljadele toimub läbi avalike getter/setter meetodite. See aitab tagada andmete korrektsust ning õiget muutmist, kui antud andmetel on mingid kindlad reeglid paigas (nt arv ei tohi negatiivne olla).

Modifikaatorite kasutamine

Nähtavuse modifikaatoreid on võimalik kasutada väljade, meetodite, konstruktorite ja klasside peal.

Väljad

Väljad peaksid pea koguaeg olema private modifikaatoriga. Otsene ligipääs väljadele loetakse halvaks tavaks, kuna see paljastab klassi sisemise implementatsiooni detaile (teisisõnu, kas kolmas osapool peab teadma, kuidas punktist A punkti B-ni jõutakse?).
Näiteks:

class Person {
private String name; // private - access goes through methods
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

Meetodid

Meetodite puhul ei olda nii ranged, kuid jätkuvalt eelistatakse nii piiravat ligipääsu kui võimalik.
Näiteks:

class Order {
private double price;
private double taxRate = 0.20;

public Order(double price) {
this.price = price;
}

// Public - part of the interface other code should use
public double getTotalPrice() {
return price + calculateTax();
}

// Private - internal helper, not meant to be called from outside
private double calculateTax() {
return price * taxRate;
}
}

Konstruktorid

Konstruktorid on reeglina public modifikaatoriga, kuid private ja protected tulevad kasuks kindlates olukordades. Näiteks singleton mustri või utiilklasside puhul luuakse klassile privaatne konstruktor, et vältida antud klassilt instantsi tegemist selleks mitte ette nähtud viisil.
Näiteks:

class Configuration {
private static Configuration instance;

// Private constructor - nobody outside can call new Configuration()
private Configuration() {
// initialization
}

// Controlled access point
public static Configuration getInstance() {
if (instance == null) {
instance = new Configuration();
}
return instance;
}
}

Klassid

Javas on tavapraktika paigutada ühte faili üks public klass, mille nimi peab vastama faili nimega. Sellega ka reeglina piirdutakse. Samas Javas on võimalik luua lisaks põhilisele klassile ka muid klasse, mis antud juhul peaksid olema ilma modifikaatorita. Sellist disaini kasutatakse tavaliselt sisemiste implementatsioonidetailide peitmiseks, kuid on väga harva kasutusel.

Näiteks:

// Top-level class that can be accessed from anywhere
public class TopLevelClass {
// Some code here...
}

// A hidden class in same file, that can be accessed by only top-level class
// or files in same package
class HiddenImplementationClass {
// Some code here...
}

Nested klasside (klass klassi sees) puhul on lubatud kõik nähtavuse modifikaatorid. Need võivad olla public (näiteks Builder mustri või enum'ite puhul, mis on antud klassiga tihedalt seotud) või private, kui neid kasutatakse ainult abiklassidena. See aitab tagada kapseldamist ning hoiab klassi avalikku liidest puhtana.

Näiteks:

public class Order {

// Public enum class, can be accessed anywhere
public enum Status {
PENDING,
PROCESSING,
SHIPPED,
DELIVERED
}

// A private helper class only meant to be used by Order class
private static class PriceCalculator {
double applyDiscount(double base, double discount) {
return base * (1 - discount);
}

// can include more methods
}

private String id;
private Status status;
private double price;

public Order(String id, double price) {
this.id = id;
this.price = price;
this.status = Status.PENDING;
}

public void ship() {
PriceCalculator calc = new PriceCalculator();
this.price = calc.applyDiscount(price, 0.05); // 5% shipping discount
this.status = Status.SHIPPED;
}

public Status getStatus() { return status; }
public double getPrice() { return price; }
}
public class Main {
public static void main(String[] args) {
Order order = new Order("ORD-001", 49.99);
System.out.println(order.getStatus()); // PENDING

order.ship();
System.out.println(order.getStatus()); // SHIPPED
System.out.println(order.getPrice()); // 47.4905

// Public nested enum - accessible from outside through Order.Status
Order.Status s = Order.Status.DELIVERED;

// Private nested class - NOT accessible from outside
// Order.PriceCalculator calc = new Order.PriceCalculator(); // Error
}
}

Head tavad

  • Väljad peaksid vaikimisi private olema, protected kui alamklass peaks ülemklassi detaile pärima. public ainult siis kui muud valikut pole.
  • Meetodid peaksid olema public ainult juhul, kui need on mõeldud klassiväliseks kasutuseks, vastasel juhul private
  • Konstruktorid peaksid olema public, kui olukord ei nõua muud.
  • Üldiselt tuleks eelistada nii piiratud ligipääsu kui võimalik.