Hi! I'm Remsey, your Java instructor. Ready to master Classes and Objects together? This is where real OOP starts. π
Prefer to learn by listening? Hit play below and watch the audio come to life!
Alright, let's talk about something that's at the absolute heart of Java β Classes and Objects.
Think of it this way. You want to build a fleet of custom cars. π
You don't build each car from scratch β you make a blueprint. That blueprint describes what every car should have: a colour, a brand, a number of doors, and the ability to drive and honk.
In Java, that blueprint is a Class. Each actual car you build from it is an Object.
One blueprint. Infinite cars. That's the power of OOP β Object-Oriented Programming.
A class is a template β it defines the properties (fields) and behaviours (methods) that every object of that type will have. But by itself, a class doesn't do anything. It just sits there, waiting to be used.
public class Car {
// Fields (properties)
String brand;
String colour;
int numberOfDoors;
// Method (behaviour)
public void drive() {
System.out.println("π The " + colour + " " + brand + " is driving!");
}
public void honk() {
System.out.println("π£ Beep beep!");
}
}
That's your blueprint. Now let's actually build some cars.
An object is a specific instance of a class. You create it using the new keyword. Each object gets its own copy of the fields, so they can have totally different values.
public class Main {
public static void main(String[] args) {
// Create two Car objects from the same blueprint
Car myCar = new Car();
myCar.brand = "Tesla";
myCar.colour = "red";
myCar.numberOfDoors = 4;
Car friendsCar = new Car();
friendsCar.brand = "BMW";
friendsCar.colour = "blue";
friendsCar.numberOfDoors = 2;
myCar.drive();
friendsCar.drive();
myCar.honk();
}
}
Same blueprint. Two totally different cars. That's objects in action. π―
Right now, we're setting each field manually after creating the object. Tedious, right? Java has a smarter way: the constructor.
A constructor is a special method that runs automatically when you create a new object. It lets you set everything up in one go β like a factory worker who assembles the whole car as it rolls off the production line.
public class Car {
String brand;
String colour;
int numberOfDoors;
// Constructor β same name as the class, no return type
public Car(String brand, String colour, int numberOfDoors) {
this.brand = brand;
this.colour = colour;
this.numberOfDoors = numberOfDoors;
}
public void drive() {
System.out.println("π The " + colour + " " + brand + " is driving!");
}
public void honk() {
System.out.println("π£ Beep beep!");
}
}
class Main {
public static void main(String[] args) {
Car myCar = new Car("Tesla", "red", 4);
Car friendsCar = new Car("BMW", "blue", 2);
myCar.drive();
friendsCar.drive();
System.out.println("Doors on my car: " + myCar.numberOfDoors);
}
}
Notice the this keyword? That's how a constructor refers to the object being created β distinguishing the field (this.brand) from the parameter (brand). Smart, right?
Right now our fields are fully public. Anyone can waltz in and set myCar.numberOfDoors = -999. That's chaos. Java has a concept called encapsulation: hide your data and provide controlled access via getters and setters.
public class BankAccount {
private String owner;
private double balance;
public BankAccount(String owner, double initialBalance) {
this.owner = owner;
this.balance = initialBalance;
}
// Getter β read-only access
public double getBalance() {
return balance;
}
// Controlled setter β validates before changing
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("β
Deposited β¬" + amount + ". New balance: β¬" + balance);
} else {
System.out.println("β Invalid deposit amount!");
}
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("πΈ Withdrew β¬" + amount + ". New balance: β¬" + balance);
} else {
System.out.println("β Insufficient funds or invalid amount!");
}
}
}
class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount("Remsey", 1000.0);
System.out.println("π° Balance: β¬" + account.getBalance());
account.deposit(250.0);
account.withdraw(80.0);
account.withdraw(5000.0); // This should fail
}
}
The private keyword means nobody can touch balance directly. They have to go through your methods β and your methods enforce the rules. That's encapsulation: data with a bodyguard. πͺ
toString() Method β Give Your Object a VoiceBy default, if you print an object in Java you get something hideous like Car@1a2b3c4d. Not exactly useful.
Override toString() to give your object a human-readable description. Java calls this automatically whenever you print the object.
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
// Override toString so printing the object looks nice
@Override
public String toString() {
return "π€ Person{" + "name='" + name + "', age=" + age + "}";
}
}
class Main {
public static void main(String[] args) {
Person p1 = new Person("Remsey", 30);
Person p2 = new Person("Layla", 25);
System.out.println(p1);
System.out.println(p2);
System.out.println("Hi, my name is " + p1.getName() + "!");
}
}
You've got the blueprint. Now build something yourself. Below is a partial Dog class β your job is to complete the constructor and add a fetch() method!
public class Dog {
private String name;
private String breed;
// TODO: Add a constructor that sets name and breed
public void bark() {
System.out.println("π " + name + " says: Woof woof!");
}
// TODO: Add a fetch() method that prints "[name] fetches the ball and brings it back!"
// TODO: Override toString() to print: "Dog{name='...', breed='...'}"
}
class Main {
public static void main(String[] args) {
Dog dog = new Dog("Max", "Labrador");
dog.bark();
dog.fetch(); // Uncomment once you've added the method
System.out.println(dog);
}
}
Test your understanding with these quick exercises! Try to solve them on your own first.
Task: Create a Student class with fields name, studentId, and grade (0β10). Add a constructor, a study() method, a getGrade() getter, and a toString(). Create two students and print them.
grade a private doublestudy() method can print a fun message using the student's nametoString() like: Student{name='...', id='...', grade=...}public class StudentDemo {
static class Student {
private String name;
private String studentId;
private double grade;
public Student(String name, String studentId, double grade) {
this.name = name;
this.studentId = studentId;
this.grade = grade;
}
public double getGrade() { return grade; }
public void study() {
System.out.println("π " + name + " is hitting the books hard!");
}
@Override
public String toString() {
return "Student{name='" + name + "', id='" + studentId + "', grade=" + grade + "}";
}
}
public static void main(String[] args) {
Student s1 = new Student("Remsey", "S001", 8.5);
Student s2 = new Student("Layla", "S002", 9.2);
s1.study();
s2.study();
System.out.println(s1);
System.out.println(s2);
}
}
Task: Create a Product class with name, price, and quantity. Add a constructor, a getTotalPrice() method that returns price * quantity, and a toString(). Create three products and print each one's total price.
private double price and private int quantitygetTotalPrice() is a simple multiplication β no setter neededpublic class ShopDemo {
static class Product {
private String name;
private double price;
private int quantity;
public Product(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
public double getTotalPrice() {
return price * quantity;
}
@Override
public String toString() {
return "ποΈ " + name + " x" + quantity + " @ β¬" + price + " = β¬" + getTotalPrice();
}
}
public static void main(String[] args) {
Product coffee = new Product("Coffee", 3.50, 4);
Product book = new Product("Java Book", 39.99, 1);
Product headphones = new Product("Headphones", 79.95, 2);
System.out.println(coffee);
System.out.println(book);
System.out.println(headphones);
}
}
Task: Create a Counter class with a private count field (starts at 0). Add methods increment(), decrement(), reset(), and getCount(). Make sure count never goes below zero. Create two counters and test all methods.
decrement(): only subtract if count > 0reset() simply sets count back to 0public class CounterDemo {
static class Counter {
private String name;
private int count = 0;
public Counter(String name) {
this.name = name;
}
public void increment() {
count++;
System.out.println("β " + name + " count: " + count);
}
public void decrement() {
if (count > 0) {
count--;
System.out.println("β " + name + " count: " + count);
} else {
System.out.println("β οΈ " + name + " is already at zero!");
}
}
public void reset() {
count = 0;
System.out.println("π " + name + " reset to 0");
}
public int getCount() { return count; }
}
public static void main(String[] args) {
Counter likes = new Counter("Likes");
Counter errors = new Counter("Errors");
likes.increment();
likes.increment();
likes.increment();
likes.decrement();
errors.increment();
errors.decrement();
errors.decrement(); // Should warn: already at zero
errors.reset();
System.out.println("Final likes: " + likes.getCount());
System.out.println("Final errors: " + errors.getCount());
}
}
π‘ Pro Tip: Try solving these exercises without looking at the solutions first! It's the best way to learn. If you get stuck, check the hints before peeking at the solution.
Continue your Java journey with Interfaces β the contract pattern!