Encapsulation
Problem
Minus €500
In the last lesson we put the withdrawal check in a method. But the field is open, and anyone can write to it directly:
Account account = new Account("Maria", 100);account.withdraw(500);account.balance = -500;System.out.println(account.balance);-500.0The method correctly refused to withdraw €500. The next line simply set the balance by hand. The check is there, but you can get around it. We need a way to say "only the account touches the balance".
The idea
private: only the class touches it
Put private in front of the field instead of public:
class Account { private String owner; private double balance; // ...}Now only the code inside the Account class can reach the field. From outside, the line
account.balance = -500;no longer compiles:
error: balance has private access in Accountprivate and public are called access modifiers. They say who can see a field, a method or a constructor. The class hides its data and shows it only through its own methods. This is called encapsulation.
More precisely, if you are curious
- Without a modifier, a field or method is visible in the same package (a group of classes in one folder). While everything is in one file, this is almost like
public. So write the modifier explicitly. - There is also
protected: visible in the class and in its subclasses. You'll meet it in the module "Inheritance and polymorphism". public class Mainis a public class. A file can have only one of those, and it has the same name as the file. That's why our classes above it have nopublic.
The idea
Reading without touching
From outside, we can't even read the balance now. So the class gives a method that only returns it:
class Account { private String owner; private double balance;
public Account(String owner, double balance) { this.owner = owner; this.balance = balance; }
public String getOwner() { return owner; }
public double getBalance() { return balance; }
public void deposit(double amount) { if (amount > 0) { balance += amount; } }
public boolean withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; return true; } return false; }}
public class Main { public static void main(String[] args) { Account account = new Account("Maria", 100); account.withdraw(30); account.deposit(50); System.out.println(account.getOwner() + ": €" + account.getBalance()); }}Maria: €120.0getBalance() is a getter: a method that returns the value of a field. Its name is get plus the field name with a capital letter. The balance changes only through deposit and withdraw, and they do the checks.
withdraw now returns boolean: whether the withdrawal went through. This way, whoever withdraws knows if it worked.
Account account = new Account("Maria", 100);account.withdraw(500);account.deposit(50);System.out.println(account.getBalance());Step by step, in words
- balance is private: the glass means there is no way to the field from outside.
- The withdrawal goes through the check in the method: 500 is more than 100. Refused, and the balance stays as it is.
- The deposit is checked too: 50 is a positive amount. The method adds it.
- getBalance() hands out a copy of the balance. The field itself stays hidden.
Try it
Refused
What will the program print?
Account account = new Account("Maria", 100);System.out.println(account.withdraw(150));account.deposit(-20);System.out.println(account.getBalance());€150 is more than the balance, so withdraw returns false and leaves the balance alone. The deposit of -€20 is refused too, because the amount must be positive. The balance stays 100.
The idea
Setter: a change with a check
Sometimes code outside needs to change a field, but not to just any value. An animal's age can change, but it can't be negative:
class Animal { private int age;
public int getAge() { return age; }
public void setAge(int age) { if (age >= 0) { this.age = age; } }}
Animal cat = new Animal();cat.setAge(4);cat.setAge(-3);System.out.println(cat.getAge());4setAge is a setter: a method that stores a value in a field after it checks it. With -3, the check fails and the age stays 4.
The idea
Which field gets a setter?
Not every field needs a setter, and some don't even need a getter. Think about what code outside should be able to do with the product:
- the name and the price are set when the product is created and are only read after that: getter only;
- the quantity in the cart changes, but it's at least 1: a getter and a setter with a check.
class Product { private String name; private double price; private int quantity;
public Product(String name, double price) { this.name = name; this.price = price; this.quantity = 1; }
public String getName() { return name; }
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
public void setQuantity(int quantity) { if (quantity >= 1) { this.quantity = quantity; } }}
Product coffee = new Product("Coffee", 7.50);coffee.setQuantity(3);coffee.setQuantity(0);System.out.println(coffee.getName() + " x" + coffee.getQuantity());Coffee x3The rule is: start with private for everything and open only what you need. A setter that stores anything without a check brings back the problem from the first card.
In Java, you write getters and setters by hand. IntelliJ IDEA can generate them: Alt+Insert → Getter and Setter.
Try it
What's missing?
class Account {
double balance;
}private: the field is visible only to the code in Account.Try it
Find the bug
The field is private, and yet the balance becomes -500:
Account account = new Account();account.setBalance(100);account.withdraw(30);account.setBalance(-500);System.out.println(account.getBalance());-500.0Which line in the class breaks the protection?
Pick the line with the bug.
setBalance stores any value without a check. The field is hidden, but the setter opens it again. Remove it and set the starting balance in the constructor. After that, it changes only through deposit and withdraw.
Exercise
Hidden data
Solve the tasks in your editor. All fields are private.
1. Person. Person with a name and an age, a constructor, the getters getName() and getAge() and introduce().
Maria is 34 years old.Hi, I'm Maria and I'm 34 years old.2. An account with no minus. Account with an owner and a balance. A deposit accepts only a positive amount. A withdrawal returns whether it went through and never lets the balance go below 0. Code outside can read the balance, but can't write it.
Account account = new Account("Peter", 50);account.deposit(25);account.deposit(-10);System.out.println(account.withdraw(100));System.out.println(account.withdraw(40));System.out.println("Balance: €" + account.getBalance());falsetrueBalance: €35.03. Animal. Animal with a species and an age. The species is set in the constructor and is only read after that. The age has a setter that refuses a negative number and prints a message. The constructor goes through the same check too.
Animal cat = new Animal("cat", 3);cat.setAge(4);cat.setAge(-1);System.out.println(cat.getSpecies() + ", " + cat.getAge() + " years old");Invalid age: -1cat, 4 years oldMore tasks
- Grade. A
Gradeclass with a subject name and a grade from 2 to 6. The setter refuses anything outside those limits. Try it with 5, 7 and 1. - Product with a discount. Add a method to the
Productfrom the lesson that applies a discount in percent and accepts only 1 to 90%. The price has no setter.
Test