Skip to main content

MBeanInfo.equals: who's asking?

Posted by emcmanus on November 13, 2006 at 12:40 PM EST

Defining an equals(Object) method in a public class is not always straightforward. One reason it might not be is that the answer to the question "are these objects equal?" might be "who's asking?".

In a very interesting comment on my previous blog entry, rullrich suggests that the real problem is with MBeanInfo.equals. I'm inclined to disagree, but the issue is a subtle one.

One frequent difficulty with implementing an equals(Object) method is what to do in subclasses. Josh Bloch has an exhaustive discussion of this question in Effective Java, so I won't go into it in any more detail, except to say that there is a blemish in the JMX API because it doesn't follow Bloch's advice. The class OpenMBeanInfoSupport is a subclass of MBeanInfo and overrides its equals method. In addition to the requirements of MBeanInfo.equals, it requires that its argument be an instance of OpenMBeanInfo. This means that this method violates the symmetry property guaranteed by the contract for Object.equals. If I have an object mbeanInfo of class MBeanInfo and an object openMBeanInfo of class OpenMBeanInfoSupport, then mbeanInfo.equals(openMBeanInfo) might be true while openMBeanInfo.equals(mbeanInfo) is false. Ugh.

But the question at hand is whether two MBeanInfo objects should be equal if (for example) they contain the same attributes but in a different order. This is where I say we need to know: who's asking?

I can see at least two use cases for callers of MBeanInfo.equals:

  1. A client has been coded to interact with a particular sort of MBean, say the MemoryMXBean from java.lang.management. It wants to check that the interface of the MBean it is interacting with is indeed the expected interface.

  2. A graphical client (such as JConsole) has created a GUI for a certain MBean and wants to know if the MBean's interface has changed so it can change the GUI.

JConsole GUI for MemoryMXBean

The snapshot shows the JConsole GUI for the MemoryMXBean. If the order of the attributes changed, and assuming we think it is useful to preserve the order from the MBeanInfo, then we would want to change the order in the GUI too. You could imagine JConsole calling mbeanServer.getMBeanInfo(memoryMXBeanName) and comparing the MBeanInfo it gets back with the one it previously used to construct this view.

In this second use case, MBeanInfo.equals should return false if the order of the attributes has changed, or if any other detail has changed, such as the description of an attribute. But in the first use case, MBeanInfo.equals should ignore these details because they don't change the set of valid client operations. Obviously we can't make MBeanInfo.equals conform to both use cases so we must pick one.

We chose the second use case because it is precise. Two MBeanInfo objects are different if they differ in any detail, including the order of attributes. The first use case is much less precise:

  • Changing the type of an attribute from int to Integer has no effect on clients so should we consider two MBeanInfo objects equal if that is their only difference?
  • What about changes to the Descriptor of an MBeanInfo or a contained MBeanAttributeInfo?
  • Adding a new attribute to an MBean does not invalidate existing clients, but we can't consider two MBeanInfo objects equal if one of them has an attribute that the other doesn't. (It would violate symmetricity again.)

A secondary reason for preferring the definition where order is significant is that it makes for a faster equals method. If order were not significant then we would need either a quadratic algorithm or the creation of extra objects inside the method. This is very much a secondary reason; efficiency concerns should almost never be the primary motivation for API design choices. Correctness and simplicity trump efficiency!

A summary of the choice we made, then, is that it allows me to answer the question: Is this MBeanInfo object the same as one I got earlier for the same MBean? If the answer is yes, then the MBean interface definitely hasn't changed. If the answer is no, then the MBean interface might or might not have changed, but I must assume it has.

The choice does not in general allow you to use MBeanInfo.equals to answer the incompatible question: Is this MBeanInfo object the same as the one I expected?

So the moral is that when you define an equals method you must know what question you want it to answer.

But what about the order of methods in a Standard MBean interface?

rullrich also makes some other interesting points. First, in principle calling getMBeanInfo() twice on the same MBean might return a different object each time, with a different attribute order, even if the MBean has not changed in any way. This is not a problem with the JDK, but you could imagine an implementation that (a) does not cache the MBeanInfo for a Standard MBean interface (as the JDK does), and where (b) the order of the methods returned by Class.getMethods() can vary at different times for the same Class object (which it cannot in the JDK). Well, I think a JMX implementation running on a JVM with property (b) had better ensure that it does in fact cache the MBeanInfo. We could certainly modify the spec so it says this explicitly, but I think we would be addressing a very theoretical problem.

Second, I am also inclined to think we should preserve the order returned by Class.getMethods(). The implication for the regression test is that it should use reflection to determine what it thinks the order of attributes and operations in the MBeanInfo should be, and compare this with the order that is actually in the MBeanInfo returned by the JMX API. That test is then imposing a stronger restriction on the JDK's implementation of the API than is required by the spec, but that's all right. The bug in the test that I actually coded is that it hardwired this expected order instead of getting it from Class.getMethods().

Related Topics >>