Access modifiers and encapsulation
Hiding the inside of an object, why private fields plus methods beats exposed data, and getters and setters.
Finished reading?
Mark this session so you can track where you are.
Hiding the inside of an object, why private fields plus methods beats exposed data, and getters and setters.
Finished reading?
Mark this session so you can track where you are.
Back when we first met objects, we left a door wide open. Anyone could reach into a bank account from outside and write account.balance = -999, and the account would simply accept it. This session is about closing that door, and about the surprisingly large idea hiding behind the lock.
public and private apart and say what each one allows.private and reach it safely through a getter and a setter.protected) only makes full sense once we get there, so we just name it and move on.Here is a small BankAccount. It has a balance, and a constructor that sets the starting amount. Nothing about it is wrong yet. Read it, then look at what main does on the last line before printing.
public class Main { public static void main(String[] args) { BankAccount acc = new BankAccount(100.0); acc.balance = -999.0; System.out.println("Balance: " + acc.balance); }} class BankAccount { double balance; BankAccount(double startingBalance) { balance = startingBalance; }}Balance: -999.0
It prints Balance: -999.0. The account is now in a state that should be impossible. A real bank account cannot hold minus nine hundred and ninety nine dollars out of thin air. But the fieldbalance is wide open, so any line of code anywhere in the program can reach in with a dot and write whatever it likes. The careful constructor we wrote is pointless if the very next line can scribble over its work.
A field that anyone can write to is a field that anyone can break. The object cannot protect itself if its insides are exposed to the whole program.
Java lets you put a single word in front of a field or a method that controls who can reach it from outside. These words are called access modifiers, because they modify who has access. Two of them matter right now.
publicmeans “anyone, anywhere, may touch this”. That is what our fields have been all along, even though we never wrote the word; a plain field with no modifier is reachable from quite a lot of the program by default. privatemeans the opposite: “only code written inside this same classmay touch this”. From outside the class, a private field might as well not exist.
public double balance;private double balance;protectedwritten in real Java code. It means “this class and its future children”, which is a sentence that has no meaning until you have met children of a class. We cover it fully in inheritance. If you see it before then, read it as “a softer private” and carry on.Let us make balance private. The moment we do, the outside world can no longer writeacc.balance = -999.0, because that line lives in main, which is a different class. Good. But we have a new problem: now nobody can read the balance either, and reading it is perfectly reasonable. We locked the whole vault when all we wanted was to control the deposits.
The fix is to keep the field private, and add small public methods that act as the only doorways in and out. A method that hands the value back is called a getter. A method that lets you change the value, on the object's own terms, is called a setter. The object decides the rules; outsiders go through the doorway or not at all.
public class Main { public static void main(String[] args) { BankAccount acc = new BankAccount(100.0); acc.deposit(50.0); acc.deposit(-30.0); System.out.println("Balance: " + acc.getBalance()); }} class BankAccount { private double balance; BankAccount(double startingBalance) { balance = startingBalance; } void deposit(double amount) { if (amount > 0) { balance = balance + amount; } else { System.out.println("Rejected: deposit must be positive."); } } double getBalance() { return balance; }}Rejected: deposit must be positive. Balance: 150.0
Step through it and watch the second deposit. The constructor sets balance to 100.0. The first deposit(50.0) passes the amount > 0 check, so the balance climbs to150.0. The second call, deposit(-30.0), fails the check. The account does not touch its balance; it prints a refusal and returns untouched. So getBalance() hands back 150.0, and the final line prints Balance: 150.0. The bad input bounced off the wall of the method instead of landing inside the object.
acc.balance = -999.0 from the first example would now be a compile error, refused before the program ever runs, because balance is private. Our in-browser interpreter is looser about the wall between classes, so it may not stop a direct write the way a real compiler does. What it does run faithfully is the validation logic inside deposit, and that is the real lesson here: the guard in the setter is what keeps the value sane. Trust the rule, not the playground's leniency.The word setter can make it sound like a setter just shoves a value into a field. The whole point is that it does not have to. A setter is an ordinary method, so it can think before it writes. Ourdeposit is a setter with an opinion: it only accepts amounts above zero. That singleif is the difference between an account that can be broken and one that cannot.
Compare the two ways of changing a balance side by side. The naked field trusts everyone. The guarded method trusts no one until they pass the check.
acc.balance = x;acc.deposit(x);Notice where the rule lives. It is written once, inside the class, next to the data it protects. There is no way to deposit money that skips the check, because the check is the only door. If you later decide deposits must also be under a million dollars, you add one line to deposit and every part of the program obeys it instantly, without changing a single line that callsdeposit.
A getter lets the outside world read. A setter lets the outside world ask to change. Because the setter is a method, the object gets the final say on whether the change is allowed.
We now have a name for the whole move. Encapsulation is bundling the data and the methods that guard it into one unit, and hiding the data behind those methods. The word shares a root withcapsule: the important thing is sealed inside, and you interact with it only through the surface it chooses to show. The balance lives in the capsule; deposit and getBalance are the surface.
Two payoffs make this worth the small extra typing. The first you have already seen: the object can never be put into a state that makes no sense, because every path to its data runs through a method that checks. The second is quieter but just as valuable. Because outsiders only ever touch the public methods, you are free to change everything behind those methods later, and no caller notices.
private first, by reflex, the moment you declare them. Then open exactly the doorways you need with public methods. Starting locked and opening on purpose is far safer than starting open and trying to remember to lock up later.The constructor and encapsulation work as a team. The constructor makes sure an object is born in a valid state; the private fields and guarding setters make sure it stays valid for the rest of its life. Together they mean a BankAccount is correct from its first breath to its last, with no gap for a nonsense value to slip through.
Try each one yourself first, then open the answer.
private do that public does not?BankAccount, after deposit(50.0) and then deposit(-30.0), what does getBalance() return, and why is the answer not 120.0?amount > 0 check inside deposit better than asking every caller to remember to check before calling?protected exists but did not teach it. From the hint in this session, roughly what does it mean, and where will you learn it properly?Take these away. They continue exactly what we just did.
withdraw(double amount) method to BankAccount. It should refuse the withdrawal (and print a message) if the amount is not positive, or if it would push the balance below zero. Test it in the playground: try a valid withdrawal, a negative one, and one larger than the balance, and confirm the balance only changes when it should.Temperature class with a private field for degrees in Celsius. Write a constructor and a setter that reject any value below -273.15, since nothing can be colder than absolute zero. Add a getter. In a paragraph, explain what invariant your setter is protecting.BankAccount exposed its balance as a public field and other code across the program read it directly. Later you want to start storing the balance as an int number of cents instead of a double number of dollars. Why is that change painful with a public field, and why would a private field with a getBalance() method have made it painless?