Reputation: 27822
In my Java project, I have the following class / interface hierarchy:
public interface ProductSearcher {
Set<Product> search(String request);
}
public interface OnlineProductSearcher extends ProductSearcher {
}
public interface DatabaseProductSearcher extends ProductSearcher {
}
The OnlineProductSearcher
searches for products at some remote machine (e.g. an implementation uses HTTP), while the DatabaseProductSearcher
searches for products within my local machine (e.g. an implementation uses JPA).
As it turns out, from time to time, the OnlineProductSearcher
may have problems searching for products because the remote machine is down, is rate-limiting my requests, responses with 5xx, 4xx, and whatnot.
So I had the idea to have my OnlineProductSearcher
implementations throw an RemoteMadeProblemsException
whenever there is a problem related to the remote machine.
And as I want to force any OnlineProductSearcher
user to handle these exception gracefully and not forget to do so, I made RemoteMadeProblemsException
a checked exception, i.e. RemoteMadeProblemsException extends Exception
.
So I went along and had the idea to redefine OnlineProductSearcher
like this:
public interface OnlineProductSearcher extends ProductSearcher {
Set<Product> search(String request) throws RemoteMadeProblemsException;
}
But in Java, it is not possible to redeclare/constrain methods from a supertype inside a subtype (Eclipse tells me "Exception RemoteMadeProblemsException is not compatible with throws clause in ProductSearcher.search(String)")
Now I see two solutions to this situations:
ProductSearcher.search(String)
to throw a RemoteMadeProblemsException
.RemoteMadeProblemsException
extend RuntimeException
and don't have OnlineProductSearcher.search(String)
declare a throws
clause.I find both solutions inadequate:
DatabaseProductSearcher.search
to catch/throw a RemoteMadeProblemsException
which doesn't make sense (it's a local database after all).OnlineProductSearcher.search(String)
and forgets to try-catch a RemoteMadeProblemsException
, letting the exception fall through and ripple up.What are better solutions to this "only some subtype may throw an exception" problem?
Upvotes: 3
Views: 252
Reputation: 41168
The problem you have is this:
ProductSearcher x = new OnlineProductSearcher();
This is entirely legal syntax and now if someone calls x.method()
there is no way for Java to know about that checked exception.
This is why subclasses can only make implementations more specific. They can return subclasses and accept super classes but not the other way around. This is because the requirement is that any call made to the super method is also valid against the subclass method.
For example if:
Number process(Integer i) {
}
is a super class then a valid subclass is:
Integer process(Number i) {
}
Because every call to process
in the super class is also valid in the sub class. The exact same argument applies to throws
declarations. By making the sub class throw a checked exception you make it impossible to treat it as a method with the same signature as in the super class.
The solution to your dilemma is to define a more generic exception ProductSearcherException
and have ProductSearcher
throw that exception.
Your OnlineSearcherException
then subclasses ProductSearcherException
and your throw declaration becomes legal.
One thing you can do to improve things involves having three classes instead of one:
This does weaken the ability for people to doProductSearcher x = new LocalProductSearcher
and then use the more generic class (as then they would need to catch the exception) but for anyone using LocalProductSearcher
throughout they would never need to do the catch.
Note though that even in the local case you may find yourself needing to throw exceptions in the future so having them is not terrible.
Upvotes: 4
Reputation: 3884
IMHO You should add the throws Exception
to your superinterface (and probably some more refined version, not just Exception).
The reasoning is that if you do not declare throws exception, then your contract is: Under normal execution no implementation of this method should throw an exception. In your program this is clearly not the case, as the remote may experience problems.
Your reason for not adding an exception to the database version is silly: The DB is local today, but may be remote tomorrow. And local DBs can have problems too.
If you REALLY don't want to catch exceptions from the DatabaseProductSearcher version, then you need to reference it as itself, not as the superinterface, and redefine the method in DatabaseProductSearcher to not throw anything. Then when you refer to it by that interface, you are not forced to catch anything, as the compiler now knows that this version is safe.
Upvotes: 0