-1

In the following code, why do the two lines containing System.out.println(person); yield different outputs? The second line indirectly calls the method Job.toString yielding the string "Manager", but the first line mysteriously does not yielding Job@28f67ac7. The line in between person.put("a", "b"); doesn't seem to me like it should make any difference.

Code:

import java.util.*;
import org.apache.commons.lang3.builder.*;

class Job extends HashMap<String, String> {
    @Override public String toString() {
        return "Manager";
    }
}

class Person extends HashMap<String, String> {
    Job job;

    Person() {
        this.job = new Job();
    }

    @Override public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
}

class Test {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person);
        person.put("a", "b");
        System.out.println(person);
    }
}

Console:

Person@2b80d80f[job=Job@28f67ac7,threshold=0,loadFactor=0.75]
Person@2b80d80f[job=Manager,threshold=12,loadFactor=0.75]
Brian Schack
  • 300
  • 3
  • 14
  • 1
    Can I suggest that extending `HashMap` like this is almost certainly not as useful as you think. A `Person` isn't a `HashMap`; they might *have* a `HashMap` of attributes, though, so composition would be a more appropriate relationship here. – Andy Turner Feb 25 '18 at 22:33
  • @AndyTurner I agree, extending HashMap is not useful in this example. But considering that the example is contrived, would you have expected to get inconsistent outputs? – Brian Schack Feb 25 '18 at 22:38
  • @BrianSchack no, I wouldn't. I can see no logical explanation for this behavior, aside from poor implementation of the `ToStringBuilder` class. – Andy Turner Feb 25 '18 at 22:40
  • 1
    @cricket_007 The Override annotation is optional. I added it in, but it doesn't change the output. – Brian Schack Feb 25 '18 at 22:41
  • @AndyTurner Yes, I am afraid that I might have stumbled on a bug in Apache Commons. I have been reading through the source code, but I can't seem to find where it is though. – Brian Schack Feb 25 '18 at 22:44
  • Thoughts: are you sure you are using a good version of A C? Looking at the corresponding version of the A C source code? You should be able to track down where the badness happens using a debugger. – Stephen C Feb 25 '18 at 22:49
  • @AndyTurner I opened an issue (LANG-131) on the Apache Commons issue tracker: https://issues.apache.org/jira/browse/LANG-1381 – Brian Schack Feb 25 '18 at 23:01
  • @StephenC Yes, I am using the latest version (3.7) of commons-lang and the corresponding source code. I have not yet been able to track down where the badness happens –– hence this question. – Brian Schack Feb 25 '18 at 23:02

1 Answers1

5

2 things contribute to the output changing:

  • ToStringBuilder avoids calling toString() on equal instances to avoid infinite recursions.
  • Your Person and Job classes inherit HashMap's equals() method, causing new Person().equals(new Job()) == true

This means while person and person.job in your example remain equal to each other, ToStringBuilder will not call person.job.toString(), but when the map contents change, person.job.toString() will be called.

tkruse
  • 10,222
  • 7
  • 53
  • 80