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?

Mikhail Kholodkov
  • 23,642
  • 17
  • 61
  • 78
GhostCat
  • 137,827
  • 25
  • 176
  • 248

6 Answers6

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>
Leo Aso
  • 11,898
  • 3
  • 25
  • 46
  • 1
    @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. – M. Prokhorov Jul 31 '18 at 11:51
  • 1
    you could also provide a wrapper implementation that upgrades any `Factory` to a `DefaultExceptionFactory` – WorldSEnder Jul 31 '18 at 13:03
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.

Eugene
  • 117,005
  • 15
  • 201
  • 306
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.

M. Prokhorov
  • 3,894
  • 25
  • 39
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;
}
Beno
  • 945
  • 11
  • 22
  • 3
    This looks interesting, but I haven't managed to get that compiled. Have you tried making instances that throw, say, `IOException`? – M. Prokhorov Jul 31 '18 at 11:53
  • 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. – GhostCat Jul 31 '18 at 11:54
  • More specifically, I'm receiving compile errors for use site of this lambda form saying "`Illegal lambda expression: Method newInstanceFor of type Fiddle.Factory is generic`". – M. Prokhorov Jul 31 '18 at 11:56
  • 1
    @GhostCat, the lambda SAM type itself does compile for me as well. The use sites don't though. – M. Prokhorov Jul 31 '18 at 11:57
  • @M.Prokhorov to making instance you must specify exception like `instance.newInstanceFor(parameter);` or let java to recognize itself see the https://github.com/jsunsoftware/concurrent/blob/master/src/main/java/com/jsunsoft/util/concurrent/locks/Lock.java – Beno Jul 31 '18 at 12:14
  • 1
    @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. – M. Prokhorov Jul 31 '18 at 12:23
  • @GhostCat I don't understand why the instantation of SomeClassThatReallyThrows should ask you to handle exception? – Beno Jul 31 '18 at 12:24
  • @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. – Beno Jul 31 '18 at 12:34
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/177097/discussion-between-beno-arakelyan-and-m-prokhorov). – Beno Jul 31 '18 at 12:39
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.

foundationer
  • 85
  • 2
  • 9
  • this sneaky throws feature is not lombok specific, but rather jls specific. And have you tried your code againt OPs requirments? – Eugene Aug 18 '18 at 21:11
-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.