Objects and References

Understanding Object References

When you create an object in Java, the variable doesn't contain the object itself—it contains a reference to the object's location in memory. Think of it like a GPS coordinate that points to where your robot is parked in the garage. The coordinate isn't the robot, but it tells you exactly where to find it.

Creating Object References:

public class FRCRobot {
    private String teamName;
    private int teamNumber;
    private double batteryLevel;
    
    public FRCRobot(String name, int number) {
        this.teamName = name;
        this.teamNumber = number;
        this.batteryLevel = 100.0;
    }
    
    public void useBattery(double amount) {
        this.batteryLevel -= amount;
        if (this.batteryLevel < 0) {
            this.batteryLevel = 0;
        }
    }
    
    public double getBatteryLevel() {
        return this.batteryLevel;
    }
    
    public String toString() {
        return teamName + " (Team " + teamNumber + ") - Battery: " + 
               String.format("%.1f", batteryLevel) + "%";
    }
}

// When you create an object, you get a reference to it
FRCRobot robot = new FRCRobot("Lightning Bot", 12345);
System.out.println(robot);  // Lightning Bot (Team 12345) - Battery: 100.0%

Reference Assignment Copies the Reference

When you assign one object variable to another, you're not creating a new object—you're copying the reference. Both variables now point to the same object in memory. This is like giving someone else the GPS coordinates to your robot; now both of you can find and control the same robot.

Reference Assignment Example:

FRCRobot robot1 = new FRCRobot("Thunder Bot", 54321);
System.out.println("Original: " + robot1);

// This copies the REFERENCE, not the object
FRCRobot robot2 = robot1;

// Both variables point to the same object!
robot2.useBattery(25.0);  // Use battery through robot2

System.out.println("robot1: " + robot1);  // Shows 75% battery
System.out.println("robot2: " + robot2);  // Shows 75% battery

// They're the same object, so changes affect both variables
System.out.println("Same object? " + (robot1 == robot2));  // true

The null Reference

A reference variable can be set to null, which means it doesn't point to any object. It's like having a GPS coordinate that points to nothing—there's no robot there. Trying to use a null reference will cause a NullPointerException, one of the most common errors in Java programming.

Working with null References:

FRCRobot robot = new FRCRobot("Storm Bot", 98765);
System.out.println(robot);  // Works fine

// Set the reference to null
robot = null;
System.out.println(robot);  // Prints: null

// This will cause a NullPointerException!
// robot.useBattery(10.0);  // [ERROR] Don't do this!

// Always check for null before using an object
if (robot != null) {
    robot.useBattery(10.0);
    System.out.println("Battery used successfully");
} else {
    System.out.println("Robot reference is null - cannot use battery");
}

// Safe way to create a new robot
robot = new FRCRobot("Phoenix Bot", 11111);
System.out.println("New robot created: " + robot);

NullPointerException Prevention:

Essential Safety Checks:

  • Always Check: Use if (object != null) before calling methods
  • Initialize Variables: Give object variables initial values when possible
  • Defensive Programming: Validate parameters in methods that receive objects
  • Read Error Messages: NullPointerException tells you which line caused the error
  • Use Debugging: Print object values to see which ones are null

Objects as Method Parameters

Objects can be passed as parameters to methods, just like primitive values. Since objects are passed by reference, the method receives a copy of the reference, not a copy of the object. This means the method can modify the original object.

Objects as Parameters:

public class RobotMaintenance {
    
    // Method that takes a robot as a parameter
    public static void performMaintenance(FRCRobot robot) {
        if (robot == null) {
            System.out.println("[ERROR] Cannot perform maintenance on null robot");
            return;
        }
        
        System.out.println("[MAINTENANCE] Performing maintenance on: " + robot);
        
        // Simulate maintenance - restore battery to full
        robot = new FRCRobot("Maintained Bot", 0);  // This creates a NEW object
        // The original robot is unchanged because we changed the local reference
        
        System.out.println("[OK] Maintenance complete");
    }
    
    // Better maintenance method that actually modifies the robot
    public static void rechargeBattery(FRCRobot robot) {
        if (robot == null) {
            System.out.println("[ERROR] Cannot recharge null robot");
            return;
        }
        
        System.out.println("🔋 Recharging: " + robot);
        
        // This modifies the original object by calling its methods
        // We can't directly set batteryLevel because it's private
        // But we can call methods that modify the object's state
        
        System.out.println("[OK] Battery recharged (simulated)");
    }
    
