Bridge methods in JNBridgePro 7.3
We’ve fixed an issue in JNBridgePro 7.3 that some of you might have encountered in the past. While the workaround was straightforward, now the issue doesn’t come up at all. I’m talking about proxying covariant return values. To solve the problem, our generated Java proxies now include bridge methods.
What’s a bridge method, and how does it figure in JNBridgePro? The problem comes up in Java-to-.NET projects. Let’s say we have the following C# code:
public interface TestInterface { TestInterface2 f(); } public interface TestInterface2 { void g(); } public class ReturnClass : TestInterface2 { public void g() { } } public class MyTestClass : TestInterface { public ReturnClass f() { return null; } }
Even though MyTestClass.f()
returns ReturnClass
and TestInterface
(implemented by MyTestClass
) requires that f()
return TestInterface2
, this is fine in C# because ReturnClass
implements TestInterface2
. This is what’s known as a covariant return value. When it’s compiled into IL, it’s pretty much compiled directly into binary that echoes the types in the code above: the .NET runtime has no problem with this.
What about Java? If you were to take the code above and make the minor changes to make it Java, and then tried to compile it, what happens depends on the version of Java you’re using (or on the source compliance level you’ve told the compiler to use). If you compiled for anything before Java 5 (ancient history, I know), there would be an error because MyTestClass.f()
returns ReturnClass
when the interface expects TestInterface2
: covariant return values are not allowed. On the other hand, if you compiled for Java 5 or later, all would be fine: covariant return values are allowed.
The interesting thing is that if you compiled the Java directly into Java bytecodes, in the same way that C# is compiled, even when the compiler accepts the above source code as correct, the bytecodes would be illegal and running them would fail with an AbstractMethodError
because the JVM doesn’t consider MyTestClass
to implement TestInterface
: it’s missing a method f()
that returns TestInterface2
.
What the Java compiler does to satisfy the JVM is to add an extra compiler-generated method to MyTestClass
. While it’s purely binary, if it were decompiled back to Java, it would look something like this:
public TestInterface2 f() { return this.f(); }
where this.f()
refers to the original f()
, the one that returned ReturnClass
. This new compiler-generated method is called a bridge method, and its presence convinces the JVM that MyTestClass
indeed implements TestInterface
.
How does this relate to JNBridgePro? Well, if you took the original C# code and proxied it to Java in JNBridgePro 7.2 or earlier, the proxied MyTestClass
wouldn’t have the bridge method, and using the proxies from Java would lead to a problem:
TestInterface t = new MyTestClass(); // throws an exception!
Starting with JNBridgePro 7.3, the proxy contains the appropriate bridge method, and there’s no exception.
This seems pretty contrived, doesn’t it? Here’s a real-life situation connected with .NET generic collections that seems to frequently bite users.
In the System.Collections.Generic
namespace, the interface IEnumerable<>
contains the method
IEnumerator<> GetEnumerator();
Most collections that implement IEnumerable<>
define GetEnumerator()
to return IEnumerator<>
. However, a few collections, including List<>
, do other things. List<>
defines GetEnumerator()
as
List<>.Enumerator GetEnumerator();
where List<>.Enumerator
is an internal type that implements IEnumerator<>
. According to a Microsoft blog post, this is done for reasons of efficiency. Since covariance is allowed in C#, and in the .NET runtime, this presents no problem, but when the code is proxied to Java, the proxy for List<>
needs a bridge method, and when it’s not there, there’s a problem:
// Java, using the above proxies IEnumerable__1 e = SomeClass.returnData(); // returnData() defined to return IEnumerable__1, but really returns List<> IEnumerator__1 en = e.GetEnumerator(); // throws an AbstractMethodError!
Before JNBridgePro 7.3, it was necessary to rewrite the first line above as:
List__1 e = (List__1) SomeClass.returnData();
and everything would work. This was not a major problem, particularly if you knew that returnData()
was really returning a List__1
, but if you couldn’t easily find the real return class, or it was a dynamically generated class that couldn’t be proxied ahead of time, this would represent a real problem. This didn’t seem to happen in practice, but even when a static type cast allowed a workaround, this is really something that the user shouldn’t have to worry about. Starting with JNBridgePro 7.3, the bridge methods are generated in the proxies, the simple version of the code works without the extra type cast, and all is well.
This issue can also occur with parameters of generic classes, and with subclasses where interfaces aren’t involved. We think we’ve covered all the bases on this, but if you encounter something we’ve missed, please let us know.