1。简介本文开始介绍Groovy中的各类知识。会有多篇文章详细介绍和学习Groovy中类型的相关知识点。内容来自1.6.6中的相关知识点。从官方Groovy文档中获取的类型。内容比较多。你可以通过目录查询你想了解的模块。2.可选类型——可选类型可选类型意味着即使不为变量设置显式类型,程序也可以运行。Groovy作为一种动态语言,自然而然地实现了这个特性,例如在声明一个变量时:StringaString='zinyan.com'//声明一个变量字符串//我们调用这个字符串的大小写转换方法并输出printlnaString.toUpperCase()//Output:ZINYAN.COM在Groovy中,我们可以传递可选的类型关键字:def代替:defaString='zinyan.com'//声明一个变量字符串printlnaString.toUpperCase()的两种写法是相同的。def不仅可以代替String,它可以代替任何数据类型。所以在这里使用显式类型并不重要。当我们将此功能与静态类型检查结合使用时,这一点尤其有趣,因为类型检查器会执行类型推断。类似地,Groovy不强制方法中声明的参数类型:Stringconcat(Stringa,Stringb){a+b}printlnconcat('zinyan','.com')//Output:zinyan.com可以使用defas要覆盖的返回类型和参数类型以利用duck类型,如以下示例所示:defconcat(defa,defb){a+b}printlnconcat('zinyan','.com')//output:zinyan.comprintlnconcat(1,2)//Output:3我们可以通过def可选类型实现动态参数处理。扩展了该方法的使用范围。建议在此处使用def关键字来描述适用于任何类型的方法的意图,但从技术上讲,我们可以使用Object并且结果是相同的:在Groovy中,def严格等同于使用Object。最终,可以从返回类型和描述符中完全删除该类型。但是如果你想从返回类型中移除它,你需要在方法中添加一个显式修饰符,以便编译器可以在方法声明和方法调用之间进行区分,如下例所示:privateconcat(a,b){a+b}printlnconcat('zinyan','.com')//输出:zinyan.comprintlnconcat(1,2)//输出:3我们直接省略了def。在公共API的方法参数或方法返回类型中省略类型通常被认为是不好的做法。虽然在局部变量中使用def并不是真正的问题,因为变量的可见性仅限于方法本身,当def设置在方法参数上时,def将在方法签名中转换为Object,这使得它用户很难知道哪种类型的参数是预期的类型。PS:综上所述,我们可以将类型定义为def,然后省略def。但是不建议大家在对外提供的API中省略def。很容易造成阅读困难。其次,def是java中的Object对象。只是Groovy帮我们在编译器中进行了转换,用于中间的各种转换和解析函数。3.静态类型检查——静态类型检查默认情况下,Groovy在编译时执行最少的类型检查。由于它主要是一种动态语言,因此静态编译器通常在编译时执行的大部分检查都不可用。通过运行时元编程添加的方法可能会改变类或对象的运行时行为。举例介绍://创建一个对象classPerson{StringfirstNameStringlastName}//初始化实例对象defp=newPerson(firstName:'Zin',lastName:'yan')printlnp.formattedName在动态语言中,很像上面例子这样的代码通常不会抛出任何错误。在Java中,这通常会在编译时失败。如果我们直接执行上面的代码,会输出::8)错误信息,如果我们想正常运行,我们需要进行依赖的运行时元编程。即修改运行时状态,实现动态特性:Person.metaClass.getFormattedName={"$delegate.firstName$delegate.lastName"}完整的例子是://CreateanobjectclassPerson{StringfirstNameStringlastName}//初始化实例对象Person.metaClass.getFormattedName={"$delegate.firstName$delegate.lastName"}defp=newPerson(firstName:'Zin',lastName:'yan')printlnp.formattedName表示,在一般而言,在Groovy中,除了声明的类型之外,我们不能对对象的类型做出任何假设,即使我们知道,也没有办法在编译时确定将调用什么方法,或者将调用哪个属性检索。此功能用于DSL和测试脚本的许多功能中。这里我就不展开了。但是,如果我们的程序不依赖于动态特性,而是来自静态世界(尤其是来自Java的思维方式),这样的“错误”在编译时不会被捕获,并且可能会崩溃。正如我们在前面的示例中看到的,编译器无法确定这是一个错误。为了让编译器知道这一点,必须明确地向编译器表明我们正在切换到类型检查模式。这可以通过使用@groovy.transform.TypeChecked注释类或方法来完成。当类型检查被激活时,编译器将添加以下工作:类型推断被激活,这意味着即使def用于局部变量,类型检查器也能够从赋值中推断出变量的类型。方法调用是在编译时解析的,也就是说如果方法没有声明在类上,编译器会抛出一个错误,一般你在静态语言中寻找的编译时错误都会出现:methodnotfound,property未找到、方法调用的不兼容类型、数字精度错误等...让我们描述类型检查器在各种情况下的行为,并解释在代码中使用@TypeChecked的限制。3.1@TypeChecked注解在编译时激活类型检查。我们可以在类的开头添加@groovy.transform.TypeChecked注解,让编译器在编译类时启用类型检查:@groovy.transform.TypeCheckedclassCalculator{intsum(intx,inty){x+y}}或添加到方法:classCalculator{@groovy.transform.TypeCheckedintsum(intx,inty){x+y}}在第一种情况下,所有方法、属性、字段、内部类...注释类Types的大部分将被类型检查,而在第二种情况下,只有方法和它包含的潜在闭包或匿名内部类将被类型检查。我们也可以用@TypeChecked(TypeCheckingMode.skip)注解来指示类型检查器跳过类型检测:@TypeCheckedclassGreetingService{Stringgreeting(){doGreet()}//跳过doGreet方法的类型检查。@TypeChecked(TypeCheckingMode.SKIP)privateStringdoGreet(){defb=newSentenceBuilder()b.Hello.my.name.is.Zinyanb}}defs=newGreetingService()asserts.greeting()=='你好,我叫Zinyan'在前面的示例中,SentenceBuilder依赖于动态代码。没有真正的Hello方法或属性,因此类型检查器通常会抛出异常并且编译失败。因为使用生成器的方法被标记为TypeCheckingMode.SKIP,所以此方法会跳过类型检查,因此即使类的其余部分进行了类型检查,也会编译代码。以下部分描述了Groovy中类型检查的语义。3.2类型检查当且仅当:T等于A.或T是以下类型之一:String,boolean,Booleanor班级。或者o为空且T不是基本类型。或者T和A是一个数组,并且A的组件类型可分配给T的组件类型。或者T是数组,A是集合或流(stream),A的组件类型可赋值给T的组件类型。或者T是A的超类。OrT是A实现的接口。OrT或A是原始类型,其封装类型是可分配的。或者Textednsgroovy.lang.Closure是闭包,A是SAM类型(单一抽象方法类型)。或者T和A从java.lang.Number派生,并遵循下表:TAExamplesDoubleAnybutBigDecimalorBigIntegerDoubled1=4dDoubled2=4fDoubled3=4lDoubled4=4iDoubled5=(short)4Doubled6=(byte)4FloatAnytypebutBigDecimal,BigIntegerorDoubleFloatf1=4fFloatf2=4lFloatf3=4iFloatf4=(short)4Floatf5=(byte)4LongAnytypebutBigDecimal,BigInteger,DoubleorFloatLongl1=4lLongl2=4iLongl3=(short)4Longl4=(byte)4IntegerAnytypebutBigDecimal,BigInteger,Double,FloatorLongIntegeri1=4iIntegeri2=(short)4Integeri3=(byte)4Short除BigDecimal、BigInteger、Double、Float、Long或IntegerShorts1=(short)4Shorts2=(byte)4ByteByteByteb1=(byte)43.3List和Map的构造函数除了上述赋值规则外,如果认为赋值无效,在类型检查模式下,如果满足以下条件,List或MapA可以赋值给T的类型变量:assignment是一个变量声明,A是一个List,T有一个构造函数,其参数与List的元素类型相匹配。赋值是一个变量声明,A是一个映射,T有一个无参数的构造函数,每个映射键都有一个属性。具体例子如下:@groovy.transform.TupleConstructorclassPerson{StringfirstNameStringlastName}Personclassic=newPerson('Zin','yan')可以使用“列表构造函数”:Personlist=['Zin','yan']创建一个Person对象,或者使用Map构造函数创建一个Person对象:Personmap=[firstName:'Zin',lastName:'yan']如果使用Map构造函数,将对映射键,检查是否定义了具有相同名称的属性。例如,下面的代码会在编译时失败:@groovy.transform.TupleConstructorclassPerson{StringfirstNameStringlastName}Personmap=[firstName:'Zin',lastName:'yan',age:1024]会触发下面的错误:org.codehaus.groovy.runtime.typehandling.GroovyCastException:无法将对象'{firstName=Zin,lastName=yan,age=1024}'与类'java.util.LinkedHashMap'转换为类'Person'由于:org.代码之家。groovy.runtime.metaclass.MissingPropertyExceptionNoStack:Nosuchproperty:ageforclass:Person3.4方法解析在类型检查模式下,方法在编译时解析。解析按名称和参数进行。返回类型与方法选择无关。参数类型按照以下规则匹配参数类型:A类型的参数o可以用于类型T的参数当且仅当:T等于A。或者T是一个字符串,A是一个GString。或者o为空且T不是基础类型。或者T是一个数组,A是一个数组,A的组件类型可分配给T的组件类型。或者T是A的超类。或者T是由A实现的接口。或者T或A是原始类型,其封装类型是可分配的。或T扩展groovy.lang.Closure并且A是SAM类型(单一抽象方法类型)。或者T和A派生自java.lang.Number并遵循与数字赋值相同的规则。如果在编译时没有找到具有适当名称和参数的方法,则抛出错误。下面的示例说明了与“普通”Groovy的区别:展示了一个Groovy可以编译的类。但是,如果您尝试创建MyService的实例并调用doSomething方法,它将在运行时失败,因为printLine不存在。当然,我们已经展示了Groovy如何使它成为一个完全有效的调用,例如通过捕获MethodMissingException或实现自定义元类,但如果您知道自己不处于这种情况,@typecheck会派上用场:@groovy.transform.TypeCheckedclassMyService{voiddoSomething(){printLine'Dosomething'}}只需添加@类型检查将触发编译时方法解析。类型检查器将尝试在接受字符串的MyService类上查找方法printLine,如果找不到。它将无法编译并显示消息:找不到匹配方法MyService#printLine(java.lang.String)了解类型检查器背后的逻辑很重要:它是编译时检查,因此根据定义类型检查器确实不知道我们所做的任何类型的运行时元编程。这意味着如果激活类型检查,则在没有@TypeChecked的情况下完全有效的代码将不再编译。如果您考虑鸭子类型,这一点尤其重要:classDuck{voidquack(){println'Quack!'}}classQuackingBird{voidquack(){println'Quack!'}}@groovy.transform.TypeCheckedvoidaccept(quacker){quacker.quack()}accept(newDuck())就像引入一个接口,但基本上,通过激活类型检查,获得类型安全但失去一些语言的特性。希望Groovy会引入像流类型这样的特性来缩小类型检查和非类型检查Groovy之间的差距。4.总结本文内容未完待续。您可以在下一篇文章中阅读完整内容。以上内容参考Groovy官方文档:https://img.ydisp.cn/news/20230103/cmhakrxfnw3
