Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
217: Building a Simple Banking Application
When we start building a "real" application, like a banking system, we move away from simple logic puzzles and start dealing with state. The hardest part isn't usually the syntax; it's making sure the state of your objects changes exactly how you think it does. I'll show you a snippet of a BankAccount class that looks correct at a glance, but it's fundamentally broken.
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public void withdraw(double amount) {
double balance = this.balance;
balance -= amount;
System.out.println("Withdrawal successful!");
}
public double getBalance() {
return balance;
}
}
If you run this, you'll notice something frustrating: you can call withdraw(50.0) a hundred times, and the balance will never actually go down. Why? Let's look at what's happening under the hood.
The Local Variable Shadowing Trap
Look closely at the first line inside the withdraw method. You've written double balance = this.balance;. By adding that double keyword, you aren't updating the class's balance; you're creating a brand new local variable that just happens to have the same name as your field.
You're essentially taking a photo of the balance, scribbling a lower number on the photo, and then throwing the photo away. The actual account balance remains untouched. I've seen this happen to senior devs when they're rushing; it's a classic "shadowing" error. To fix this, we need to operate directly on the instance field.
Preventing Accidental Bankruptcy
Even after we fix the variable issue, this code has a massive logic flaw: it allows the balance to go negative. In a real banking app, we need "guards"—conditional checks that stop a method from executing if the requirements aren't met.
Here is the professional way to handle this. I've replaced the local variable with a direct update and added a check to ensure the user actually has the money they're trying to take.
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public void deposit(double amount) {
if (amount <= 0) {
System.out.println("Deposit amount must be positive.");
return;
}
this.balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0) {
System.out.println("Withdrawal amount must be positive.");
return;
}
if (amount > this.balance) {
System.out.println("Insufficient funds! Transaction cancelled.");
return;
}
this.balance -= amount;
System.out.println("Withdrawal successful. New balance: " + this.balance);
}
public double getBalance() {
return balance;
}
}
A quick side note: in a real production environment, you'd never use double for money because of floating-point precision errors (where 0.1 + 0.2 equals 0.30000000000000004). You'd use BigDecimal or store the balance as cents in a long. But for this lesson, we'll stick to double to keep the focus on the application logic.
Connecting the Pieces in a Main Application
Now that our BankAccount logic is sound, you can build a coordinator class to manage these accounts. The key here is to treat the BankAccount as a "black box"—your main application shouldn't touch the balance directly; it should only interact with it through the deposit and withdraw methods we just built. This is the core of encapsulation.
📋 Practical Task
Implementing an Inter-Account Transfer System
Now it's your turn to expand this system. Your task is to create a class called BankManager that can handle transfers between two different account objects.
- Create a method
public void transfer(BankAccount fromAccount, BankAccount toAccount, double amount). - The method must ensure that the
fromAccounthas enough funds before attempting the transfer. - If the funds are available, the amount should be withdrawn from the
fromAccountand deposited into thetoAccount. - If the funds are insufficient, print an error message and do not modify either account.
- In your
mainmethod, create twoBankAccountobjects with different initial balances and perform a successful transfer and a failed transfer (due to insufficient funds) to verify your logic.
There are no comments for now.