3

First, I should say that I am able to get the generic type of instances or anonymous inner classes.

Here is the code example, look for the catch block:

public static interface MyInterface<E extends Throwable>{
    void call() throws E;
}


public static <E extends Throwable> void method(MyInterface<E> lambda) throws E {
    try {
        lambda.call();
    } catch(Throwable ex) {
        // Pseudo code                                   
        Class<?> T = ((ParameterizedType)ex.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0]; 
        if ( T.isInstance(ex) ) {
            throw ex;
        } else {
             ... 
        }                                                  
    }
}

public static void throwsException() throws Exception {
    throw new Exception();
}

This doesn't work:

method(() -> {
    throwsException();
});

But this does:

method(new MyInterface<Exception>() {
    @Override
    public void call() throws Exception {
        throwsException();
    }
});

But with the introduction of Lambdas, i can no longer enforce this!

It should also be noted that this now potentially breaks backwards compatibility with Libraries older than < 8, and that reflected out this information.

I have researched this topic, and there only seems to be possible workarounds for getting the method parameters, but this is regarding the throws part so that won't work.

Note: I have seen this thread already: Reflection type inference on Java 8 Lambdas

Community
  • 1
  • 1
mjs
  • 21,431
  • 31
  • 118
  • 200
  • 2
    I don't see this as backwards compatibility issue. `getGenericInterfaces` works by returning what appears in the source code. With lambdas, you don't have an actual type argument. – Sotirios Delimanolis Dec 18 '14 at 16:00
  • @SotiriosDelimanolis "What appears in the source code" is kind of vague. It is not true because the type can and is inferred from the method. The compiler will not accept a lambda that throws something else than specified by a the method being called. The reciever would have expected this to be provided. – mjs Dec 18 '14 at 16:25
  • 3
    Remember that generic types are a compile time concept, they don't exist at runtime. What you're getting through `getGenericInterfaces` is what appears in the source code (ie. compile time), not the type that was potentially inferred at some method invocation (they they could sometimes be the same). – Sotirios Delimanolis Dec 18 '14 at 17:30
  • *"But with the introduction of Lambdas, i can no longer enforce this! "* You were never able to enforce anything here. You probably shouldn't have been doing this in the first place. – Radiodef Dec 18 '14 at 19:45
  • @SotiriosDelimanolis If a method excepted an anonymous inner class or instance of that type before, that method expected to be able to extract this information. Now it can't. The actual type is available at compile time, it is inferred, but not explicitly declared. – mjs Dec 18 '14 at 19:54
  • 1
    Except not really. `getGenericInterfaces` only works with direct implementations. Your solution will fail if you add a level of indirection, ie. an extra interface or subtype in between the instance type and the `MyInterface` interface. It will also fail if you provide a `Proxy` instance that implements `MyInterface`. Your parameter type `MyInterface` can only restrict the type of the expression. It cannot restrict how that type was generated, whether compile time or runtime. – Sotirios Delimanolis Dec 18 '14 at 20:19
  • subtype is not problem when using getGenericSuperclass. For interfaces and abstract classes it does work. If I get a null as a return then I can handle that. Anyhow, not any point of arguing this. You say I shouldn't even be doing this because there is no safe way to cover all, I say that I am willing to pay. With the introduction of lambdas, no one will use AIC anymore, so the problem just got alot bigger. Now, I am no longer willing to pay. – mjs Dec 18 '14 at 21:42
  • 1
    Sorry for code in comments, but I want to get my point across if it hadn't already. Say you have, for whatever reason, `interface Some extends MyInterface` and `class SomeImpl implements Some`. You can use `method(new SomeImpl());`, but the `getGenericSuperclass` "hack" will not work. – Sotirios Delimanolis Dec 18 '14 at 22:01
  • 1
    Euh, I mean `getGenericInterfaces` rather than `getGenericSuperclass` for this case. You might want to look into type tokens and why/how they work too. – Sotirios Delimanolis Dec 18 '14 at 22:26

1 Answers1

4

You can simply write

public static interface MyInterface<E extends Throwable>{
    void call() throws E;
}

public static <E extends Throwable> void method(MyInterface<E> lambda) throws E {
    try {
        lambda.call();
    }
    catch(Throwable ex) {
        throw ex;
    }
}

without dealing with Reflection. Since MyInterface<E>.call() is guaranteed to throw types assignable to E or unchecked exceptions only, it is clear that the catched ex must be either of a type assignable to E or an unchecked exception, hence may be safely re-thrown.

This works since Java 7.


In contrast, if your method performs other operations which may throw exceptions not guaranteed to be compatible with E, you should declare them explicitly rather than doing Reflection magic. Otherwise you have a method changing its behavior in a strange way as it depends then on the parameter whether a particular exception type is simply thrown or handled differently (wrapped or whatever your fall-back behavior is).

Note that your Reflection magic is broken anyway. Consider this simple example:

class Foo<E extends Throwable> implements MyInterface<E> {
    public void call() throws E {
    }
}

when instantiating this class directly, you can’t get the actual type arguments at runtime via Reflection. This is exactly the same behavior as with implementations created via lambda expressions.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Well, your second argument is my case. I am writing an API and would like to handle and then wrap all other exceptions not explicitly declared and expected to be rethrown. All other exceptions I would like to wrap in a second exception so the caller can easily catch on one or the other. It is not completely useless scenario. I do not want to wrap the declared one. Both scenarios in my case leads to an action, but both should be able to be caught and handled differently. – mjs Dec 18 '14 at 16:35
  • 3
    @momo: so what you want to do is play exception lottery. The user of your API has no way to know which exceptions it actually may throw or wrap (and into which type). After all, no one can guaranty that you will be able to instantiate the declared exception type as it may have `private` constructors or be an `abstract` type. And as said, it doesn’t work with all scenarios anyway, even without lambdas. – Holger Dec 18 '14 at 16:39
  • Well, your edit wouldn't work no, but it is not an abstract class and I believe there are other ways to get that information as well. It will work for an abstract class. For a non-abstract class there are ways as well: http://stackoverflow.com/questions/3403909/get-generic-type-of-class-at-runtime ... For interfaces it will always work pre-lambdas – mjs Dec 18 '14 at 17:15
  • 2
    @momo: none of the answers of that linked question have a solution for this case. It is simply impossible to get that type in this case. If you think you can live with the fact that your code is broken and works only for a specific case, well, then why do you bother with lambdas? It just means that there is another case for which your broken code doesn’t work… – Holger Dec 18 '14 at 17:25
  • It was one case before, now it's two. I kind of new this wasn't possible but I wanted it to be brought forth. The problem that existed before was at least for exceptions pretty rare. Lambdas are used in a much larger extent today, and therefore I won't go the broken route. But it is Java that's broken on this one. – mjs Dec 18 '14 at 19:50
  • 3
    No, it did not go from "one case to two". It went from "it works in this one case, and breaks in these 27" to "it works in this one case, but breaks in these 28". – Brian Goetz Dec 19 '14 at 04:52
  • I figured out a way to know if the exception is of type E or not. I cast it to E and if it throws an exception, then it must be a runtime exception different from E. – mjs Dec 28 '14 at 21:32
  • 1
    You should learn what it means when the compiler warns you about an “unchecked cast”… – Holger Dec 29 '14 at 10:03
  • Just wanted to say, that actually casting the exception to E is not really going to thrown an exception! You would throw E but it might actually be a checked exception. That means in Java a checked exception can be turned into a unchecked one this way. – mjs Feb 21 '15 at 14:00