Force a class to override the .equals method
Asked Answered
P

12

57

I have a bunch of class who implement a common interface : Command.

And this bunch of class goes to a Map.

To get the Map working correctly, I need to each class who implements Command to override the Object.equals(Object other) method.

it's fine.

But i whould like to force the overriding of equals. => Have a compilation error when something who implement command dont override equals.

It's that possible ?

Edit : BTW , i will also need to forcing the override of hashcode...

Prothesis answered 23/10, 2009 at 9:14 Comment(1)
interface Command { public abstract boolean equals(Object that); }Itch
D
90

No, you can't. What you can do, however, is use an abstract base class instead of an interface, and make equals() abstract:

abstract class Command {
   // put other methods from Command interface here

   public abstract boolean equals(Object other);
   public abstract int hashCode();
}

Subclasses of Command must then provide their own equals and hashCode methods.

It's generally bad practice to force API users to extend a base class but it may be justified in this case. Also, if you make Command an abstract base class instead of an interface, rather than introducing an artificial base class in addition to the Command interface, then there's no risk of your API users getting it wrong.

Dalenedalenna answered 23/10, 2009 at 9:20 Comment(3)
You might want to add in an abstract hashCode, just for completeness.Sanfred
work like a charm, and indeed, plus i already had a abstract base class.... so, it's not a big refactoring. ThanksProthesis
After over a decade of Java as a first language, I just learned something new.Skyway
A
17

Can you extend your objects from an abstract XObject rather than java.lang.Object ?

public abstract class XObject
 extends Object
{
@Override
public abstract boolean equals(Object o);
}
Acrophobia answered 23/10, 2009 at 9:18 Comment(3)
There's no guarantee that the implementations of Command will extend this class, though.Dalenedalenna
This was what I was going to post originally :-)Ligament
Ah ... but you could declare your other APIs to require an instance of (a subclass of) your abstract class, rather than just a Command instance. A bit ugly, but if you REALLY want to FORCE people to override equals, that will do it.Rogovy
R
4

Abstract classes won't work if you have a grandchild since its father already overrided both equals and hashCode methods and then you have your problem all over again.

Try using annotatins and APT (http://docs.oracle.com/javase/1.5.0/docs/guide/apt/GettingStarted.html) to get it done.

Reinhardt answered 12/4, 2013 at 16:44 Comment(0)
O
1

This would only be possible if Command was an interface, or an abstract class, where equals(..) is a method declared as abstract.

The problem is that Object, which is the superclass of all objects, already defines this method.

If you wish to indicate that this is a problem (at run-time), you could throw an exception, to force users of your API to override it. But it is not possible at compile time, at least to my knowledge.

Try to work around it, by having an API-specific method, e.g. CommandEquals. The other option is (as mentioned) extend another class which defines an Equals abstract method.

Oxytocic answered 23/10, 2009 at 9:21 Comment(2)
It only works for abstract classes (as described by Pierre), not for interfaces. You don't have to implement the equals method declared in the interface, because it's already present in your object.Dustan
Read my second sentence again.Oxytocic
R
1

You can create boolean myEquals() in interface Command, and create Adapter like this:

class MyAdapter{
  Command c;
  boolean equals(Object x) {
    return c.myEquals((Command)x);
  }
}

Then you just use map.put(key, new MyAdapter(command)) instead of map.put(key, command)

Rome answered 23/10, 2009 at 9:58 Comment(1)
This is the best solution. The other one (use an abstract equals) doesn't ensure the abstraction remain in future. If at certain point a developer remove the two abstraction, the build result is ok, but the equals doesn't work anymore. Using this adapter ensures at least that a compiler error arise if someone changes the code.Abalone
P
1
interface A{
    public boolean equal2(Object obj);
}

abstract class B implements A {

    @Override
    public boolean equals(Object obj) {
        return equal2(obj);
    }

}


class C extends B {

    public boolean equal2(Object obj) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}
Petasus answered 23/10, 2009 at 10:10 Comment(1)
so C must ovverride a method used for equals.Petasus
F
1

Well if you want a runtime check you can do something like:

    interface Foo{

}
class A implements Foo {

}
class B implements Foo {
    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }
}

