Skip to main content

Section 3.4 Classes and Objects

So far, we’ve battled this messy situation:
// Player 1's stats
int player1Health = 100;
int player1Gold = 50;

// Player 2's stats (getting messy...)
int player2Health = 100;
int player2Gold = 50;

// Whoops, which health am I changing?
player1Health = healPlayer(player2Health, 5); // Easy mistake!
Methods helped a bit, but we still pass variables around separately. Wouldn’t it be great if all these stats lived in one place?

Subsection 3.4.1 A Single “Entity” That Owns Its Data

Let’s pretend we want to treat the player as a single entity, storing all of that player’s stats in one “box.” We also want that “box” to have special “buttons” (like “heal” or “add gold”) that automatically change that player’s stats.
That’s exactly what Java’s class feature will let us do. But let’s build it slowly so it’s not mysterious.

Subsection 3.4.2 Step 1: An Empty "Container" Called Player

Let’s start with the most basic version of this feature. Here’s the smallest possible container we can make:
public class Player {
    // We'll fill details in soon!
}
  • public class Player — This line starts defining our container.
  • The { } braces mark what belongs inside it.
This might look empty and useless, but we’ve just done something powerful: we’ve created a new type in Java. Just like int lets us create numbers and String lets us create text, Player will let us create... well, players! We’ll fill in what that means exactly, but the key idea is there: Java lets us create our own types to represent anything we need in our programs.
Right now our container is empty—like an empty box. It compiles, but doesn’t hold anything yet. Let’s fix that next.

Subsection 3.4.3 Step 2: Putting Variables Inside That Box

Remember how we used to write:
int playerHealth;
int playerGold;
in our main method? Instead of having these floating around separately, we can put them inside our container:
public class Player {
    // These belong to the Player now
    int health;
    int gold;
}
This is like saying "every Player has their own health and gold." When we create a Player (using new Player()), Java sets aside space for both variables. They’ll start at 0 by default, which isn’t great—we usually want to pick starting values like (10, 50). We’ll fix that next, but first appreciate what we’ve done: we’ve moved from separate, floating variables to a single container that keeps related data together.

Subsection 3.4.4 Step 3: Setting Initial Values

Remember how our variables defaulted to 0? That’s not great for a game—we want to choose starting values. If this was a method, we’d write something like:
public static Player createPlayer(int startHealth, int startGold) {
    // ... somehow make a Player with these values ...
}
Since creating objects with initial values is something we do constantly, Java gives us special syntax for it—a shorter, cleaner way to write this common task:
public class Player {
    int health;
    int gold;

    // Like createPlayer, but special syntax!
    // Constructor syntax:
    // 1. Same name as the class
    // 2. No return type (not even void)
    // 3. public ClassName(parameters) { ... }
    public Player(int startHealth, int startGold) {
        health = startHealth;  // set this Player's health
        gold   = startGold;    // set this Player's gold
    }
}
Now instead of calling createPlayer(10, 50), we write:
Player p = new Player(10, 50);
What happens when you write new Player(10, 50)?
  1. new tells Java to create space in memory
  2. Java fills that space with default values (0 for numbers)
  3. The constructor runs to set up the object properly
  4. You get back a reference to your new Player
This special method is called a constructor. You might wonder: "Doesn’t it return a Player?" It does, but Java’s grammar treats constructors specially—they implicitly return the new object without needing a return type. That’s why:
  • The name matches the class (Player)
  • There’s no return type (Java handles that for us)
You’ll see this pattern everywhere in Java:
String name = new String("Alice");      // Make new String with "Alice"
Scanner input = new Scanner(System.in); // Make new Scanner reading System.in
The constructor tells Java "when someone asks for a new Player, here’s how to set it up." Much better than default 0s!

Subsection 3.4.5 Step 4: Adding "Operations" for That Player

Remember how we wrote methods before? Each had a return type (like int or void) and parameters in parentheses. We’ll do the same here, but inside our class:
Why not just use static methods? Let’s compare approaches:
////////////////////////////////////////////////
// Static approach - explicitly pass everything
////////////////////////////////////////////////
public static void healPlayer(int currentHealth, int healAmount) {
    currentHealth += healAmount;
}

////////////////////////////////////////////////
// Instance method approach - data is inside
////////////////////////////////////////////////
public void heal(int amount) {
    this.health += amount;  // 'this' refers to current Player
}
The instance method might look simpler, but what’s really happening? When you write:
player1.heal(5);
Java secretly translates this to something like “call heal with the data belonging to player1.” That’s what this means in an instance method—it’s a reference to "the object we’re calling this method on." So:
public void heal(int amount) {
    this.health += amount;  // explicitly say "this object's health"
}
is the same as:
public void heal(int amount) {
    health += amount; // 'this.' is implicit
}
The instance method approach is:
  • More intuitive ("tell the player to heal")
  • Safer (data bundle stays together)
  • Less typing (no need to pass every field)
Much better than juggling separate variables and explicit parameters!
public class Player {
    int health;
    int gold;

    // ...constructor as before...
    public Player(int startHealth, int startGold) {
        health = startHealth;
        gold   = startGold;
    }

    // The method is right next to the data it changes
    public void heal(int amount) {
        health += amount;  // directly use this Player's health
    }

    public void addGold(int amount) {
        gold += amount;    // same for gold
    }
}
// Create an object using the Player class:
Player p = new Player(10, 50);
p.heal(5);  // can't mix up which health we're changing!

Subsubsection 3.4.5.1 The ’this’ Keyword: Who Am I?

When writing instance methods, you might wonder: "How does Java know which Player’s health to change?" The answer is the this keyword.
public class Player {
    int health;
    // ...other fields...