    // Method that checks robot battery and recommends action
    public static void checkBatteryStatus(FRCRobot robot) {
        if (robot == null) {
            System.out.println("[ERROR] Cannot check battery on null robot");
            return;
        }
        
        double battery = robot.getBatteryLevel();
        System.out.printf("🔋 Battery level: %.1f%%\n", battery);
        
        if (battery < 20) {
            System.out.println("[WARNING]  LOW BATTERY - Charge immediately!");
        } else if (battery < 50) {
            System.out.println("⚡ Consider charging soon");
        } else {
            System.out.println("[OK] Battery level is good");
        }
    }
}

// Usage examples:
FRCRobot competitionBot = new FRCRobot("Competition Bot", 12345);
competitionBot.useBattery(60.0);  // Use 60% battery

RobotMaintenance.checkBatteryStatus(competitionBot);
RobotMaintenance.rechargeBattery(competitionBot);
RobotMaintenance.performMaintenance(competitionBot);

System.out.println("Final robot state: " + competitionBot);

Objects as Return Values

Methods can also return objects. This is useful for creating factory methods, cloning objects, or building new objects based on existing ones. The method returns a reference to the object, which can then be assigned to a variable.

Methods Returning Objects:

public class RobotFactory {
    private String manufacturer;
    private int robotsBuilt;
    
    public RobotFactory(String manufacturer) {
        this.manufacturer = manufacturer;
        this.robotsBuilt = 0;
    }
    
    // Method that returns a new robot object
    public FRCRobot buildRobot(String teamName, int teamNumber) {
        this.robotsBuilt++;
        System.out.println("[FACTORY] Building robot #" + robotsBuilt + " for " + teamName);
        
        // Create and return a new robot
        FRCRobot newRobot = new FRCRobot(teamName, teamNumber);
        return newRobot;
    }
    
    // Method that creates a copy of an existing robot
    public FRCRobot cloneRobot(FRCRobot original) {
        if (original == null) {
            System.out.println("[ERROR] Cannot clone null robot");
            return null;
        }
        
        System.out.println("[ROBOT] Cloning robot: " + original);
        
        // Create a new robot with similar properties
        // Note: We can't access private fields directly, so we use available methods
        FRCRobot clone = new FRCRobot("Clone Bot", 99999);
        
        // In a real implementation, you'd copy all the properties
        return clone;
    }
    
    // Method that returns a robot with specific configuration
    public FRCRobot buildCompetitionRobot(String teamName, int teamNumber) {
        FRCRobot robot = this.buildRobot(teamName, teamNumber);
        
        // Perform competition-specific setup
        System.out.println("⚙️  Configuring for competition...");
        
        return robot;
    }
    
    public int getRobotsBuilt() {
        return this.robotsBuilt;
    }
}

// Usage examples:
RobotFactory factory = new RobotFactory("FRC Robotics Inc.");

// Create new robots using factory methods
FRCRobot robot1 = factory.buildRobot("Lightning Bolts", 12345);
FRCRobot robot2 = factory.buildCompetitionRobot("Thunder Cats", 54321);
FRCRobot robot3 = factory.cloneRobot(robot1);

System.out.println("\n📊 Factory Status:");
System.out.println("Robots built: " + factory.getRobotsBuilt());
System.out.println("Robot 1: " + robot1);
System.out.println("Robot 2: " + robot2);
System.out.println("Robot 3: " + robot3);

Object Equality and the equals() Method

By default, Java compares objects using reference equality (==), which checks if two variables point to the same object in memory. However, you often want to compare objects based on their content. This is where the equals() method comes in. You can override this method to define what makes two objects 'equal' based on their properties.

Implementing equals() Method:

public class FRCRobot {
    private String teamName;
    private int teamNumber;
    private double batteryLevel;
    
    public FRCRobot(String name, int number) {
        this.teamName = name;
        this.teamNumber = number;
        this.batteryLevel = 100.0;
    }
    
    // Override the equals method to compare robot content
    @Override
    public boolean equals(Object compared) {
        // Check if they're the same object in memory
        if (this == compared) {
            return true;
        }
        
        // Check if the compared object is null
        if (compared == null) {
            return false;
        }
        
        // Check if the compared object is the right type
        if (!(compared instanceof FRCRobot)) {
            return false;
        }
        
        // Cast to FRCRobot so we can access its properties
        FRCRobot other = (FRCRobot) compared;
        
        // Compare the important properties
        // Two robots are equal if they have the same team name and number
        return this.teamName.equals(other.teamName) && 
               this.teamNumber == other.teamNumber;
    }
    
