Reputation: 103
Why can't I Coerce my map into a constructor for IStringHolder?
When I run Example.main
interface IStringHolder {
String getString()
}
class StringHolder implements IStringHolder {
String string
StringHolder(string) {
this.string = string
}
@Override
String getString() {
return string
}
}
class Example {
public static void main(String[] args) {
doSomething([string:"hey"] as IStringHolder)
}
static void doSomething(IStringHolder stringHolder) {
println stringHolder.getString()
}
}
I get the following exception
Exception in thread "main" java.lang.UnsupportedOperationException
at org.codehaus.groovy.runtime.ConvertedMap.invokeCustom(ConvertedMap.java:52)
at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:124)
at com.sun.proxy.$Proxy0.getString(Unknown Source)
at IStringHolder$getString.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:117)
at Example.doSomething(Example.groovy:26)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.invoke(StaticMetaMethodSite.java:46)
at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.callStatic(StaticMetaMethodSite.java:102)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:56)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:194)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:206)
at Example.main(Example.groovy:21)
Why is the exception coming from the first line in Example.doSomething?
When I coerce the map to StringHolder then it works. What is going on?
Upvotes: 0
Views: 4064
Reputation: 111
The problem with this code is this line:
doSomething([string:"hey"] as IStringHolder)
The language feature of Groovy you're attempting to leverage here is called Explicit Constructor Coercion, which looks for a constructor within the IStringHolder
class that it can call with the arguments you provided.
In this case, Groovy will look within the IStringHolder
class for a no-arg constructor it can call to instantiate it, then it will look for a setter setString(String str)
and call it with the "hey"
you provided.
The reason you're seeing UnsupportedOperationException
is because there is no constructor in IStringHolder
to call, because it's not an implementation.
Switching the type coercion to as StringHolder
does work, but probably not as you're expecting. Had you provided simply "hey"
, instead of string:"hey"
, it would have done what you were expecting and what I detailed above. This is because the map is interpreted as a Named Argument Constructor, which causes Groovy to instantiate your object using the only constructor it can find, passing null
into it, and then calling the synthetic setString(String str)
method with "hey"
once it has been constructed. While this yields the result you're looking for, it does it in a non-intuitive way.
Explicit Constructor Coercion
StringHolder stringHolderImpl = [ "hey" ] as StringHolder
Here Groovy will look for a constructor that takes one-arg of type String
and pass our value "hey"
into it.
Implicit Constructor Coercion
StringHolder stringHolder = [ "hey" ]
This is the same as above, except Groovy is using the left-side type to determine what the right-side probably is, before it proceeds with finding the constructor, etc.
I don't recommend this only because it makes Groovy assume too much, and IDEs like Intellij will complain about this syntax.
Explicit Closure Coercion
If you're adamant about not using the implementation class StringHolder
you can create an implementation at runtime via:
IStringHolder stringHolderImpl = { getString: "hey" } as IStringHolder
This creates a closure which satisfies the IStringHolder
contract, so it can be easily cast to an implementation of it.
Implicit Closure Coercion
IStringHolder stringHolderImpl = { getString: "hey" }
Again I don't really recommend this style, but it works.
I'm a fan of explicit constructor coercion due to the explicit nature and because it doesn't require the overhead of creating a closure. I'll use explicit closure coercion from time to time when I need an implementation of a class that only needs to exist at runtime.
What's the best option? You'll have to determine that for yourself!
Upvotes: 2