Topic Note Part 1: Classes, Objects & Encapsulation¶
Course: Java Programming Masterclass - Tim Buchalka (Udemy)
Section: 07. Mastering Java OOP Classes & Inheritance
Status: Complete
Learning Objectives¶
- Understand the fundamentals of Object-Oriented Programming (OOP)
- Master class creation with fields, methods, and access modifiers
- Implement encapsulation using getters and setters
- Learn to create and instantiate objects using the
newkeyword - Understand constructors: default, parameterized, overloading, and chaining
- Distinguish between references, objects, and instances
- Compare static vs instance members (variables and methods)
- Work with POJOs (Plain Old Java Objects) and Java Records
Core Concepts¶
What is Object-Oriented Programming?¶
Object-Oriented Programming (OOP) is a programming paradigm that models real-world entities as software objects containing both data (state) and code (behavior).
graph TD
subgraph "Real World"
A[Object: Computer]
B[State: RAM, OS, HDD Size]
C[Behavior: Boot, Shutdown, Beep]
end
subgraph "Software World"
D[Class: Computer Blueprint]
E[Fields: ram, os, hddSize]
F[Methods: boot, shutdown, beep]
end
A --> D
B --> E
C --> F
| Real-World Concept | Java Equivalent |
|---|---|
| Object characteristics | Fields (variables/attributes) |
| Object actions | Methods |
| Blueprint/Template | Class |
Key Insight
A class is not a data type in the traditional sense—it's a blueprint or template for creating objects. Think of it as a powerful, user-defined data type.
Classes in Java¶
Anatomy of a Class¶
A class is declared using the class keyword and contains members (fields and methods).
public class Car {
// Fields (instance variables) - represent STATE
private String make;
private String model;
private String color;
private int doors;
private boolean convertible;
// Methods - represent BEHAVIOR
public void describeCar() {
System.out.println(doors + "-door " + color + " " + make + " " + model);
}
}
Access Modifiers¶
Java provides four access levels for controlling visibility of class members:
| Modifier | Class | Package | Subclass | World |
|---|---|---|---|---|
public |
✅ | ✅ | ✅ | ✅ |
protected |
✅ | ✅ | ✅ | ❌ |
| no modifier (package-private) | ✅ | ✅ | ❌ | ❌ |
private |
✅ | ❌ | ❌ | ❌ |
Best Practice
Always make fields private unless you have a compelling reason not to. This is the foundation of encapsulation.
For Top-Level Classes¶
A top-level class (defined in its own source file) can only have:
public- accessible from anywhere- no modifier (package-private) - accessible only within the same package
Encapsulation¶
What is Encapsulation?¶
Encapsulation has two meanings in OOP:
- Bundling of data (fields) and behavior (methods) into a single unit (class)
- Information hiding - restricting direct access to internal state
Why Encapsulate?¶
flowchart LR
subgraph Without_Encapsulation
A[External Code] -->|Direct Access| B[car.make = Invalid]
B --> C[Invalid State]
end
subgraph With_Encapsulation
D[External Code] -->|setMake| E{Validation}
E -->|Valid| F[Valid State]
E -->|Invalid| G[Rejected]
end
Getters and Setters¶
Getters retrieve field values; Setters modify them with optional validation.
public class Car {
private String make = "Tesla";
// GETTER - retrieves the value
public String getMake() {
return make;
}
// SETTER - sets the value with validation
public void setMake(String make) {
if (make == null) {
this.make = "Unknown";
return;
}
String lowerCaseMake = make.toLowerCase();
switch (lowerCaseMake) {
case "holden", "porsche", "tesla" -> this.make = make;
default -> this.make = "Unsupported";
}
}
}
Naming Conventions¶
| Field Type | Getter Name | Setter Name |
|---|---|---|
String name |
getName() |
setName(String name) |
int age |
getAge() |
setAge(int age) |
boolean active |
isActive() |
setActive(boolean active) |
The this Keyword
When a parameter name matches a field name, use this.fieldName to refer to the instance field:
Creating Objects¶
The new Keyword¶
Objects are created (instantiated) using the new keyword:
// Declaration and instantiation
Car car = new Car();
// Using the object
car.setMake("Porsche");
car.setModel("Carrera");
car.describeCar(); // Output: 2-door Gray Porsche Carrera
What Happens in Memory?¶
graph LR
subgraph "Stack Memory"
A[car reference]
end
subgraph "Heap Memory"
B[Car Object<br/>make: Porsche<br/>model: Carrera<br/>...]
end
A -->|points to| B
Default Field Values¶
Unlike local variables, class fields are automatically initialized with default values:
| Type | Default Value |
|---|---|
byte, short, int, long |
0 |
float, double |
0.0 |
char |
'\u0000' (null character) |
boolean |
false |
Reference types (String, objects) |
null |
null vs. Uninitialized
- Uninitialized variable: Compile-time error when accessed
- null reference: Compiles but throws
NullPointerExceptionat runtime
Constructors¶
What is a Constructor?¶
A constructor is a special code block that initializes an object when it's created. It:
- Has the same name as the class
- Has no return type (not even
void) - Is called automatically when
newis used
public class Account {
private String number;
private double balance;
// Constructor
public Account(String number, double balance) {
this.number = number;
this.balance = balance;
}
}
// Usage
Account bobsAccount = new Account("12345", 1000.00);
The Default Constructor¶
If you don't declare any constructor, Java provides an implicit default (no-args) constructor:
Critical Rule
If you declare ANY constructor, Java will NOT create the default constructor for you. You must explicitly declare it if needed.
Constructor Overloading¶
Like methods, constructors can be overloaded with different parameter lists:
public class Customer {
private String name;
private double creditLimit;
private String email;
// Constructor 1: All fields
public Customer(String name, double creditLimit, String email) {
this.name = name;
this.creditLimit = creditLimit;
this.email = email;
}
// Constructor 2: Name and email only (default credit limit)
public Customer(String name, String email) {
this(name, 1000.00, email); // Calls Constructor 1
}
// Constructor 3: No args (all defaults)
public Customer() {
this("Default name", "nobody@email.com"); // Calls Constructor 2
}
}
Constructor Chaining with this()¶
The this() call invokes another constructor in the same class:
flowchart TD
A["new Customer()"] --> B["Customer() no-args"]
B -->|"this('Default', 'email')"| C["Customer(name, email)"]
C -->|"this(name, 1000, email)"| D["Customer(name, creditLimit, email)"]
D --> E["Fields initialized"]
Critical Rule
this() MUST be the first statement in the constructor body:
Best Practice: Single Point of Initialization
Put all field initialization logic in one main constructor and have others delegate to it. This avoids code duplication and ensures consistency.
Direct Field Assignment vs. Calling Setters¶
In constructors, prefer direct field assignment over calling setters:
// ✅ Recommended
public Account(String number) {
this.number = number;
}
// ⚠️ Can cause issues with inheritance (covered later)
public Account(String number) {
setNumber(number);
}
References, Objects, and Instances¶
Understanding the Terminology¶
| Term | Definition |
|---|---|
| Class | Blueprint/template for creating objects |
| Object | An instance of a class in memory |
| Instance | Synonym for object |
| Reference | A variable that "points to" an object in memory |
The House Analogy¶
graph TD
subgraph "Blueprint (Class)"
A[House Blueprint]
end
subgraph "Physical Houses (Objects)"
B[House at 123 Main St]
C[House at 456 Oak Ave]
end
subgraph "Addresses (References)"
D["blueHouse (123 Main)"]
E["anotherHouse (123 Main)"]
F["greenHouse (456 Oak)"]
end
A -->|"new House()"| B
A -->|"new House()"| C
D --> B
E --> B
F --> C
Multiple References to Same Object¶
House blueHouse = new House("blue");
House anotherHouse = blueHouse; // Same object, new reference
anotherHouse.setColor("yellow");
System.out.println(blueHouse.getColor()); // "yellow"
System.out.println(anotherHouse.getColor()); // "yellow"
// Both print "yellow" - they reference the SAME object!
Key Insight
When you assign one reference to another, you're copying the address, not the object. Both variables now point to the same object in memory.
Dereferencing and Garbage Collection¶
// Object created with no reference - immediately eligible for GC
new House("red"); // Cannot access this object!
// Object with reference
House myHouse = new House("beige");
// Creating new object and reassigning reference
House anotherRed = new House("red");
myHouse = anotherRed; // Beige house is now eligible for GC
Static vs. Instance Members¶
Static Variables¶
A static variable is shared by ALL instances of a class:
public class Dog {
private static String name; // Shared by all dogs!
public Dog(String name) {
Dog.name = name;
}
public void printName() {
System.out.println(name);
}
}
// Usage
Dog rex = new Dog("Rex");
Dog fluffy = new Dog("Fluffy");
rex.printName(); // "Fluffy" - not "Rex"!
fluffy.printName(); // "Fluffy"
// Both print "Fluffy" because static variable is shared
graph TD
subgraph "Class Level - Static"
A["static name = Fluffy"]
end
subgraph "Instance Level"
B[rex instance] --> A
C[fluffy instance] --> A
end
When to Use Static Variables¶
| Use Case | Example |
|---|---|
| Counters | private static int instanceCount; |
| Unique ID generators | private static long nextId; |
| Constants | public static final double PI = 3.14159; |
| Shared resources | Database connections, loggers |
Access Convention
Always access static members using the class name, not an instance:
Instance Variables¶
Instance variables are unique to each object:
public class Dog {
private String name; // Instance variable - unique per dog
public Dog(String name) {
this.name = name;
}
}
Dog rex = new Dog("Rex");
Dog fluffy = new Dog("Fluffy");
rex.printName(); // "Rex"
fluffy.printName(); // "Fluffy"
Static vs. Instance Methods¶
| Aspect | Static Method | Instance Method |
|---|---|---|
| Declaration | public static void method() |
public void method() |
| Access instance fields? | ❌ No | ✅ Yes |
| Access static fields? | ✅ Yes | ✅ Yes |
| Requires object to call? | ❌ No | ✅ Yes |
Can use this? |
❌ No | ✅ Yes |
public class Calculator {
// Static method - no instance needed
public static int add(int a, int b) {
return a + b;
}
}
// Call without creating an object
int sum = Calculator.add(5, 3);
Decision Flowchart¶
flowchart TD
A[Should method be static?] --> B{Uses instance<br/>variables or methods?}
B -->|Yes| C[Make it an<br/>INSTANCE method]
B -->|No| D[Consider making it<br/>STATIC]
POJOs and Java Records¶
Plain Old Java Objects (POJOs)¶
A POJO is a class primarily used to store data with minimal behavior.
public class Student {
private String id;
private String name;
private String dateOfBirth;
private String classList;
// Constructor
public Student(String id, String name, String dateOfBirth, String classList) {
this.id = id;
this.name = name;
this.dateOfBirth = dateOfBirth;
this.classList = classList;
}
// toString for printing
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", dateOfBirth='" + dateOfBirth + '\'' +
", classList='" + classList + '\'' +
'}';
}
// Getters and setters for all fields...
}
Related Terms¶
| Term | Description |
|---|---|
| POJO | Plain Old Java Object - simple data class |
| JavaBean | POJO with specific rules (no-arg constructor, serializable) |
| Entity | POJO that mirrors a database table |
| DTO | Data Transfer Object - for moving data between layers |
The @Override Annotation¶
The @Override annotation tells the compiler that a method is meant to override a method from a parent class:
toString() Method
Every class inherits a toString() method from Object. When you pass an object to System.out.println(), Java automatically calls its toString() method.
Java Records (Java 16+)¶
Records are a modern, concise alternative to POJOs for immutable data:
// Traditional POJO: ~60 lines of boilerplate
public class Student { ... }
// Record: 1 line!
public record LPAStudent(String id, String name, String dateOfBirth, String classList) { }
What Records Generate Automatically¶
For each component in the record header, Java creates:
| Generated | Description |
|---|---|
private final field |
Immutable storage |
| Public accessor method | Same name as field (e.g., name() not getName()) |
toString() |
Formatted output of all fields |
equals() and hashCode() |
Based on all fields |
| Constructor | With all parameters |
Using Records¶
// Creating a record instance
LPAStudent student = new LPAStudent("S001", "Bill", "1985-11-05", "Java Masterclass");
// Accessor methods (no "get" prefix!)
System.out.println(student.name()); // "Bill"
System.out.println(student.classList()); // "Java Masterclass"
// Implicit toString()
System.out.println(student); // LPAStudent[id=S001, name=Bill, ...]
// No setters - records are IMMUTABLE
student.setName("Bob"); // ❌ Compile error!
POJO vs Record¶
| Feature | POJO | Record |
|---|---|---|
| Mutability | Mutable (has setters) | Immutable (no setters) |
| Boilerplate | Lots of code | Minimal |
| Field access | getXxx() / isXxx() |
xxx() (field name) |
| When to use | Need to modify data | Read-only data transfer |
When to Use Records
Use records when: - Data is read-only after creation - You're passing data between layers (DTOs) - You want to reduce boilerplate
Use POJOs when: - You need to modify fields after creation - You need custom behavior in getters/setters - Framework requires JavaBean conventions
Key Insights & Best Practices¶
Encapsulation Best Practices¶
- Always make fields
private- control access through methods - Use getters for read access - even if they just return the value
- Add validation in setters - reject invalid data before it corrupts state
- Consider immutability - use final fields and records when possible
Constructor Best Practices¶
- Use constructor chaining - centralize initialization in one constructor
- Don't call overridable methods in constructors (more on this in inheritance)
- Assign directly to fields rather than calling setters in constructors
- Validate parameters - throw exceptions for invalid input
Static vs. Instance Decision¶
| Scenario | Use |
|---|---|
| Method needs object state | Instance |
| Method is a utility/helper | Static |
| Counter shared across instances | Static |
| Each object has unique data | Instance |
Quick Reference¶
Class Declaration Template¶
public class ClassName {
// 1. Fields (instance variables)
private Type fieldName;
// 2. Constructors
public ClassName() { }
public ClassName(Type param) {
this.fieldName = param;
}
// 3. Getters and Setters
public Type getFieldName() { return fieldName; }
public void setFieldName(Type fieldName) { this.fieldName = fieldName; }
// 4. Other methods
public void doSomething() { }
// 5. toString (optional)
@Override
public String toString() { return "..."; }
}
Record Declaration Template¶
public record RecordName(Type field1, Type field2) {
// Optional: Custom compact constructor for validation
public RecordName {
if (field1 == null) throw new IllegalArgumentException();
}
// Optional: Additional methods
public String formatted() { return field1 + ": " + field2; }
}
Questions Explored¶
- What is a class and how does it relate to objects?
- What is encapsulation and why is it important?
- How do constructors work and what is constructor chaining?
- What's the difference between a reference and an object?
- When should I use static vs. instance members?
- What are POJOs and when should I use Java Records?
Related Notes¶
| Part | Topic | Link |
|---|---|---|
| 1 | Classes, Objects & Encapsulation | You are here |
| 2 | Inheritance & Method Overriding | Part 2 → |
| 3 | Strings & StringBuilder | Part 3 → |
| 4 | Composition | Part 4 → |
| 5 | Encapsulation (Advanced) | Part 5 → |
| 6 | Polymorphism | Part 6 → |
Last Updated: 2026-01-26