    public void heal(int health) {  // Uh oh, parameter named same as field!
        health = health;    // Which health is which? 😕
    }

    // Better: use 'this' to be clear
    public void heal(int health) {
        this.health = health;  // "THIS object's health = parameter health"
    }
}
When you call player1.heal(5), Java secretly passes player1 to the method as this. You can think of it like:
// What you write:
player1.heal(5);

// What Java effectively does:
heal(player1, 5); // player1 becomes 'this' inside the method
The this keyword is especially useful when:
  • Parameter names match field names
  • You need to pass the current object to another method
  • You want to be extra clear about which variable you’re using
Usually you can skip writing this. (it’s implicit), but it’s there if you need it!

Subsection 3.4.6 Using Our New Type

Let’s add a main method right to our Player class to try it out:
Look how natural this is:
  1. new Player(10, 50) creates a fresh Player with starting values
  2. p1.heal(5) tells that specific Player to heal itself
  3. Each Player keeps track of its own stats
Now that we’ve seen it in action, let’s give these ideas their proper names:
  • A Player we create (like p1 or p2) is called an object or instance.
  • The methods inside Player (like heal) are called instance methods because they work on a specific instance.
  • The setup code (like Player(10, 50)) is called a constructor.
Think of it like this: You wrote a recipe (Player class), and now you can bake as many cookies (Player objects) as you want. Each cookie has its own ingredients (health, gold), but they all follow the same recipe!

Subsection 3.4.7 One Blueprint, Many Objects

Think about how games like Minecraft work:
// Define the blueprint once
public class Player {
    int health;
    int gold;
    // ...constructor and methods as before...
}

// Create many different players from it
Player steve = new Player(20, 0);    // Steve starts with 20 health, 0 gold
Player alex  = new Player(20, 100);  // Alex starts with 20 health, 100 gold
When steve.heal(5) runs, it only changes Steve’s health. Alex’s stats stay the same. Each object is independent, just like:
  • One house blueprint → many different houses
  • One recipe → many different cookies
  • One character class in a game → many different players
When we say Player p1 = new Player(...), we say p1 is an “instance” of that class. That’s just fancy talk for “a house built from the blueprint.” No big mystery!
The class is just the instructions (“Players have health and gold”). The objects are the actual things we create using those instructions. That’s why we say “class is the blueprint, objects are the houses.”

Subsection 3.4.8 Putting It All Together

Let’s see how all the pieces work together. Here’s our complete Player class:
The magic is in how it all fits together:
  1. Create objects using the blueprint (new Player(...))
  2. Each object gets its own variables (health, gold)
  3. Methods work on the object you call them on (hero.heal(5) vs enemy.heal(10) )
No more “which health am I changing?” Everything belongs to a specific Player!
One Last Thing: Notice how anyone can write p1.health = -999;? That’s not great—players shouldn’t have negative health! If our game allows negative health, that could cause weird glitches like invincible players or healing that kills you. We’ll fix this by making variables private, letting only the Player class control how they change:
public class Player {
    private int health;  // Only Player methods can touch this now
    private int gold;    // This too!

    // Methods control how these values change
    public void heal(int amount) {
        if (amount > 0) {
            health += amount;  // Only allow positive healing
        }
    }
}

Subsection 3.4.9 Exercises: Classes and Objects

Checkpoint 3.4.1. Multiple Choice: Why Use Classes?

In this section, we introduced the idea of creating a Player class to hold related stats (health, gold, etc.). Which statement best captures why this is beneficial for handling multiple players in a game?
  • Because Java won’t compile if you declare more than three int variables outside a class.
  • That’s not true; Java doesn’t limit the number of variables outside a class.
  • Each Player object bundles its own stats, reducing confusion and preventing you from mixing up player1Health and player2Health.
  • Exactly! Grouping related data (and methods) in a class makes it far less error-prone than many separate variables.
  • Because static methods can only accept one parameter, so we must use a class to handle multiple stats.
  • Static methods can accept multiple parameters; that’s not the main reason classes are helpful.
  • Classes automatically remove the need to type public or static in your code.
  • Classes don’t remove public or static usage; you still control that in the class definition.

Checkpoint 3.4.2. True/False: Separate Data in Each Object.

    If you create two Player objects, p1 and p2, each has its own health and gold fields. Calling p1.heal(5) affects only p1, and does not affect p2.
  • True.

  • Each instance has its own copy of the fields, so operations on p1 do not change p2.
  • False.

  • Each instance has its own copy of the fields, so operations on p1 do not change p2.

Checkpoint 3.4.3. Parsons: A Minimal Player Class.

Rearrange the following lines to form a complete, minimal Player class that compiles and runs. It should have:
  • A health field
  • A constructor that sets health
  • A method heal(int amount) that adds to health
  • A main method creating a Player, calling heal, and printing health
Only one arrangement will compile and run correctly.

Checkpoint 3.4.4. Short Answer: What Is the this Keyword?

In the code example, this appears inside heal to refer to the current object’s fields (e.g., this.health). In 1-2 sentences, explain what this refers to when an instance method is called (e.g., p1.heal(5)).
Solution.
Sample Solution: When we call p1.heal(5), this inside the heal method refers to p1. In other words, this is the specific Player object on which the method was invoked.

Checkpoint 3.4.5. Reflection: Why No Return Type for a Constructor?

Java constructors look like methods but don’t have a return type. In 2-3 sentences, explain why constructors lack a void or Player return type. Hint: think about how new Player(...) already returns a fully constructed object without needing an explicit return statement.
Solution.
Sample Solution: A constructor’s job is to initialize a new object. The new keyword allocates memory and then calls the constructor to set fields. By definition, constructors implicitly return the new object reference—so Java syntax doesn’t require (or allow) a named return type.
You have attempted of activities on this page.