    // Other methods...
    public void useBattery(double amount) {
        this.batteryLevel -= amount;
        if (this.batteryLevel < 0) {
            this.batteryLevel = 0;
        }
    }
    
    public String toString() {
        return teamName + " (Team " + teamNumber + ") - Battery: " + 
               String.format("%.1f", batteryLevel) + "%";
    }
}

// Testing object equality:
FRCRobot robot1 = new FRCRobot("Lightning Bolts", 12345);
FRCRobot robot2 = new FRCRobot("Lightning Bolts", 12345);  // Same team
FRCRobot robot3 = new FRCRobot("Thunder Cats", 54321);    // Different team

System.out.println("Reference equality (==):");
System.out.println("robot1 == robot2: " + (robot1 == robot2));  // false (different objects)
System.out.println("robot1 == robot1: " + (robot1 == robot1));  // true (same object)

System.out.println("\nContent equality (equals()):");
System.out.println("robot1.equals(robot2): " + robot1.equals(robot2));  // true (same content)
System.out.println("robot1.equals(robot3): " + robot1.equals(robot3));  // false (different content)
System.out.println("robot1.equals(null): " + robot1.equals(null));      // false (null check)

// Even if battery levels are different, they're still the same team
robot2.useBattery(50.0);
System.out.println("\nAfter using battery:");
System.out.println("robot1: " + robot1);
System.out.println("robot2: " + robot2);
System.out.println("Still equal? " + robot1.equals(robot2));  // true (we ignore battery in equals)

Object Equality and Collections

The equals() method is crucial when working with collections like ArrayList. Methods like contains(), indexOf(), and remove() use the equals() method to find objects in the collection. Without a proper equals() implementation, these methods won't work as expected.

Objects in Collections:

import java.util.ArrayList;

public class TeamRoster {
    public static void main(String[] args) {
        ArrayList<FRCRobot> robots = new ArrayList<>();
        
        // Create some robots
        FRCRobot robot1 = new FRCRobot("Lightning Bolts", 12345);
        FRCRobot robot2 = new FRCRobot("Thunder Cats", 54321);
        FRCRobot robot3 = new FRCRobot("Storm Surge", 98765);
        
        // Add robots to the roster
        robots.add(robot1);
        robots.add(robot2);
        robots.add(robot3);
        
        System.out.println("[LIST] Team Roster:");
        for (int i = 0; i < robots.size(); i++) {
            System.out.println((i + 1) + ". " + robots.get(i));
        }
        
        // Test contains() method - this uses equals()
        FRCRobot searchRobot = new FRCRobot("Lightning Bolts", 12345);
        
        if (robots.contains(searchRobot)) {
            System.out.println("\n[OK] Found Lightning Bolts in roster!");
        } else {
            System.out.println("\n[ERROR] Lightning Bolts not found in roster");
        }
        
        // Test indexOf() method
        int index = robots.indexOf(searchRobot);
        if (index >= 0) {
            System.out.println("Lightning Bolts is at position: " + (index + 1));
        }
        
        // Test with a robot not in the list
        FRCRobot unknownRobot = new FRCRobot("Mystery Bot", 00000);
        if (robots.contains(unknownRobot)) {
            System.out.println("Found Mystery Bot");
        } else {
            System.out.println("[ERROR] Mystery Bot is not in the roster");
        }
        
        // Remove a robot using equals()
        boolean removed = robots.remove(searchRobot);
        if (removed) {
            System.out.println("\n🗑️  Removed Lightning Bolts from roster");
            System.out.println("Updated roster size: " + robots.size());
        }
        
        // Show that the original robot1 is still in memory
        System.out.println("\nOriginal robot1 still exists: " + robot1);
        System.out.println("Are they the same object? " + (robot1 == searchRobot));  // false
        System.out.println("Are they equal? " + robot1.equals(searchRobot));        // true
    }
}

Try It Yourself:

Practice with Objects and References

  • Create a Sensor class with equals() method that compares sensor type and ID
  • Build a Robot class that can be safely cloned with all its sensor references
  • Design a Competition class that manages teams and handles null robot references safely
  • Create a method that finds robots with low battery levels in a team roster
Test your understanding of object references, null handling, and object equality:

Open full interactive app