groovy面向对象

目录

1.类型

1.1原始类型

1.2 类

1.2.1普通类

1.2.2内部类

1.2.3抽象类

1.3接口

1.4构造方法

1.4.1位置参数

1.4.2命名参数

 1.5方法

1.5.1. 方法定义

1.5.2. Named parameters

1.5.3. 默认参数

1.5.4. 可变参数

1.5.5. 重载方法选择算法

1.5.6. 异常声明

1.6字段和属性

1.6.1. 字段

1.6.2. 属性

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

1.8. Inheritance

1.9. Generics

2. Traits

2.1. Methods

2.1.1. Public methods

2.1.2. Abstract methods

2.1.3. Private methods

2.1.4. Final methods

2.2. The meaning of this

2.3. Interfaces

2.4. Properties

2.5. Fields

2.5.1. Private fields

2.5.2. Public fields

2.6. Composition of behaviors

2.7. Overriding default methods

2.8. Extending traits

2.8.1. Simple inheritance

2.8.2. Multiple inheritance

2.9. Duck typing and traits

2.9.1. Dynamic code

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. Chaining behavior

2.12.1. Semantics of super inside a trait

2.13. Advanced features

2.13.1. SAM type coercion

2.13.2. Differences with Java 8 default methods

2.14. Differences with mixins

2.15. Static methods, properties and fields

2.16. Inheritance of state gotchas

2.17. Self types

2.17.1. Type constraints on traits

2.17.2. The @SelfType annotation

2.18. Limitations

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的成员,具有:

  • 必须的访问修饰符(publicprotected, 或private)
  • 一个或多个可选修饰符(staticfinalsynchronized)
  • 可选的类型
  • 必须的名称
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遵循这些相同的约定,但提供了一种更简单的方法来定义属性。您可以使用以下内容定义属性:

  • 缺省的访问修饰符(没有publicprotected 或private)
  • 一个或多个可选的修饰符 (staticfinalsynchronized)
  • 可选的类型
  • 必需的名称

然后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)

2. Traits

2.1. Methods

2.1.1. Public methods

2.1.2. Abstract methods

2.1.3. Private methods

2.1.4. Final methods

2.2. The meaning of this

2.3. Interfaces

2.4. Properties

2.5. Fields

2.5.1. Private fields

2.5.2. Public fields

2.6. Composition of behaviors

2.7. Overriding default methods

2.8. Extending traits

2.8.1. Simple inheritance

2.8.2. Multiple inheritance

2.9. Duck typing and traits

2.9.1. Dynamic code

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. Chaining behavior

2.12.1. Semantics of super inside a trait

2.13. Advanced features

2.13.1. SAM type coercion

2.13.2. Differences with Java 8 default methods

2.14. Differences with mixins

2.15. Static methods, properties and fields

2.16. Inheritance of state gotchas

2.17. Self types

2.17.1. Type constraints on traits

2.17.2. The @SelfType annotation

2.18. Limitations

2.18.1. Compatibility with AST transformations

2.18.2. Prefix and postfix operations

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值