Intermediate Java Programming and Design
Core Classes and Objects
Blueprints for Objects
In Java, everything revolves around objects. But to create an object, you first need a blueprint. That blueprint is called a class. A class bundles together related data (variables, also called fields or attributes) and the behaviors that can act on that data (methods).
Think of a class like an architectural drawing for a house. The drawing specifies the number of rooms, the size of the windows, and where the doors go. An object is an actual house built from that drawing. You can build many houses (objects) from a single blueprint (class), and each house will have its own unique state, like its color or address.
Let's work with a practical example: a bank account. A bank account has data (account number, balance) and behaviors (deposit, withdraw). We can model this with a BankAccount class.
Constructing Your Objects
When an object is created from a class, a special method called a constructor is run. Its job is to initialize the object's state. A constructor has the same name as the class and no return type.
public class BankAccount {
String accountNumber;
double balance;
// Constructor for a new account with a zero balance
public BankAccount(String accNumber) {
accountNumber = accNumber;
balance = 0.0;
}
}
But what if you want to open an account and make an initial deposit at the same time? You can create another constructor that accepts different parameters. This is called constructor overloading.
public class BankAccount {
String accountNumber;
double balance;
// First constructor
public BankAccount(String accountNumber) {
this.accountNumber = accountNumber;
this.balance = 0.0;
}
// Overloaded constructor for initial deposit
public BankAccount(String accountNumber, double initialBalance) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
}
Notice the this keyword. It's a reference to the current object itself. It's used here to distinguish between the class field accountNumber and the constructor's parameter, which happens to have the same name. Using this removes ambiguity and is a common practice.
Protecting Your Data
A core principle of object-oriented programming is encapsulation. It means bundling the data and the methods that operate on that data within one unit (the class) and restricting direct access to some of the object's components.
Why is this important? Imagine if you could directly change the balance of a BankAccount object from anywhere in your code. Someone could accidentally set it to a negative number, which shouldn't be possible.
Encapsulation is like having a car's dashboard. You can use the steering wheel and pedals (public methods) to control the car, but you can't directly manipulate the engine's pistons (private data). This prevents you from breaking things.
We achieve this using access modifiers like private and public.
public: Accessible from any other class.private: Accessible only within its own class.
By making fields private, we force other parts of the program to use public methods to interact with the data. This allows us to add validation logic.
public class BankAccount {
private String accountNumber;
private double balance;
public BankAccount(String accountNumber, double initialBalance) {
this.accountNumber = accountNumber;
// Ensure initial balance is not negative
if (initialBalance >= 0) {
this.balance = initialBalance;
}
}
// A 'getter' method to read the balance
public double getBalance() {
return this.balance;
}
// A 'setter' method to deposit money
public void deposit(double amount) {
if (amount > 0) {
this.balance += amount;
}
}
// A method to withdraw money
public void withdraw(double amount) {
if (amount > 0 && this.balance >= amount) {
this.balance -= amount;
}
}
}
Now, the only way to change the balance is through the deposit and withdraw methods, which contain logic to prevent invalid operations like depositing a negative amount or withdrawing more money than is available.
Where Objects Live
When your Java program runs, it uses two main areas of memory: the Stack and the Heap.
-
Stack: This memory is fast and highly organized. It's used for storing method calls and local variables (including primitives like
intanddouble). When a method is called, a new frame is pushed onto the stack for its variables. When the method finishes, its frame is popped off, and the memory is reclaimed automatically. -
Heap: This is a larger pool of memory used for storing objects. When you use the
newkeyword to create an object, Java allocates memory for it on the heap. This memory is not automatically reclaimed when a method ends. Instead, Java's Garbage Collector periodically cleans up objects on the heap that are no longer referenced by any part of the program.
When you declare an object variable like BankAccount myAccount;, the variable myAccount itself (the reference) lives on the stack. The actual BankAccount object created with new BankAccount(...) lives on the heap.
Sharing Among Objects
Sometimes, you want a piece of data to be shared across all instances of a class. For example, what if our bank wanted to set a single, universal interest rate for all savings accounts? It wouldn't make sense for each BankAccount object to have its own separate interestRate field.
This is where the static keyword comes in. A static member belongs to the class itself, not to any individual object (instance). There is only one copy of a static variable, no matter how many objects you create.
public class BankAccount {
private String accountNumber;
private double balance;
// Static variable shared by all instances
private static double interestRate = 0.02;
public BankAccount(String accountNumber, double initialBalance) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
// An instance method - belongs to a specific object
public void applyInterest() {
this.balance += this.balance * interestRate;
}
// A static method - belongs to the class
public static void setInterestRate(double newRate) {
if (newRate >= 0) {
interestRate = newRate;
}
}
}
You access static members using the class name, not an object reference:
BankAccount.setInterestRate(0.025);
Static methods, like setInterestRate, cannot use the this keyword or access non-static instance variables like balance. This is because they aren't associated with a specific object; they belong to the class as a whole.
Let's check your understanding of these core concepts.
In object-oriented programming with Java, what is the best definition of a "class"?
What is the primary role of a constructor in a Java class?
Understanding how classes and objects manage data and memory is fundamental to writing robust, error-free Java applications. These concepts form the architectural backbone of the language.