← Java tutorials

LESSON 20 / 40 · FREE TUTORIAL

Lock Your Chest

Encapsulation private and verification

100–120 minutesThis chapter uses JDK 21 and does not require the preview function.Object-oriented Introduction

WHAT YOU WILL MAKE

Making a safety box

Can use private to protect object fields; can provide controlled reading and modification through methods; can use verification to maintain capacity and non-negative inventory; can avoid handing over internal variable arrays directly to external code. Prerequisites: class, object, constructor, method parameters and return value. Today we need to install doors and locks for data, and we also need to design buttons that are really easy to use.

Before you start: It is recommended to complete Lessons 17–19 first

Box maintenance inventory with controlled operations
  1. RequestDeposit or withdraw quantity
  2. VerificationDo not exceed capacity and live up to inventory
  3. StatusWrite only if successful, unchanged if failed

01 / 15

Before you begin: Put the examples in the right place

This chapter uses JDK 21 and does not require the preview function. A complete program is given for each common example, saved with annotated file names. Multiple examples are placed in their own folders to avoid auxiliary classes with the same name.

Terminal command: Replace the Chinese placeholder with the actual name

javac -encoding UTF-8 ExampleFile.java
java ExampleClass

Class names are case-sensitive. If the complete challenge code is divided into multiple sections, please connect them in the same file in order. Do not create separate classes for the continued sections. Run verification only lists the program output; additional input sequences are provided where input is required.

02 / 15

Encapsulation allows the rules to have only one entrance

The public field is like a diamond warehouse without a door: any code can change the inventory to -999. Encapsulation puts the data and the rules for maintaining the data in the same class.

private Hide the field in the box
  1. Fieldprivate saves internal state
  2. MethodPublic controlled entry
  3. VerificationFirst determine the legality
  4. Rules0 to inventory within capacity

L20Example01.java · Complete independent example

class Chest { public int diamonds; }
public class L20Example01 {
    public static void main(String[] args) {
        Chest chest = new Chest();
        chest.diamonds = -999;
        System.out.println(chest.diamonds);
    }
}

Output

-999

Encapsulation target

  • Fields cannot be written arbitrarily externally.
  • All modifications go through the same set of checks.
  • Classes expose clear and concise methods to the outside world.
  • Public operations that pass verification should maintain the legal status of the object.

03 / 15

private visible range

This chapter writes the box and the calling program into two independent classes: private fields can be maintained inside the box, and the calling program uses public methods. Java also allows access to private members between the same outermost class and its nested classes; do not use this exception to test external access rules.

Private fields can only be accessed directly in their own class body
  1. Inside the boxMaintainable private fields
  2. Independent external classPrivate fields cannot be written directly
  3. Disclosure methodProvide allowed operations
  4. ExceptionsNested members of the same outer class

BadPrivate.java · Intentional error example

class Wallet { private int emeralds; }
public class BadPrivate {
    public static void main(String[] args) {
        Wallet wallet = new Wallet();
        wallet.emeralds = 99;
    }
}

04 / 15

getter provides controlled reading

If the external party needs to view the status, a getter or more meaningful query method can be provided. Not every field must have a getter.

The read method only hands over the required information
  1. getCountRead current inventory
  2. remainingSpaceRead remaining space
  3. QueryDo not modify status
  4. Minimum disclosureOnly provide information that is truly needed

L20Example03.java · Complete independent example

