Why control access
So far every field was public. That is convenient, but it exposes the inside of the class. Any code anywhere can change dog.name to anything, and if you later decide to check or compute that value differently, every piece of code that touches the field has to change with it.
An access modifier is a keyword that decides which code may see a member of a class. The members are its fields, methods and constructors.
Encapsulation
Encapsulation is the practice of hiding an object's data behind methods. The class controls how its data is read and written, and outside code only sees the public methods. Here is an account that never allows a negative deposit:
class Account {
private double balance;
public void deposit(double amount) {
if (amount < 0) throw new IllegalArgumentException();
balance += amount;
}
}
Because balance is private, the only way to change it is deposit, and deposit enforces the rule. If the field were public, any caller could write account.balance = -500; and break it. Rules like this one are called invariants, and private fields are how a class protects them.
The four levels
| Modifier | Who can use the member |
|---|---|
public | Code anywhere. |
private | Only code inside the same class. |
protected | Code in the same package, and subclasses. |
| (no modifier) | Only code in the same package. This is called package-private. |
For now, focus on public and private. The usual pattern is to make fields private and give outside code only the methods it needs.
Getters and setters
A method that returns a private field is called a getter, and one that assigns it is a setter. By convention they are named getName and setName, or isActive for a boolean getter. A getter is the simplest case:
public String getName() {
return name;
}
Code inside the class can use the private field directly, so the getter just returns it. Code outside the class cannot:
Dog rex = new Dog("Rex");
System.out.println(rex.name);
This fails to compile with the error name has private access in Dog. The allowed way to read the value is rex.getName().
A class that offers only a getter makes the field read-only from outside: callers can see the name but never change it. Adding a setter is a choice, and a setter can validate its argument, which a public field never could.
The lesson's exercise, step by step
The starter code gives Dog a private name field and a constructor. The mainFunction prints rex.getName(), but no such method exists yet. There are two steps:
- Declare the getter. Below the constructor, write
public String getName(). It is public so thatSolutionmay call it, and its return type isStringbecause the field is aString. - Return the field. The body is
return name;. InsideDog, the private field is accessible directly.
The finished program:
class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Solution {
public void mainFunction() {
Dog rex = new Dog("Rex");
System.out.println("Dog's name: " + rex.getName());
}
}
It prints:
Dog's name: Rex
The constructor is public too. A constructor can be made private as well, but then no code outside the class could call new Dog("Rex").
Details worth knowing
- Private means private to the class, not to the object. Code in the
Dogclass can read the private field of any otherDogobject, not just its own. - The default is not public. A member with no modifier is visible only inside its package, so it is easy to leave something less visible than intended.
- Private methods are common. Helper methods that only the class itself needs should be
privatetoo, so they can change freely later. - Changing a field is a bigger change than it looks. If the field is private, you can rename it or store it differently, as long as
getName()still returns the right result.
Common mistakes
- Reading the field from outside.
rex.nameinSolutionis a compile error once the field is private. Userex.getName(). - Making the getter private. A private getter is visible only inside
Dog, so the call inSolutionfails with the same kind of error. - Using the wrong return type.
public void getName()can't return the name. The return type must beString. - Forgetting
return. A body of justname;is not a valid statement, and a missingreturnin a non-void method is a compile error. - Adding a setter to every field out of habit. A setter that does nothing but assign the field gives back most of what
privateprotects. Add one only when outside code really needs to change the value. - Mismatched names. The getter can have any name, but sticking with
getNamefor a field callednamefollows the convention that other Java code and tools expect.
Key takeaways
- An access modifier controls which code may use a member of a class.
publicmeans everywhere, andprivatemeans inside the same class only.protectedand package-private sit in between.- Make fields private and expose what outside code needs through public methods. That is encapsulation.
- A getter such as
public String getName() { return name; }gives read access to a private field. - Touching a private member from another class is a compile error, not a runtime one.