public static void main(String[] args) {
    Class<A> clazzA = A.class;
    Class<B> clazzB = B.class;

    Class<Object> objectClass = Object.class;
    try {
        Method methodFromObject = objectClass.getMethod("equals",Object.class);
        Method methodFromA = clazzA.getMethod("equals",Object.class);
        Method methodFromB = clazzB.getMethod("equals",Object.class);
        System.out.println("Object == A" + methodFromObject.equals(methodFromA));
        System.out.println("Object == B" + methodFromObject.equals(methodFromB));
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }
}

Will print true for the first one and false for the second. If you want it compile time looks like the only option is to create an annotation and use annotation processing tool to check that all annotated classes overrides equals.

Finagle answered 23/10, 2009 at 10:40 Comment(0)
E
0

Since equals() is inherited from Object I doubt you can't really force that, since for every type there is an automatic inherited implementation of equals() available.

Elect answered 23/10, 2009 at 9:18 Comment(0)
R
0

I don't think it is possible to force overriding of equals as it comes from the Object class.

On a related note, note that you need to override 'hashCode' method from the Object class when ever you override equals. This becomes espeically important if you are going to use instances of your classes as keys of a Map. Check this article : http://www.artima.com/lejava/articles/equality.html which provides some hints about how to override equals in a correct manner

Risky answered 23/10, 2009 at 9:24 Comment(0)
L
0

As the other answers have already explained, no you cannot force the kind of thing that you are trying to.

One thing that might work 'enough' is to define a second interface, call this one MappableCommand.

public interface MappableCommand 
{

}

In your documentation for this interface indicate that a class should only implement this (empty) interface if the class designer has considered the requirements you stated.

Then you can set the Value type of your map to MappableCommand, and only MappableCommands will be able to be added to the map.

This is similar to the logic behind why one needs to implement the (blank) interface Serializable for classes that can be serialized by Java's default serialization mechanisms.

If this doesn't work, then you may have to settle for throwing a run time error;

Fake Edit:

If you wanted to make this requirement more obvious you could define the new interface this way

public interface MappableCommand 
{

    public void iOverrodeTheEqualsMethod();

    public void seriouslyIPromiseThatIOverrodeIt();

}
Ligament answered 23/10, 2009 at 9:46 Comment(0)
R
0

Here's a variation of some of the other proposed solutions:

public abstract class CommandOverridingEquals implements Command {
    public abstract boolean equals(Object other);
    public abstract int hashcode();
}

Map<String, CommandOverridingEquals> map = 
    new HashMap<String, CommandOverridingEquals>();

Or if you really want to be sure, use a checked hashmap; e.g.

Map<String, CommandOverridingEquals> map = Collections.checkedMap(
    new HashMap<String, Command>(),
    String.class, CommandOverridingEquals.class);

But no matter what you do you cannot stop someone doing this:

public class AntiFascistCommand extends CommandOverridingEquals {
    public boolean equals(Object other) { return super.equals(other); }
    public int hashcode() { return super.hashcode(); }
    ...
}

I tend to think that this kind of thing will cause trouble down the track. For example, suppose that I have a bunch of existing command classes that extend some other base class, and (incidentally) override equals and hashcode in the prescribed way. Problem is, I cannot use those classes. Instead, I'm forced to reimplement them or write a bunch of wrappers.

IMO, it is a bad idea to try to force the developer into a particular implementation pattern. It would be better to put some strong warnings into the Javadocs and rely on the developers to do the right thing.

Rogovy answered 23/10, 2009 at 10:8 Comment(1)
Sure, but if the developper miss the .equals override. It will be a mess in several map. And hard to figure out. I take the risk.Prothesis
V
0

Is it possible for you to provide your own java.util.comparator to the map in question?

Vallonia answered 27/10, 2009 at 19:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.