class Chest {
    private final String item;
    private final int capacity;
    private int count;
    public Chest(String item, int capacity) {
        this.item = item == null || item.isBlank() ? "Unknown item" : item.trim();
        this.capacity = Math.max(1, capacity);
    }
    public boolean deposit(int amount) {
        if (amount <= 0 || amount > capacity - count) return false;
        count += amount;
        return true;
    }
    public boolean withdraw(int amount) {
        if (amount <= 0 || amount > count) return false;
        count -= amount;
        return true;
    }
    public int getCount() { return count; }
    public int remainingSpace() { return capacity - count; }
    public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example03 {
    public static void main(String[] args) {
        Chest chest = new Chest("diamond", 64);
        System.out.println(chest.getCount());
        System.out.println(chest.remainingSpace());
    }
}

Output

0
64

05 / 15

Behavioral methods are safer than universal setters

setCount(-999) makes the caller responsible for all rules. Deposit and withdraw express real actions and can be checked centrally.

Check first, then modify

Is the quantity legal?

ConditionPath
trueWrite quantity, return true
falseLeave as is, return false

↓ Caller Check Results

L20Example04.java · Complete independent example

class Chest {
    private final String item;
    private final int capacity;
    private int count;
    public Chest(String item, int capacity) {
        this.item = item == null || item.isBlank() ? "Unknown item" : item.trim();
        this.capacity = Math.max(1, capacity);
    }
    public boolean deposit(int amount) {
        if (amount <= 0 || amount > capacity - count) return false;
        count += amount;
        return true;
    }
    public boolean withdraw(int amount) {
        if (amount <= 0 || amount > count) return false;
        count -= amount;
        return true;
    }
    public int getCount() { return count; }
    public int remainingSpace() { return capacity - count; }
    public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example04 {
    public static void main(String[] args) {
        Chest chest = new Chest("diamond", 64);
        System.out.println(chest.deposit(12));
        System.out.println(chest.withdraw(3));
        System.out.println(chest.getCount());
    }
}

Output

true
true
9

06 / 15

Check to keep the legal range

The failed operation should not modify the object. Check all conditions first before writing fields.

Check first, then modify

Is the quantity legal?

ConditionPath
trueWrite quantity, return true
falseLeave as is, return false

↓ Caller Check Results

L20Example05.java · Complete independent example

class Chest {
    private final String item;
    private final int capacity;
    private int count;
    public Chest(String item, int capacity) {
        this.item = item == null || item.isBlank() ? "Unknown item" : item.trim();
        this.capacity = Math.max(1, capacity);
    }
    public boolean deposit(int amount) {
        if (amount <= 0 || amount > capacity - count) return false;
        count += amount;
        return true;
    }
    public boolean withdraw(int amount) {
        if (amount <= 0 || amount > count) return false;
        count -= amount;
        return true;
    }
    public int getCount() { return count; }
    public int remainingSpace() { return capacity - count; }
    public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example05 {
    public static void main(String[] args) {
        Chest chest = new Chest("diamond", 64);
        chest.deposit(12);
        int before = chest.getCount();
        boolean ok = chest.deposit(Integer.MAX_VALUE);
        System.out.println(!ok && chest.getCount() == before);
    }
}

Output

true

07 / 15

boolean reports operation results

The modification method returns boolean, which allows the caller to know whether the operation is completed without having to guess the field changes.

The caller decides the next step based on success or failure.
  1. trueIndicates that the operation is completed
  2. falseRejection and unchanged
  3. HintCaller selection feedback
  4. TestCheck results and status simultaneously

L20Example06.java · Complete independent example

class Chest {
    private final String item;
    private final int capacity;
    private int count;
    public Chest(String item, int capacity) {
        this.item = item == null || item.isBlank() ? "Unknown item" : item.trim();
        this.capacity = Math.max(1, capacity);
    }
    public boolean deposit(int amount) {
        if (amount <= 0 || amount > capacity - count) return false;
        count += amount;
        return true;
    }
    public boolean withdraw(int amount) {
        if (amount <= 0 || amount > count) return false;
        count -= amount;
        return true;
    }
    public int getCount() { return count; }
    public int remainingSpace() { return capacity - count; }
    public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example06 {
    public static void main(String[] args) {
        Chest chest = new Chest("diamond", 64);
        if (chest.deposit(12)) System.out.println("Deposited");
        int before = chest.getCount();
        boolean ok = chest.withdraw(999);
        System.out.println(!ok && chest.getCount() == before);
    }
}

Output

Deposited
true

08 / 15

Capacity and non-negative inventory

The core invariant condition of SecureChest is 0 <= count <= capacity. The constructor first establishes the legal capacity, and then each method continues to hold it.

The upper and lower borders are like the bottom and lid of a box
  1. 0Legal empty box
  2. capacityLegal full box
  3. Negative numberNegative inventory is not allowed
  4. Crossing the lineExceeding capacity is not allowed

L20Example07.java · Complete independent example

class Chest {
    private final String item;
    private final int capacity;
    private int count;
    public Chest(String item, int capacity) {
        this.item = item == null || item.isBlank() ? "Unknown item" : item.trim();
        this.capacity = Math.max(1, capacity);
    }
    public boolean deposit(int amount) {
        if (amount <= 0 || amount > capacity - count) return false;
        count += amount;
        return true;
    }
    public boolean withdraw(int amount) {
        if (amount <= 0 || amount > count) return false;
        count -= amount;
        return true;
    }
    public int getCount() { return count; }
    public int remainingSpace() { return capacity - count; }
    public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example07 {
    public static void main(String[] args) {
        Chest chest = new Chest("diamond", 0);
        System.out.println(chest.remainingSpace());
        System.out.println(chest.deposit(1));
        System.out.println(chest.deposit(1));
        System.out.println(chest.withdraw(1));
    }
}

Output

1
true
false
true
Acceptance and inspection forms
EnterIs it allowed?Reason
Store 0NoThe quantity must be greater than 0
Deposit until just fullYesEqual to capacity is legal
Take out allYesInventory can be 0
Take out excess inventoryNoCannot be a negative number

09 / 15

Do not expose internal variable arrays

If the private array is returned as is by the getter, the internal content can still be modified by external references. Although the field is private, the backdoor is open.

Handing over the array reference is equivalent to giving away the backdoor key.
  1. Internal arrayprivate is still a variable object
  2. Return directlyInternal references will be shared
  3. copyOfNew array, element reference shallow copy
  4. SafetyString is immutable, the object still needs to be checked

L20Example08.java · Complete independent example

class ItemBox {
    private final String[] items = {"diamond", "Torch"};
    public String[] getItemsCopy() {
        return java.util.Arrays.copyOf(items, items.length);
    }
    public String getItem(int index) {
        if (index < 0 || index >= items.length) return null;
        return items[index];
    }
}
public class L20Example08 {
    public static void main(String[] args) {
        ItemBox box = new ItemBox();
        String[] copy = box.getItemsCopy();
        copy[0] = "Soil";
        System.out.println(copy[0]);
        System.out.println(box.getItem(0));
        System.out.println(box.getItem(9));
    }
}

Output

Soil
diamond
null

10 / 15

Design the public interface of the box

The "public interface" here refers to the set of methods that can be used by the caller, not the interface keyword of Java. First write what the caller needs to do, and then design the minimum set of methods.

List the allowed operations first and then decide the fields
  1. depositControlled Deposit
  2. withdrawControlled Retrieval
  3. getCountInventory query
  4. remainingSpaceBalance query
Acceptance and inspection forms
Disclosure methodEnterBackGuarantee
depositPositive quantitybooleanDo not exceed capacity
withdrawPositive quantitybooleanNot less than 0
getCountNoneintRead inventory only
remainingSpaceNoneintRead space only
describeNoneStringProvide summary

11 / 15

Three types of test locking rules

Package testing not only looks at the return value, but also confirms the object status through public query.

Both normal boundary and error tests check status
  1. NormalDeposit 12
  2. BorderJust full, just empty
  3. ErrorFull boxes are deposited and empty boxes are taken out
  4. StatusNo changes allowed after rejection

L20Example10.java · Complete independent example

class Chest {
    private final String item;
    private final int capacity;
    private int count;
    public Chest(String item, int capacity) {
        this.item = item == null || item.isBlank() ? "Unknown item" : item.trim();
        this.capacity = Math.max(1, capacity);
    }
    public boolean deposit(int amount) {
        if (amount <= 0 || amount > capacity - count) return false;
        count += amount;
        return true;
    }
    public boolean withdraw(int amount) {
        if (amount <= 0 || amount > count) return false;
        count -= amount;
        return true;
    }
    public int getCount() { return count; }
    public int remainingSpace() { return capacity - count; }
    public String describe() { return item + " " + count + "/" + capacity; }
}
public class L20Example10 {
    public static void main(String[] args) {
        Chest chest = new Chest("diamond", 64);
        System.out.println(chest.deposit(12));
        System.out.println(chest.deposit(52));
        System.out.println(chest.deposit(1));
        System.out.println(chest.getCount());
        System.out.println(chest.withdraw(64));
        System.out.println(chest.withdraw(1));
        System.out.println(chest.getCount());
    }
}

Output

true
true
false
64
true
false
0
Acceptance and inspection forms
TestOperationExpected
NormalCapacity 64 stored in 12true inventory 12
BorderRe-deposit 52true inventory 64
ErrorStore 1 more when the box is fullfalse inventory 64
BorderTake out 64true inventory 0
ErrorEmpty box takes 1false inventory 0

12 / 15

Packaging Failure Clinic

The compiler can detect direct access to private outside the class, but cannot automatically detect setter leaks or array reference leaks.

Private error reporting and rule loopholes should be handled separately
  1. Direct accessCompiler blocks
  2. Missing verificationThe compiler will not do it for you
  3. Modify first and then rejectDestroyed state
  4. Array leakInternal mutable references disclosed

L20Example11.java · Complete independent example

class BrokenChest {
    private int count = 0;
    public boolean withdraw(int n) {
        count -= n;
        return count >= 0;
    }
    public int getCount() { return count; }
}
public class L20Example11 {
    public static void main(String[] args) {
        BrokenChest c = new BrokenChest();
        System.out.println(c.withdraw(3));
        System.out.println(c.getCount());
    }
}

Output

false
-3

13 / 15

Challenges Implementation of a safe box

The challenge program is called SecureChest. A chest holds an item, capacity and quantity, and only public methods can be used externally.

Private fields and public methods jointly maintain inventory rules
  1. ConstructorInitialization capacity and name
  2. depositOnly add inventory when the balance is sufficient
  3. withdrawDeduction will be made only when the inventory is sufficient
  4. QueryVerified by public method

SecureChest.java · Complete standalone example

class Chest {
    private final String item;
    private final int capacity;
    private int count;
    public Chest(String item, int capacity) {
        this.item = item == null || item.isBlank() ? "Unknown item" : item.trim();
        this.capacity = Math.max(1, capacity);
    }
    public boolean deposit(int amount) {
        if (amount <= 0 || amount > capacity - count) return false;
        count += amount;
        return true;
    }
    public boolean withdraw(int amount) {
        if (amount <= 0 || amount > count) return false;
        count -= amount;
        return true;
    }
    public int getCount() { return count; }
    public int remainingSpace() { return capacity - count; }
    public String describe() { return item + " " + count + "/" + capacity; }
}
public class SecureChest {
    public static void main(String[] args) {
        Chest chest = new Chest("diamond", 64);
        System.out.println(chest.deposit(12));
        System.out.println(chest.deposit(52));
        System.out.println(chest.deposit(1));
        System.out.println(chest.describe());
        System.out.println(chest.withdraw(64));
        System.out.println(chest.withdraw(1));
        System.out.println(chest.describe());
        System.out.println(chest.remainingSpace());
    }
}

Output

true
true
false
diamond 64/64
true
false
diamond 0/64
64

14 / 15

Expansion tasks and unit review

Lessons 17 to 20 complete the first stage of object-oriented: blueprint, reference, birth and encapsulation. The assignment must submit both the interface table and the test table.

The third-level task continues to strengthen the object boundary
  1. Wooden PickaxeMob life changed to private
  2. Iron PickaxeMaintain 0 to 20 hunger value
  3. Diamond PickaxeDesigning a stateful password box
  4. ReviewEncapsulation is the constraint of code access

Third gear operation

