目录
1.7.3. Annotation member values
1.7.5. Closure annotation parameters
2.7. Overriding default methods
2.9.2. Dynamic methods in a trait
2.10. Multiple inheritance conflicts
2.10.1. Default conflict resolution
2.10.2. User conflict resolution
2.11. Runtime implementation of traits
2.11.1. Implementing a trait at runtime
2.11.2. Implementing multiple traits at once
2.12.1. Semantics of super inside a trait
2.13.2. Differences with Java 8 default methods
2.15. Static methods, properties and fields
2.16. Inheritance of state gotchas
2.17.1. Type constraints on traits
2.17.2. The @SelfType annotation
2.18.1. Compatibility with AST transformations
2.18.2. Prefix and postfix operations
1.类型
1.1原始类型
Groovy支持与Java语言规范定义的原始类型相同的原始类型:
- 整形类型:
byte
(8 bit),short
(16 bit),int
(32 bit) 和long
(64 bit) - 浮点类型:
float
(32 bit) 和double
(64 bit) boolean
类型:true
、false
- char 类型:16bit,可用作数字类型,以UTF-16字符集码展现
Groovy也使用“对象即一切”的概念,尽管groovy声明并存储原始类型的字段和变量为原始类型,但它也会自动地对这些原始类型的字段和变量包装为对象的引用。这和java的拆装箱类似,groovy使用的原始类型包装器有:
原始类型 | 包装类型 |
---|---|
boolean | Boolean |
char | Character |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
这里有一个包装int原始类型的例子:
class Foo {
static int i
}
assert Foo.class.getDeclaredField('i').type == int.class
assert Foo.i.class != int.class && Foo.i.class == Integer.class
现在您可能会担心每次在对原始类型的引用上使用数学运算符时,您将承担拆箱和重新装箱原始类型的成本。但事实并非如此,因为Groovy会将您的运算符编译为其方法等价物(groovy操作符中提到过的运算符重载)并使用它们。
1.2 类
Groovy类与Java类非常相似,并且基于JVM来讲与java兼容。它们都有方法,字段和属性(想想JavaBean属性)。类和类成员可以使用与Java中相同的修饰符(public,protected,private,static等),在源代码级别有一些细微差别,稍后会对其进行解释。
Groovy类与其对应的Java类之间的主要区别是:
- 没有可见性修饰符的类或方法会自动默认为public的(但是可以使用特殊注释来实现package的私有性)。
- 没有可见性修饰符的字段会自动转换为属性,这样可以减少冗长的代码,不需要显式的getter和setter方法。有关此方面的更多信息将在字段和属性部分中介绍。
- 类名不需要和源文件同名,但在大多数情况下强烈建议使用它们。
- 一个源文件可能包含一个或多个类(但如果文件中的任何代码都不包含在类中,则将其视为脚本)。脚本只是具有一些特殊约定的类,并且与源文件具有相同的名称(所以不要在脚本源文件中定义与其同名的类)。
下面是一段代码示例:
class Person { //类开头,名为Person
String name //字符串字段和名为name的属性
Integer age
def increaseAge(Integer years) { //方法定义
this.age += years
}
}
1.2.1普通类
普通类指的是顶级和具体的类。这意味着它们可以在没有任何其他类或脚本限制的情况下实例化。这样,它们只能是公共的(即使可以没有public关键字)。通过使用new关键字调用其构造函数来实例化类,如下面的代码段所示:
def p = new Person()
1.2.2内部类
内部类在另一个类中定义,外部类可以像往常一样使用内部类,另一方面,内部类可以访问其外部类的成员,即使它们是私有的。外部类以外的类不允许访问内部类。这是一个例子:
class Outer {
private String privateStr
def callInnerMethod() {
new Inner().methodA() //内部类被实例化并调用其方法
}
class Inner { //内部类定义,在其外部类中
def methodA() {
println "${privateStr}." //即使是私有的,内部类也可以访问外部类的字段
}
}
}
使用内部类有一些原因:
- 它们通过将内部类隐藏在其他类中来增加封装,其他的这些类不需要知道它。这也导致更清洁的包装和工作空间。
- [...]
- 内部类使代码更加可维护,因为内部类靠近使用它们的类。
在某些情况下,内部类是接口的实现,其外部类需要其方法,下面的代码通过使用线程来说明这一点,这很常见:
class Outer2 {
private String privateStr = 'some string'
def startThread() {
new Thread(new Inner2()).start()
}
class Inner2 implements Runnable {
void run() {
println "${privateStr}."
}
}
}
请注意,类Inner2的定义仅用于提供Outer2需要的run方法实现,在这种情况下,匿名内部类有助于消除冗长。内部类的最后一个示例可以使用匿名内部类进行简化,使用以下代码可以实现相同的功能:
class Outer3 {
private String privateStr = 'some string'
def startThread() {
new Thread(new Runnable() { //与上面的例子对比,new Inner2()被new Runnable()匿名内部类以及实现所替换
void run() {
println "${privateStr}."
}
}).start()
}
}
因此,不需要定义仅使用一次的新类。
1.2.3抽象类
抽象类表示通用概念,因此,它们无法实例化,只能创建其为子类。他们的成员包括字段/属性和抽象或具体方法。抽象方法没有实现,必须由具体的子类实现。
abstract class Abstract { //必须使用abstract关键字声明抽象类
String name
abstract def abstractMethod() //抽象方法也必须用abstract关键字声明
def concreteMethod() {
println 'concrete'
}
}
通常将抽象类与接口进行比较,但是选择抽象类或接口至少有两个重要的区别。首先,抽象类可能包含字段/属性和具体方法,但接口只包含抽象方法(方法签名);而且,一个类可以实现几个接口,但是只可以扩展一个类,抽象的或不抽象的。
1.3接口
接口定义了类需要遵循的契约。接口仅定义需要实现的方法列表,但不定义方法实现。
interface Greeter { //需要使用interface关键字声明接口
void greet(String name) //接口只定义方法签名
}
接口的方法总是公开的,在接口中使用受保护或私有方法是错误的:
interface Greeter {
protected void greet(String name) //使用protected出现编译时错误
}
以下类定义greet方法,因为它只在在Greeter接口中声明:
class DefaultGreeter {
void greet(String name) { println "Hello" }
}
greeter = new DefaultGreeter()
assert !(greeter instanceof Greeter)
换句话说,Groovy没有定义结构类型。但是,可以使用as强制运算符使对象的实例在运行时实现接口:
greeter = new DefaultGreeter() //创建一个不实现Greeter接口的DefaultGreeter实例
coerced = greeter as Greeter //在运行时将实例强制转换为Greeter
assert coerced instanceof Greeter //强制实例实现了Greeter接口
你可以看到有两个不同的对象:一个DefaultGreeter实例对象,它没有实现接口,另一个是Greeter的一个实例,它委托给被强制转换的对象。
1.4构造方法
构造函数是用于初始化具有指定状态的对象的特殊方法。与普通方法一样,只要每个构造函数具有唯一的类型签名,类就可以声明多个构造函数。如果对象在构造期间不需要任何参数,则可以使用无参数构造函数。如果没有提供构造函数,Groovy编译器将提供一个空的无参数构造函数。
Groovy支持两种调用方式:
- 位置参数的使用方式与使用Java构造函数的方式类似;
- 命名参数允许您在调用构造函数时指定参数名称。
1.4.1位置参数
要使用位置参数创建对象,相应的类需要声明一个或多个构造函数,在多个构造函数的情况下,每个构造函数必须具有唯一的类型签名。构造函数也可以使用groovy.transform.TupleConstructor注释的形式添加到类中。通常,一旦声明了至少一个构造函数,该类只能通过调用其构造函数来实例化。值得注意的是,在这种情况下,您通常无法使用命名参数创建类。Groovy支持命名参数,只要该类包含一个无参数构造函数或提供一个构造函数,该构造函数将Map参数作为第一个(也可能是唯一的)参数 - 有关详细信息,请参阅下一节。
使用声明的构造函数有三种形式。第一个是普通的Java方式,使用new关键字,其它2种是依赖于将列表强制转换为所需的类型。在这种情况下,使用as关键字强制键入变量。
class PersonConstructor {
String name
Integer age
PersonConstructor(name, age) { //构造函数声明
this.name = name
this.age = age
}
}
def person1 = new PersonConstructor('Marie', 1) //构造函数调用,经典的Java方式
def person2 = ['Marie', 2] as PersonConstructor //使用as作为关键字强制键入变量
PersonConstructor person3 = ['Marie', 3] //强制赋值
1.4.2命名参数
如果没有声明(或有一个无参数)的构造函数,则可以通过以map(键值对)的形式传递参数来创建对象。在需要允许多个参数组合的情况下,这可以派上用场。否则,还是使用传统的位置参数,就有必要声明所有可能的构造函数。有一个构造函数,其中第一个(也许只是唯一的)参数是一个Map参数,命名参数也是支持的 , 这样的构造函数也可以使用groovy.transform.MapConstructor注释添加。
class PersonWOConstructor { //没有声明构造函数
String name
Integer age
}
def person4 = new PersonWOConstructor() //实例化中没有给出参数
def person5 = new PersonWOConstructor(name: 'Marie')//实例化中给出的name参数
def person6 = new PersonWOConstructor(age: 1) //实例化中给出的age参数
def person7 = new PersonWOConstructor(name: 'Marie', age: 2) //实例化中给出的name,age参数
然而,需要强调的是,这种方法为构造函数的调用者提供了更多的功能,同时增加了调用者的责任,以使名称和值类型正确。因此,如果需要更宽泛的控制,则可能优选使用位置参数来声明构造函数。
注意:
- 虽然上面的示例没有提供构造函数,但您也可以提供无参数构造函数或构造函数,其中第一个参数是Map,最常见的是它是唯一的参数。
- 当没有声明(或有一个无参数)的构造函数时,Groovy通过调用无参构造函数替换命名的构造函数调用,然后调用每个提供的命名属性的setter。
- 当第一个参数是Map时,Groovy将所有命名参数组合到map中(无论排序如何)并将map作为第一个参数提供。如果您的属性被声明为final,那么这可能是一个很好的方法(因为它们将在构造函数中设置而不是之后用setter设置)。
- [...]
1.5方法
Groovy方法与其他语言非常相似。一些特点将在下一小节中展示。
1.5.1. 方法定义
使用返回类型或使用def关键字定义方法,以使返回类型无类型化。方法还可以接收任意数量的参数,这些参数可能没有显式声明其类型。Java修饰符可以正常使用,如果没有提供可见性修饰符,则该方法是公共的。Groovy中的方法总是返回一些值。如果未提供return语句,则将返回最后一行中计算的值。例如,请注意以下方法都不使用return关键字。
def someMethod() { 'method called' } //没有声明返回类型,也没有参数
String anotherMethod() { 'another method called' } //显式返回类型,没有参数
def thirdMethod(param1) { "$param1 passed" } //没有定义类型的参数
static String fourthMethod(String param1) { "$param1 passed" } //带String参数的静态方法
1.5.2. Named parameters
略,大家自己看了,时间紧。
1.5.3. 默认参数
默认参数使参数可选。如果未提供参数,则该方法采用默认值。
def foo(String par1, Integer par2 = 1) { [name: par1, age: par2] }
assert foo('Marie').age == 1
1.5.4. 可变参数
Groovy方法支持可变参数,如:def foo(p1,...,pn,T ... args),这里foo默认支持n个参数,但是还有一个未指定数量的其他参数args。
def foo(Object... args) { args.length }
assert foo() == 0
assert foo(1) == 1
assert foo(1, 2) == 2
这个例子定义了一个方法foo,它可以接受任意数量的参数,包括根本没有参数。args.length将返回给定的参数数量。Groovy允许T []作为T ...的替代符号。这意味着任何带有数组作为最后一个参数的方法都被Groovy看作是一个可以获取可变数量参数的方法。
def foo(Object[] args) { args.length }
assert foo() == 0
assert foo(1) == 1
assert foo(1, 2) == 2
如果使用null作为可变参数值,则args将为null,而不是长度为1的数组,其中null为唯一元素。
def foo(Object... args) { args }
Integer[] ints = [1, 2]
assert foo(ints) == [1, 2]
另一个重点是可变参数与方法重载相结合。 在方法重载的情况下,Groovy将选择最具体的方法。例如,如果方法foo采用类型为T的可变参数,而另一个方法foo也采用类型为T的一个参数,则第二种方法是首选方法:
def foo(Object... args) { 1 }
def foo(Object x) { 2 }
assert foo() == 1
assert foo(1) == 2
assert foo(1, 2) == 1
1.5.5. 重载方法选择算法
待定。
1.5.6. 异常声明
Groovy自动允许您处理已检查的异常,像处理未经检查的异常一样。您不需要声明方法可能抛出的任何已检查异常,如以下示例所示,如果找不到该文件,则会抛出FileNotFoundException:
def badRead() {
new File('doesNotExist.txt').text
}
shouldFail(FileNotFoundException) {
badRead()
}
您也不需要在try / catch块中将上一个示例中的badRead方法的调用包围起来 - 如果您愿意,您随便。
如果您希望声明代码可能抛出的任何异常(检查的或者其他什么的),您可以自由发挥。异常将成为字节码中方法声明的一部分,因此如果您的代码可能是从Java调用的,那么包含它们可能会很有用。以下示例说明了使用显式检查的异常声明:
def badRead() throws FileNotFoundException {
new File('doesNotExist.txt').text
}
shouldFail(FileNotFoundException) {
badRead()
}
1.6字段和属性
1.6.1. 字段
字段是类或trait的成员,具有:
- 必须的访问修饰符(
public
,protected
, 或private
) - 一个或多个可选修饰符(
static
,final
,synchronized
) - 可选的类型
- 必须的名称
class Data {
private int id //一个名为id的私有字段,类型为int
protected String description //一个名为description的受保护字段,类型为String
public static final boolean DEBUG = false //一个名为DEBUG的公共静态final字段,类型为boolean
}
可以在声明时直接初始化字段:
class Data {
private String id = IDGenerator.next() //使用IDGenerator.next()初始化私有字段id
// ...
}
可以省略字段的类型声明,然而,这被认为是一种不好的做法,一般来说,对字段使用强类型是个好主意:
class BadPractice {
private mapping //字段mapping不声明类型
}
class GoodPractice {
private Map<String,String> mapping //字段mapping具有强类型
}
如果您想稍后使用可选类型检查,则两者之间的区别很重要。它对文档也很重要。但是在某些情况下,若脚本或者你想依赖鸭子类型,省略类型可能会很有趣。
1.6.2. 属性
属性是一个类的外部可见特性,不仅仅使用public field来表示这些特性(提供更有限的抽象并限制重构的可能性),Java中的典型约定是遵循JavaBean约定,即使用私有字段和getters/setter的组合来表示属性。Groovy遵循这些相同的约定,但提供了一种更简单的方法来定义属性。您可以使用以下内容定义属性:
- 缺省的访问修饰符(没有
public
,protected
或private
) - 一个或多个可选的修饰符 (
static
,final
,synchronized
) - 可选的类型
- 必需的名称
然后Groovy将适当地生成getter / setter。例如:
class Person {
String name //创建一个支持私有String类型的name字段,一个getName和一个setName方法
int age //创建一个支持私有int类型的age字段,一个getAge和一个setAge方法
}
如果属性被声明为final,则不会生成setter:
class Person {
final String name //定义String类型的只读属性
final int age //定义int类型的只读属性
Person(String name, int age) {
this.name = name //将name参数指定给name字段
this.age = age //将age参数指定给age字段
}
}
属性按名称访问,并将透明地调用getter或setter,除非代码位于定义属性的类中:
class Person {
String name
void name(String name) {
this.name = "Wonder$name" //this.name将直接访问该字段,因为该属性是从定义它的类中访问的
}
String wonder() {
this.name //类似地,直接在name字段上进行读取访问
}
}
def p = new Person()
p.name = 'Marge' //对属性的写访问是在Person类之外完成的,因此它将隐式调用setName
assert p.name == 'Marge'//对属性的读访问是在Person类之外完成的,因此它将隐式调用getName
p.name('Marge') //这将调用Person上的name方法,该方法执行对该字段的直接访问
assert p.wonder() == 'WonderMarge'//这将调用Person上的wonder方法,该方法对该字段执行直接读访问
由于实例的元 properties
字段,可以列出类的所有属性:
class Person {
String name
int age
}
def p = new Person()
assert p.properties.keySet().containsAll(['name','age'])
按照惯例,即使没有声明支持的字段,Groovy也会识别属性,前提是存在遵循Java Bean规范的getter或setter方法。例如:
class PseudoProperties {
// a pseudo property "name"
void setName(String name) {}
String getName() {}
// a pseudo read-only property "age"
int getAge() { 42 }
// a pseudo write-only property "groovy"
void setGroovy(boolean groovy) { }
}
def p = new PseudoProperties()
p.name = 'Foo' //允许写p.name,因为有一个属性name
assert p.age == 42 //允许读取p.age,因为存在只读属性age
p.groovy = true //允许写p.groovy,因为有一个只写属性groovy
这种语法糖是Groovy编写的许多DSL的核心。
原文出处:http://groovy-lang.org/objectorientation.html
转载无罪,注明可嘉,且行且珍惜。
1.7注释
1.7.1. Annotation definition
1.7.2. Annotation placement
1.7.3. Annotation member values
1.7.4. Retention policy
1.7.5. Closure annotation parameters
1.7.6. Meta-annotations
Declaring meta-annotations、Behavior of meta-annotations、Meta-annotation parameters、Handling duplicate annotations、Custom annotation processors
1.8. Inheritance
(TBD)
1.9. Generics
(TBD)