给定以下类:
class A {
B b
int data
int data2
}
class B {
C c
String data
}
class C {
Date data
}
代码工作正常:
Date now = new Date()
def a = [ data:42, data2:84, b:[ data:'BBB', c:[ data:now ] ] ] as A
assert a.b.c.data == now
assert a.data == 42
assert a.data2 == 84
现在,如果我省略data2:84
,代码仍然可以正常工作,当然除了最后assert
。
但!如果我"拼错"属性名称,例如:
def a = [ data:42, data22:84, b:[ data:'BBB', c:[ data:now ] ] ] as A
我得到了
java.lang.NullPointerException:无法在空对象上获取属性"c">
我看到的是,只有A
类被实例化为 no-arg 构造函数,b
和c
属性都是null
s。
所以,跳过!=拼写错误。
因此,有两个问题:
(而是系统生理(。 这是预期的行为吗?不应该跳过拼写错误的道具吗?
如何让
as
关键词"宽大"跳过未知道具?
蒂亚
更新:
创建 JIRA 任务 https://issues.apache.org/jira/browse/GROOVY-9348
有一个主要区别。使用包含现有类字段的映射构造函数时,将初始化常规A
对象。这是println a.dump()
在这种情况下产生的结果。
<A@7bab3f1a b=B@1e1a0406 data=42 data2=84>
但是,如果将不由类字段表示的条目放入映射中,则 Groovy 不会初始化A
对象,而是创建A
类的代理。
<A1_groovyProxy@537f60bf $closures$delegate$map=[data:42, data22:84, b:[data:BBB, c:[data:Fri Dec 20 13:39:50 CET 2019]]] b=null data=0 data2=0>
此代理根本不初始化字段,但它将使用构造函数传递的映射存储为内部$closures$delegate$map
字段。
看看我用你的例子做的以下分析。
DefaultGroovyMethods.asType(Map map, Class<?> clazz)
抛出内部GroovyCastException
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '{data=42, data22=84, b={data=BBB, c={data=Fri Dec 20 12:22:28 CET 2019}}}' with class 'java.util.LinkedHashMap' to class 'A' due to: org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack: No such property: data22 for class: A Possible solutions: data2, data
来源:https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java#L11813
捕获异常并调用回退方法:
return (T) ProxyGenerator.INSTANCE.instantiateAggregateFromBaseClass(map, clazz);
来源:https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java#L11816
ProxyGenerator
使用以下方法初始化ProxyGeneratorAdapter
:public GroovyObject instantiateAggregate(Map closureMap, List<Class> interfaces, Class clazz, Object[] constructorArgs) { if (clazz != null && Modifier.isFinal(clazz.getModifiers())) { throw new GroovyCastException("Cannot coerce a map to class " + clazz.getName() + " because it is a final class"); } Map<Object,Object> map = closureMap != null ? closureMap : EMPTY_CLOSURE_MAP; ProxyGeneratorAdapter adapter = createAdapter(map, interfaces, null, clazz); return adapter.proxy(map, constructorArgs); }
来源:https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/groovy/groovy/util/ProxyGenerator.java#L162
此方法可能存在错误:在
createAdapter(map, interfaces, null, clazz)
部分中,null
值表示delegatingClass
对象。当它为 null 时,生成的代理类中没有应用委派机制。最后但并非最不重要的一点是,
adapter.proxy(map, constructorArgs)
实例化一个新对象,其字符串转储表示形式如下所示:<A1_groovyProxy@537f60bf $closures$delegate$map=[data:42, data22:84, b:[data:BBB, c:[data:Fri Dec 20 13:29:06 CET 2019]]] b=null data=0 data2=0>
如您所见,传递给构造函数的映射存储为
$closure$delegate$map
字段。所有A
类值都使用其默认值进行初始化(b
字段为null
,其余 int 字段为0
(。现在,
ProxyGenerator
类有一个创建支持委派的适配器的方法:public GroovyObject instantiateDelegateWithBaseClass(Map closureMap, List<Class> interfaces, Object delegate, Class baseClass, String name) { Map<Object,Object> map = closureMap != null ? closureMap : EMPTY_CLOSURE_MAP; ProxyGeneratorAdapter adapter = createAdapter(map, interfaces, delegate.getClass(), baseClass); return adapter.delegatingProxy(delegate, map, (Object[])null); }
来源: https://github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/groovy/groovy/util/ProxyGenerator.java#L203
我猜也许如果使用具有非空
delegatingClass
的ProxyGeneratorAdapter
,调用a.b
将使用内部委托映射中的值,而不是b
字段值。这只是我的假设。
问题是:这是一个错误吗?这要看情况。正如 cfrick 在其中一条评论中提到的,使用不正确的映射初始化A
会引发明确的错误,您就完成了。在这里,此异常被抑制,从调用者的角度来看,您不知道后台发生了什么。我使用 Groovy 2.5.8 和 3.0.0-RC1 运行这些测试,这两个版本中的行为相同。在Apache Groovy JIRA项目中报告这是一个问题听起来很合理,所以你可以从Groovy核心维护者那里得到反馈。