给定以下课程:
class A {
B b
int data
int data2
}
class B {
C c
String data
}
class C {
Date data
}
Run Code Online (Sandbox Code Playgroud)
该代码工作正常:
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
Run Code Online (Sandbox Code Playgroud)
现在,如果我省略data2:84,代码仍然可以正常工作,当然除了最后一个assert。
但!如果我“拼错”属性名称,例如:
def a = [ data:42, data22:84, b:[ data:'BBB', c:[ data:now ] ] ] as A
Run Code Online (Sandbox Code Playgroud)
我越来越
java.lang.NullPointerException:无法获取空对象上的属性“c”
我看到的是,只有A类是用无参数构造函数实例化的,并且b属性c都是nulls。
所以,跳过!=拼写错误。
因此有两个问题:
(相当哲学)。这是预期的行为吗?拼写错误的 props 不应该被跳过吗?
如何as使关键字变得“宽松”以跳过未知的道具?
TIA
更新:
有一个主要区别。当您使用包含现有类字段的映射构造函数时,A将初始化常规对象。这是println a.dump()在这种情况下产生的结果。
<A@7bab3f1a b=B@1e1a0406 data=42 data2=84>
Run Code Online (Sandbox Code Playgroud)
但是,如果您将不由类字段表示的条目放入映射中,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>
Run Code Online (Sandbox Code Playgroud)
该代理根本不初始化字段,但它将使用构造函数传递的映射存储为内部$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
Run Code Online (Sandbox Code Playgroud)
捕获异常并调用后备方法:
return (T) ProxyGenerator.INSTANCE.instantiateAggregateFromBaseClass(map, clazz);
Run Code Online (Sandbox Code Playgroud)
ProxyGeneratorProxyGeneratorAdapter通过以下方法进行初始化:
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);
}
Run Code Online (Sandbox Code Playgroud)
来源: 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对象。当它为空时,生成的代理类中没有应用委托机制。
最后但并非最不重要的一点是,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>
Run Code Online (Sandbox Code Playgroud)
如您所见,传递给构造函数的映射存储为$closure$delegate$map字段。所有A类值都使用其默认值进行初始化(null对于b字段和0剩余的 int 字段。)
现在,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);
}
Run Code Online (Sandbox Code Playgroud)
来源: https: //github.com/apache/groovy/blob/GROOVY_2_5_8/src/main/groovy/groovy/util/ProxyGenerator.java#L203
我猜测,如果使用ProxyGeneratorAdapter非空值,调用将使用内部委托映射中的值,而不是字段值。这只是我的假设。delegatingClassa.bb
问题是:这是一个错误吗?这取决于。正如 cfrick 在评论之一中提到的,A使用不正确的映射进行初始化会引发显式错误,然后您就完成了。这里这个异常被抑制了,从调用者的角度来看,你不知道后台发生了什么。我使用 Groovy 2.5.8 和 3.0.0-RC1 运行这些测试,两个版本中的行为相同。报告这是 Apache Groovy JIRA 项目中的一个问题听起来很合理,因此您可以从 Groovy 核心维护人员那里获得反馈。