8

I want to use an interface like this :

public interface ResultItem {
    public int getConfidence();
    public boolean equals(ResultItem item);
    public ResultItem cloneWithConfidence(int newConfidence);
}

I have it implemented by different kind of objects representing a voice recognition result.

The idea is, I wish to compare only results of the same kind. That is, if I create a class IntResult implementing ResultItem, I want that the method signatures become :

public boolean equals(IntResult item);
public IntResult cloneWithConfidence(int newConfidence);

I feel that there is a design flaw in my interface, because for now I am using pretty ugly casts on the results of cloneWithConfidence and of other methods returning a ResultItem.

Is there a better way?

Dunaril
  • 2,757
  • 5
  • 31
  • 53
  • What happens if you do it the way you're posting? – OscarRyz Mar 01 '11 at 16:36
  • @OscarRyz I cry over my treacherous cast and my repetitive check `if(this.getClass().isInstance(item)) return false;` in the `equals` method of the implementing classes. – Dunaril Mar 01 '11 at 16:40
  • You'd better keep crying. You need to override `Object.equals(Object other)` for `equals` to get used for collections, etc. Forget your specialized version, it may be nicer but doesn't get used anywhere (except where explicitly called by you). *By writing the specialized version, you risk forgetting to write the overriding one.* – maaartinus Mar 01 '11 at 16:47
  • There is nothing improper about comparing objects to objects of other arbitrary types. A Garfield (a cat) should not squawk when asked if is equal to Odie (a dog), no matter how indignant he may feel. He should simply reply that no, he is not equal. If Garfield would recognize anything to which he is equal, then an inability to recognize something doesn't wouldn't mean he didn't know whether he was equal. Rather, the inability to recognize something would in and of itself prove that he was not equal to it. – supercat Jan 06 '14 at 20:20

3 Answers3

11

There is a frequently-seen idiom that goes as follows:

public interface ResultItem<T extends ResultItem<T>> {
    public int getConfidence();
    public boolean equals(T item);
    public T cloneWithConfidence(int newConfidence);
}

public class IntResult implements ResultItem<IntResult> {
  //...
}
NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • Still no syntactic sugar gain (Compiler error) when we do this `ResultItem item = new IntResultItem(); IntResultItem iResultItem = item.cloneWithConfidence(100);` – ring bearer Mar 01 '11 at 16:41
  • I was very close :) I got to pretty much this, only I thought I would make the compiler crash with the recursion. – Dunaril Mar 01 '11 at 16:44
  • @aix this solution gives me actually much trouble. I get warnings all the time now when using ResultItem in a part of the code. Also, I am declaring a list `List` and I cannot pass it as an argument of type `List`. The compiler does not accept it. Any idea why ? – Dunaril Mar 01 '11 at 17:00
  • 1
    @Dunaril 1. Regarding the warnings, you have to use `ResultItem` or `ResultItem>` as appropriate. 2. The second problem isn't specific to this solution: try passing a `List` where a `List` is expected, and you'll see the same behaviour. – NPE Mar 01 '11 at 17:13
  • @aix Thanks. Is there then a workaround for the second problem ? – Dunaril Mar 01 '11 at 17:14
  • @Dunaril I am sure someone will suggest a better way, but a partial workaround could be along the lines of `public > void f(List l) {}`. That'll accept `List`. – NPE Mar 01 '11 at 17:21
  • @Dunaril - if you just want to read the list, try something like `List extends ResultItem>>`. – jtahlborn Mar 01 '11 at 17:48
  • @jtahlborn It seems promising but I need to remove and add elements to the list in one of the methods. – Dunaril Mar 01 '11 at 18:24
  • Unless I've misunderstood something the recursive definition idiom proposed doesn't really buy anything compared to a simpler form. See: http://stackoverflow.com/questions/18553621/how-can-i-make-an-interface-instance-method-accept-arguments-of-the-same-class-o – Marcus Junius Brutus Aug 31 '13 at 23:07
4

Not really an answer to your question, but an important remark (I think):

If you want your equals-method to be usable for objects in collections and similar, you need to implement public boolean equals(Object o), and it should work for comparisons to all kinds of objects (in most cases returning false, though). You may have additionally a method with a narrower parameter type, and in implementations delegate like this:

public class IntResult {
    public boolean equals(Object o) {
        return o instanceof IntResult &&
             this.equals((IntResult)o);
    }
    public boolean equals(IntResult that) {
        // TODO
    }

}

Make sure you comply to all the conditions in the contract of equals, namely symmetry, reflexivity, transitivity and having a compatible hashCode implementation.

Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
  • I think answers like these provide additional value and are important (+1) – Sean Patrick Floyd Mar 01 '11 at 16:43
  • IMHO, your "not really an answer" is more valuable than the real ones. However, I'd strongly recommend against the additional method with a narrower parameter type. – maaartinus Mar 01 '11 at 16:49
  • @maaartinus: Maybe the additional method should be private and named other than `equals`, like `equalsImpl` or such. It is often easier to implement when you don't have to check for the right type. – Paŭlo Ebermann Mar 01 '11 at 16:58
1

Well, you could make it generic:

public interface ResultItem<T extends ResultItem<T>> {
    public boolean equals(ResultItem<T> item);
}

Then you would need to make IntResult implement ResultItem<IntResult>.

Of course that doesn't stop another class from misbehaving, e.g. FloatResult implementing ResultItem<IntResult> but it makes various bits of API work when all the classes are well behaved.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • With all due respect I don't get why we need this complicated form. See: http://stackoverflow.com/questions/18553621/how-can-i-make-an-interface-instance-method-accept-arguments-of-the-same-class-o – Marcus Junius Brutus Aug 31 '13 at 23:07
  • @MarcusJuniusBrutus: The answer you've received there is basically "No, it's not possible." This answer shows what *is* possible... and while it's not perfect by any means, it still allows for more pleasant APIs - look at `Enum` for examples of this. – Jon Skeet Sep 01 '13 at 08:50