  • Wooden Pickaxe: Change Mob's health to private, and provide takeDamage, heal and getHealth.
  • Iron Pickaxe: Design FoodBar, the hunger value always remains from 0 to 20, and illegal operations return false.
  • Diamond Pickaxe: Design a password box that will be locked if you enter it incorrectly three times, and do not allow external direct modification of the password and lock status.
Acceptance and inspection forms
Self-evaluationCanStill need to practice
I will use private□□
I can design behavioral methods□□
I will verify the failure without changing the status□□

15 / 15

Own acceptance record

Write expectations before running, and record actual results after running. Normal cases show that the program does things; edge cases show that it knows when to stop; error cases show that it rejects unreasonable requests.

Write the evidence into the record
CategoryInput of your choiceExpectation and actual
NormalA common value or a successful operationIs it consistent line by line?
BorderMinimum value, maximum value and adjacent valueIs there one less square and one more step?
ErrorIllegal quantity, missing object or incorrect syntaxExplicitly reject? Is the status maintained?
  • Write a bug as: triggering conditions, phenomena, causes, and minimal repairs.
  • Organize a rule worth remembering and explain it in your own words. Don't just copy the definition.
  • Change an example, keep a record of the test, and let the results prove your understanding.

TAKE IT WITH YOU

Take the lesson offline

The complete lesson, code, diagrams and exercises, in your preferred language.