How to not throw a generically specified exception?
Asked Answered
L

6

19

I created a "producer" interface (to be used with method references, respectively to be easily mocked for unit tests):

@FunctionalInterface
public interface Factory<R, T, X extends Throwable> {
    public R newInstanceFor(T t) throws X;
}

which I created like that, as my first use case actually had to throw some checked WhateverException.

But my second use case doesn't have an X to throw.

The best I could come up with to make the compiler happy is:

Factory<SomeResultClass, SomeParameterClass, RuntimeException> factory;

That compiles, and does what I need, but still ugly. Is there a way to keep that single interface, but not provide an X when declaring specific instances?

Lustreware answered 31/7, 2018 at 11:24 Comment(10)
Why is X part of the interface?Lookin
I don't think this is possible. You would need something like a c++ template specialization.Meant
@TimCastelijns looks like sneaky throws idiom...Azo
What's wrong with Factory<SomeResultClass, SomeParameterClass, ?>?Dekow
@daniu, then <?> is a Throwable and is treated as such by compiler, forcing you to make a try-catch block.Delrio
@M.Prokhorov Correct. Too bad.Lustreware
@GhostCat, (not an answer, just comment on style) in your original code, consider doing what JDK does, and specify type parameters for input first, and then for output. Look at java.util.function.Function<T,R>, which is a P1 => P2 function, not P2 => P1 like yours.Delrio
as a side note, can you change your interface to @FunctionalInterface public interface Factory<R, T> { R newInstanceFor(T t) throws Throwable; }?Azo
@Azo That works for the "declaration" part, and passing a method reference. But actual invocations then give me "throws Throwable".Lustreware
ah! indeed, sorry I can't let this one go... still thinking on itAzo
A
11

The only way to do it is subclassing - but I bet you knew that. To make my argument stronger, look at BinaryOperator that extends BiFunction.

Azo answered 31/7, 2018 at 11:35 Comment(0)
A
12

You cannot do that in Java. The only way is to create a sub interface.

public interface DefaultExceptionFactory<R, T>
        extends Factory<R, T, RuntimeException>
Antidepressant answered 31/7, 2018 at 11:28 Comment(2)
@Eugene, I don't know if he needs to never spell out the type is the thing. He can make a wrapper method and place guarantees on it that the lambda in its argument never throws. Then he can spell out the X as an AssertionError (for cases when input lambda doesn't follow the contract). But that still needs a spell-out of the type in the use site.Delrio
you could also provide a wrapper implementation that upgrades any Factory<R, T, ? extends RuntimeException> to a DefaultExceptionFactory<R, T>Internuncio
A
11

The only way to do it is subclassing - but I bet you knew that. To make my argument stronger, look at BinaryOperator that extends BiFunction.

Azo answered 31/7, 2018 at 11:35 Comment(0)
D
5

This is more of a "social engineering" answer: we place a contract on the lambda form that it doesn't throw anything:

public interface Factory<T, R, X> {

    public R newInstanceFor(T arg) throws X;

    public static Factory<R, U, AssertionError> neverThrows(Factory<U, V, ?> input) {
        return u -> {
            try {
                return input.newInstanceFor(u);
            }
            catch(Throwable t) {
                throw new AssertionError("Broken contract: exception thrown", t);
            }
        };
    }
}

Usage is like this, or something along the lines of:

class MyClass {
    Factory<MyInput, MyOtherClass, AssertionError> factory;

    MyClass(Factory<MyInput, MyOtherClass, ?> factory) {
        this.factory = Factory.neverThrows(factory);
    }

    public void do() {
      factory.newInstanceFor(new MyInput()).do();
    }
}

Downside of this approach: you can't really specify the contract in the type signature, the contract is then an implementation detail. If you want to have this in type signature, you will need a second sub-interface.

Delrio answered 31/7, 2018 at 12:10 Comment(0)
B
3

You can define the method as generic like below code, if it is possible for you:

@FunctionalInterface
public interface Factory<R, T> {
    public <X extends Throwable> R newInstanceFor(T t) throws X;
}
Bacillus answered 31/7, 2018 at 11:43 Comment(9)
This looks interesting, but I haven't managed to get that compiled. Have you tried making instances that throw, say, IOException?Delrio
It compiles for me, but unfortunately, it breaks my first usecase. When using references like SomeClassThatReallyThrows::new ... now asks to handle that exception "really thrown". Too bad, this looked really nice.Lustreware
More specifically, I'm receiving compile errors for use site of this lambda form saying "Illegal lambda expression: Method newInstanceFor of type Fiddle.Factory<String,String> is generic".Delrio
@GhostCat, the lambda SAM type itself does compile for me as well. The use sites don't though.Delrio
@M.Prokhorov to making instance you must specify exception like instance.<ExceptionType>newInstanceFor(parameter); or let java to recognize itself see the github.com/jsunsoftware/concurrent/blob/master/src/main/java/…Bacillus
@BenoArakelyan, I don't understand how this example fits, to be honest. You have concrete classes, while this question is more about functional interfaces. I haven't managed to compile a sample using your code when I implement Factory with lambda expression. If you have an example, please add it to your answer.Delrio
@Lustreware I don't understand why the instantation of SomeClassThatReallyThrows should ask you to handle exception?Bacillus
@M.Prokhorov You can't use it explicitly as lambda. Java doesn't support that. I have added the link for show how you can construct generic flexible API with lambda. You can pay attention toward the Lock and Executable classes.Bacillus
Let us continue this discussion in chat.Bacillus
A
0

You can use Project Lombok's @SneakyThrows annotation:

@FunctionalInterface
public interface Factory<R, T> {

    @SneakyThrows
    R newInstanceFor(T t);
}

This allows you to throw any exception (checked or unchecked). But read the documentation because this feature must be handled with care.

Astray answered 9/8, 2018 at 19:50 Comment(1)
this sneaky throws feature is not lombok specific, but rather jls specific. And have you tried your code againt OPs requirments?Azo
M
-3

Do you have to make the exception generic? Why not define the interface as

@FunctionalInterface
public interface Factory<R, T> {
    public R newInstanceFor(T t) throws Throwable;
}

You can always catch your exception and check the type if you need in your calling function.

Matelda answered 31/7, 2018 at 11:42 Comment(4)
Exception as type parameter limits implementing code in the types of exception it can throw.Delrio
True that. But in this case, the exception type is 'X extends Throwable.'Matelda
Yes, but actual code which uses the Factory can say accept(Factory<R, T, ? extends IOException> factory).Delrio
Ahh, missed that. Agreed. Thanks.Matelda

© 2022 - 2024 — McMap. All rights reserved.