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
- RequestDeposit or withdraw quantity
- VerificationDo not exceed capacity and live up to inventory
- 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 ExampleClassClass 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.
- Fieldprivate saves internal state
- MethodPublic controlled entry
- VerificationFirst determine the legality
- 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
-999Encapsulation 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.
- Inside the boxMaintainable private fields
- Independent external classPrivate fields cannot be written directly
- Disclosure methodProvide allowed operations
- 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.
- getCountRead current inventory
- remainingSpaceRead remaining space
- QueryDo not modify status
- 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
6405 / 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.
Is the quantity legal?
| Condition | Path |
|---|---|
| true | Write quantity, return true |
| false | Leave 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
906 / 15
Check to keep the legal range
The failed operation should not modify the object. Check all conditions first before writing fields.
Is the quantity legal?
| Condition | Path |
|---|---|
| true | Write quantity, return true |
| false | Leave 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
true07 / 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.
- trueIndicates that the operation is completed
- falseRejection and unchanged
- HintCaller selection feedback
- 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
true08 / 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.
- 0Legal empty box
- capacityLegal full box
- Negative numberNegative inventory is not allowed
- 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| Enter | Is it allowed? | Reason |
|---|---|---|
| Store 0 | No | The quantity must be greater than 0 |
| Deposit until just full | Yes | Equal to capacity is legal |
| Take out all | Yes | Inventory can be 0 |
| Take out excess inventory | No | Cannot 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.
- Internal arrayprivate is still a variable object
- Return directlyInternal references will be shared
- copyOfNew array, element reference shallow copy
- 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
null10 / 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.
- depositControlled Deposit
- withdrawControlled Retrieval
- getCountInventory query
- remainingSpaceBalance query
| Disclosure method | Enter | Back | Guarantee |
|---|---|---|---|
| deposit | Positive quantity | boolean | Do not exceed capacity |
| withdraw | Positive quantity | boolean | Not less than 0 |
| getCount | None | int | Read inventory only |
| remainingSpace | None | int | Read space only |
| describe | None | String | Provide 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.
- NormalDeposit 12
- BorderJust full, just empty
- ErrorFull boxes are deposited and empty boxes are taken out
- 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| Test | Operation | Expected |
|---|---|---|
| Normal | Capacity 64 stored in 12 | true inventory 12 |
| Border | Re-deposit 52 | true inventory 64 |
| Error | Store 1 more when the box is full | false inventory 64 |
| Border | Take out 64 | true inventory 0 |
| Error | Empty box takes 1 | false 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.
- Direct accessCompiler blocks
- Missing verificationThe compiler will not do it for you
- Modify first and then rejectDestroyed state
- 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
-313 / 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.
- ConstructorInitialization capacity and name
- depositOnly add inventory when the balance is sufficient
- withdrawDeduction will be made only when the inventory is sufficient
- 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
6414 / 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.
- Wooden PickaxeMob life changed to private
- Iron PickaxeMaintain 0 to 20 hunger value
- Diamond PickaxeDesigning a stateful password box
- 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.
| Self-evaluation | Can | Still 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.
| Category | Input of your choice | Expectation and actual |
|---|---|---|
| Normal | A common value or a successful operation | Is it consistent line by line? |
| Border | Minimum value, maximum value and adjacent value | Is there one less square and one more step? |
| Error | Illegal quantity, missing object or incorrect syntax | Explicitly 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.