0

I have a class implementing an interface which forces me to implement 2 methods - fun check(): Boolean and fun onCheckFalse(): Int.

The usage looks something like:

if (check().not()) return onCheckFalse()

In my implementation, I always return true from check() so onCheckFalse() becomes logically unreachable.

What should I do there?

  1. Return some value even if it has no logical meaning?
  2. Throw an exception? Which one?
  3. Something else?

The specific use-case is for kotlin, but a general answer is of course welcome as well.

orirab
  • 2,915
  • 1
  • 24
  • 48
  • To clarify, the code `if (check().not()) return onCheckFalse()` is in another class that uses the interface, not in the interface implementation that always returns `true` for `check`, right? – Sweeper Apr 26 '22 at 11:39
  • 1
    https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-not-implemented-error/ – Hans Passant Apr 26 '22 at 11:41
  • 1
    Think about what you would expect to happen if someone calls `onCheckFalse` without doing the `if (check().not())` check. Is that something that makes sense/is allowed? If not, then throw an exception. Otherwise, return a dummy value of your choice. – Sweeper Apr 26 '22 at 11:43
  • @Sweeper what exception would you suggest for that case? or should I create a custom one? – orirab Apr 26 '22 at 12:02
  • Based on your terminology above I suggest IllegalStateException. – Tenfour04 Apr 26 '22 at 13:01
  • would any of you like to submit this as an answer and I'll accept it? – orirab Apr 26 '22 at 13:15
  • Before I could write an actual answer, I need clarification. I'm kind of confused by your description of the usage. If this is an interface, and `check()` sometimes returns false in some implementations, why shouldn't you keep your code as it is above so it will work with any instance of the interface? Is the code above your own, or part of the interface's module, where it is consuming implementations of your interface? What does the rest of the function containing the code above look like? – Tenfour04 Apr 26 '22 at 13:32
  • both the interface and the usage are in a different module, which I can also change if need be - so if you want to give a full answer, I'd expect 2 alternatives - 1 assuming the other module is untouchable, and another modifying it. – orirab Apr 26 '22 at 15:37

1 Answers1

1

It's not uncommon to be forced to implement an interface method that makes no sense in a particular implementing class*.

The usual approach in Java — and hence in Kotlin/JVM — is to throw UnsupportedOperationException; see this question. That way, if someone does unwittingly call your method, they'll find out the hard way that your class doesn't provide that functionality!

In the particular case that the method is a notification or callback — called for the class's own benefit, and not to provide any functionality to the caller — then there's no point telling the caller that it's not implemented, so the best approach there is simply an empty method that does nothing.

(However, OP has clarified that the latter doesn't apply in this particular question, despite the on…() method name.)


* The original use case for UnsupportedOperationException is for immutable collections: in Java, the same interface (e.g. java.util.List) is used for both mutable and immutable collections, and so the mutating methods are specified to throw that exception when called on immutable collections.

Of course, that particular case doesn't apply in Kotlin, which splits those interfaces into two: a read-only super-interface (e.g. List), and a sub-interface (e.g. MutableList) which adds in the mutating methods.

But there are other situations in which an implementation may not be able to implement certain methods. For example, a non-transactional database class may not be able to roll back changes, or an image without an alpha channel may not be able to set transparency.

I think that exception gets over-used, though. If your class can implement the action in some cases but not others — as in many of the examples I found online — then it would probably be more logical and helpful to throw IllegalArgumentException or IllegalStateException instead.

And ideally, an interface would be designed so that no implementations would ever need to throw UnsupportedOperationException. The List example shows that although Kotlin's collection interfaces are a little more complex than Java's, they're more expressive; you can tell at compile time whether you have a read-only collection or a read/write one, which avoids a whole class of run-time errors. So if you're designing interfaces, it's worth thinking ahead and trying to avoid the need if you can.

gidds
  • 16,558
  • 2
  • 19
  • 26
  • The first paragraph looks great, the second isn't correct from my use-case - I can rename the method, but it isn't for a subscription. Also, I can't do nothing, I have to return a value (Int). If you remove the second paragraph or edit it I'll happily accept your answer. – orirab Apr 27 '22 at 07:30
  • @orirab Done — and more info added (that I didn't have time for yesterday. – gidds Apr 27 '22 at 13:51