Heart containing Coding Chica Java 101

Not For Me, Thanks! Overriding in Java

TIP: References Quick List

Table of Contents

  1. Table of Contents
  2. Introduction
  3. Diagram
  4. Unit Test for Object’s toString() Implementation
  5. TDD Cycle
    1. Unit Tests
    2. Runtime Updates
    3. Maven Build
  6. Commit

Introduction

When inheriting method logic from a parent, we may need to alter that logic in the child. When we do so, this is called overriding. The method is still there, we are just providing our own version of the logic within that method call. To demonstrate this behavior, let’s update the behavior in our Animal class to override the toString() method from the Object (implicit) parent class. This is allowed in most cases. The exceptions to this rule are a topic for another post.

Diagram

This diagram is much smaller than the last post, so not as worried about folks having to scroll past it.

A class diagram showing our current Animal class and the methods it inherits from java.lang.Object.
A class diagram showing that the toString() and other methods are inherited from the java.lang.Object.

Unit Test for Object’s toString() Implementation

Let’s start by creating a unit test that demonstrates what the Object class’s toString() logic generates. Let’s add the following to the src/test/java/codingchica.java101.model.AnimalTest.java:

/**
 * Unit tests for the toString method.
 * @see Animal#toString() 
 */
@Nested
class ToStringTest {
    @Test
    void toString_whenInvoked_returnsExpectedFormat(){
        // Setup
        String expectedStringPrefix = "codingchica.java101.model.Animal@";

        // Execution
        String result = animal.toString();

        // Validation
        assertNotNull(result, "result not null");
        assertTrue(result.startsWith(expectedStringPrefix),
                () -> String.format("Expected '%s' to start with '%s'",
                        result,
                        expectedStringPrefix));
    }
}

This test uses a startWith check because what follows this prefix is a representation of the object’s location in memory and will change with each execution.

Normally, I wouldn’t bother adding a test for this inherited behavior, unless I want to confirm that it stays that way. However, in this case, the extra step in the unit test helps to demonstrate that the behavior does already exist and is changing, rather than being added, with our runtime updates.

TDD Cycle

Now that we have confirmed that the inherited toString() implementation is used by our class, let’s modify that behavior.

Unit Tests

First, let’s update our unit tests to confirm the expected behavior after our updates.

/**
 * Unit tests for the toString method.
 * @see Animal#toString()
 */
@Nested
class ToStringTest {
    @ParameterizedTest
    @EnumSource(value = RelativeTime.class, mode = EnumSource.Mode.EXCLUDE, names = {})
    void toString_whenTimeOfBirthSet_returnsExpectedFormat(RelativeTime dateTimeValue){
        // Setup
        String expectedString = String.format("Animal{timeOfBirth=%s}", dateTimeValue.getInstant());
        animal.setTimeOfBirth(dateTimeValue.getInstant());

        // Execution
        String result = animal.toString();

        // Validation
        assertNotNull(result, "result not null");
        assertEquals(expectedString, result, "result");
    }

    @Test
    void toString_whenTimeOfBirthNotSet_returnsExpectedFormat(){
        // Setup
        String expectedString = "Animal{timeOfBirth=null}";

        // Execution
        String result = animal.toString();

        // Validation
        assertNotNull(result, "result not null");
        assertEquals(expectedString, result, "result");
    }
}

In the code snippet above, we have updated the existing test to show that we expect a shorter class name, and the timeOfBirth field to be outputted, but not the object reference information. This test has been updated to use our RelativeTime enum as a data source, so we can confirm that the value in the output is not simply hard-coded. We have also added a new test to confirm that the default value of null is shown if the field is not set.

Runtime Updates

Now, let’s add the logic to override the existing toString() method logic:

/**
 * A string representation of the Animal class.
 *
 * @return A string representing the data in this instance of the Animal
 * class.
 */
@Override
public String toString() {
    // TODO improve the performance of this code once we discuss the
    //  better approaches.
    return "Animal{"
            + "timeOfBirth="
            + timeOfBirth
            + '}';
}

In this example, we are using String concatenation (the + symbol) to join multiple strings together. This works in our local unit tests, but it comes at a performance cost and is considered a bad practice. We briefly looked at String.format in a prior post (when adding the App getGreeting() method). However, since we have not yet discussed the reason some alternatives are better approaches, I left the String concatenation in place for the time being and added a TODO comment – a little note to ourselves for the future that we need to circle back to this performance concern.

You may also notice the @Override annotation. This is for our benefit – if we ever change either the parent or the child class’s code so that we are no longer overriding a method from our parent, but rather implementing an entirely new method, then the code will not compile. You can try this out by temporarily adding an extra character to the method name, or adding an input parameter. Either change will cause the code to no longer compile, as we would not be overriding a method from our parent.

Maven Build

At this point, if we run the Maven build, we should see success.

Commit

It is good to commit in small, incremental changes. Now is a good time to do so.

Not For Me, Thanks! Overriding in Java

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.