Skip to content

The .equal? method should not be mapped to .isEqual for Java objects (e.g. Joda LocalDate) #5990

@ikaronen-relex

Description

@ikaronen-relex

Environment

  • jruby 9.2.8.0 (2.5.3) 2019-08-12 a1ac7ff OpenJDK 64-Bit Server VM 11.0.5+10 on 11.0.5+10 +jit [darwin-x86_64]

Expected Behavior

The following RSpec should pass:

describe Java::OrgJodaTime::LocalDate do
  it 'correctly implements reference equality' do
    a = Java::OrgJodaTime::LocalDate.new(2018, 12, 4)
    b = Java::OrgJodaTime::LocalDate.new(2018, 12, 4)
    expect(a == b).to be_truthy
    expect(a.__id__ == b.__id__).to be_falsy
    expect(a.equal? a).to be_truthy
    expect(a.equal? b).to be_falsy
  end
end

Actual Behavior

The last expect fails, since a.equal? ends up calling the Java isEqual method on the LocalDate object, which implements value equality.

Per https://ruby-doc.org/core-2.5.3/BasicObject.html#method-i-equal-3F, the equal? method should never be overridden and should always implement object identity comparison. However, some Java classes (notably including any JodaTime classes inheriting from AbstractPartial) may have an isEqual method which implements some other, weaker type of comparison and which JRuby automatically calls when equal? is called on such an object from Ruby.

IMO this is not a bug in JodaTime, since it's a pure Java library that is not designed with either Ruby's conventions or JRuby's idiosyncratic method resolution in mind. Instead, I would consider it a bug in JRuby itself: in attempting to bridge the different method naming conventions in Ruby and Java, it should also respect other established conventions of the two platforms and the "principle of least surprise", e.g. by not overriding equal? for classes whose author did not intend to do so.

This is particularly relevant since JRuby itself ships with JodaTime, which includes classes that trigger this bug.

FWIW, we've worked around this issue in our own codebase with a monkey patch like this:

class Java::OrgJodaTime::LocalDate
  def equal?(other)
    __id__ == other.__id__
  end
end

which restores the expected behavior. However, this is really only hiding the problem, and in any case only addresses it for one single class.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions