面向对象
包装器类型
基本类型 | 包装器类型 |
---|---|
boolean | Boolean |
char | Character |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
它也有自动装箱(boxing
)与拆箱(unboxing
),但Groovy更灵活,它可以直接在基本类型上调用只有包装器类型才有的方法。
> def num = 1
> println(num.toString())
1
> println(num.equals(1))
true
> println(num.class.equals(int.class))
false
> println(num.class.equals(Integer.class))
true
类
在前面的内容已经出现过很多类了。Groovy和Java中的类非常相似,属性、方法、修饰基本上都相同。
普通的类
除了定义的关键字不同外,有一个比较明显的区别就是Groovy中类方法如果需要返回值时,return
关键字可加可不加。
> class Person {
> String name
> int age
// 这里的def关键字也可以不加,但如果没有明确指定返回值类型,那def就必须加
// 返回值类型也可以是void
> def int getAge(int age) {
> this.age += age
// 这里可以加return关键字,也可以不加
> this.age
> }
> }
> def person = new Person(name: 'lixingyun', age: 19)
> println person.getAge(1)
20
内部类
> class Outer {
> private String privateStr = "Outer Class Private String"
> def callInnerMethod() {
> new Inner().methodA()
> }
> class Inner {
// 内部类可以任意访问外部类的私有成员变量,这和Java是一样的
> def methodA() {
> println "${privateStr}"
> }
> }
> }
> def outer = new Outer()
> outer.callInnerMethod()
Outer Class Private String
内部类通常用于外部类实现某个接口的方法。
> class Outer2 {
> Outer2() {}
> private String privateStr = 'some thing running in thread'
// 配合多线程执行
> def startThread() {
> new Thread(new Inner2()).start()
> }
> class Inner2 implements Runnable {
> void run() {
> println "${privateStr}"
> }
> }
> class Inner3 {
> int coreNumber = 0
> Inner3(int coreNumber) {
> this.coreNumber = coreNumber
> }
> }
> }
> def outer2 = new Outer2()
> outer2.startThread()
// 支持对非静态内部类进行实例化的Java语法
> println(new Outer2().new Inner3(1).coreNumber)
1
some thing running in thread
匿名内部类
使用匿名内部类的方式来执行多线程任务会简单很多。
> class Outer3 {
> private String privateStr = 'some string running in a thread'
> def startThread() {
// 匿名内部类
> new Thread(new Runnable() {
> void run() {
> println "${privateStr}"
> }
> }).start()
> }
> }
> def outer = new Outer3()
> outer.startThread()
some string running in a thread
至于抽象类
、继承
(extends
)和接口
(interface
)都和Java中的概念没什么区别。
/**
* 抽象类
*
*/
// 抽象类必须用abstract关键字修饰
> abstract class Abstract {
> String name
// 抽象类必须有抽象方法
> abstract def abstractMethod()
> def concreteMethod() {
> println 'concrete'
> }
> }
// 抽象类不能直接实例化
// def a = new Abstract()
/**
* 类的继承
*
*/
> class Person {
> String name
> int age
> }
> class User extends Person {
> String school
> }
> def user = new User(name: 'lixingyun', age: 18, school: '清华')
> println(user.name)
lixingyun
/**
* 接口的实现
*
*/
> interface Greeter {
> void greet(String name)
> }
> class SystemGreeter implements Greeter {
// @Override关键字可加可不加
> void greet(String name) {
> println "Hello $name"
> }
> }
> def greeter = new SystemGreeter()
> greeter.greet("Groovy")
Hello Groovy
// greeter既是SystemGreeter的实例,又是Greeter的实例
> println(greeter instanceof SystemGreeter)
true
> println(greeter instanceof Greeter)
true
类的成员
构造器
Groovy可以用两种方式创建对象。
位置参数
(Positional parameters
):这种方式和Java构造函数的调用方式一样。命名参数
(Named parameters
):这一点和Scala、Go、Python这些语言没什么不同,都是通过明确指明参数的名称来调用构造器。
/**
* 使用位置参数构造类
*
*/
> class PersonPositional {
> String name
> int age
// 构造器
> PersonPositional(name, age) {
> this.name = name
> this.age = age
> }
> }
// 位置参数:第一种调用构造器的方式
> def person1 = new PersonPositional('lixingyun', 20)
// 位置参数:第二种调用构造器的方式
> def person2 = ['wanglin', 19] as PersonPositional
// 位置参数:第三种调用构造器的方式
> PersonPositional person3 = ['xiaoyan', 21]
> println("person1: ${person1.name}, ${person1.age}")
person1: lixingyun, 20
> println("person2: ${person2.name}, ${person2.age}")
person2: wanglin, 19
> println("person3: ${person3.name}, ${person3.age}")
person3: xiaoyan, 21
/**
* 使用命名参数构造类时不能有任何无参或有参构造器
*
*/
> class PersonNamed {
> String name
> int age
// 类中不能出现任何构造器
> }
> def person4 = new PersonNamed()
> def person5 = new PersonNamed(name: 'lixingyun')
> def person6 = new PersonNamed(age: 19)
> def person7 = new PersonNamed(age: 20, name: 'wanglin')
> println("person4: ${person4.name}, ${person4.age}")
person4: null, 0
> println("person5: ${person5.name}, ${person5.age}")
person5: lixingyun, 0
> println("person6: ${person6.name}, ${person6.age}")
person6: null, 19
> println("person7: ${person7.name}, ${person7.age}")
person7: wanglin, 20
方法
Groovy有这么几种方法定义的形式。
> def someMethod() {
> 'method called'
> }
> String anotherMethod() {
> 'another method called'
> }
> def thirdMethod(param1) {
> "$param1 passed"
> }
> static String fourthMethod(String param1) {
> "$param1 passed"
> }
// 只要方法的参数不同,那就不是同一个方法
> static String fourthMethod(String param1, String param2) {
> "$param1 passed,$param2 passed"
> }
> static String fourthMethod(String param1, Integer param2) {
> "$param1 passed,$param2 OK"
> }
> println(someMethod())
method called
> println(anotherMethod())
another method called
> println(thirdMethod('lixingyun'))
lixingyun passed
> println(fourthMethod('lixingyun'))
lixingyun passed
> println(fourthMethod('lixingyun', 'wanglin'))
lixingyun passed,wanglin passed
> println(fourthMethod('lixingyun', 19))
lixingyun passed, 19 ok
方法也和构造器一样,有命名参数
和位置参数
的区分,也可以把这两种混合起来用:通过Map
来实现,前提是Map
要是第一个参数,不然就不能直接提供name
和age
键。
> def test(Map args, Integer number) {
> "${args.name}: ${args.age}, and the number is ${number}"
> }
// 调用位置参数
> println(test(name: 'lixingyun', age: 19, 23))
lixingyun: 19, and the number is 23
// 调用命名参数
> println(test(23, name: 'lixingyun', age: 1))
lixingyun: 1, and the number is 23
// 如果Map不是第一个参数,那么这么用会抛出groovy.lang.MissingMethodException异常
> def test2(Integer number, Map args) {
> "${args.name}: ${args.age}, and the number is ${number}"
> }
> def test(String name, int age = 19) {
> [name: name, age: age]
> }
> println(test('lixingyun').age)
19
也能使用Java的变长参数。
> def test(Object... args) {
// 使用闭包迭代(单独调用闭包进行迭代,和联合for进行迭代,得到的结果是不同的)
> args.each {
> println(it)
> }
> println("--------------")
// 使用for迭代
> for (Object arg : args) {
> println arg
> }
> }
> println(test())
--------------
null
> println("============================")
1
--------------
1
null
> println(test(1))
> println("============================")
> println(test(1, 2))
1
2
--------------
1
2
null
传入可变参数的形式也可以是数组Array
。
> def test1(Object[] args) { args }
> def test2(Object... args) { args }
> Integer[] ints = [1, 2]
> println(test1(ints))
[1, 2]
> println(test2(ints))
[1, 2]
Groovy支持方法选择算法,也就是方法执行的结果是由传入的参数类型动态确定的。
> def test(Object o1, Object o2) { 'o/o' }
> def test(int i, String s) { 'i/s' }
> def test(String s, int i) { 's/i' }
> def test(int s, int i) { s / i }
> List<List<Object>> pairs = [['foo', 1], [2, 'bar'], [3, 4]]
// 这里根据方法的不同参数,选择执行不同的方法
> println(test(4, 2))
2
// 使用lambda表达式传递多个参数
> println(pairs.collect { a, b -> test(a, b) })
[s/i, i/s, 0.75]
Groovy不需要显式地声明任何异常,因为它会自动处理任何异常。但按照Java的开发习惯来声明并抛出异常,可能更有利于后期维护。
> // 不声明任何异常
> def badRead1() {
> new File('badRead1: does not exist').text
> }
> badRead1()
java.io.FileNotFoundException: badRead1: does not exist (No such file or directory)
> // 按照Java的开发习惯声明异常
> def badRead2() throws FileNotFoundException {
> new File('badRead2: does not exist').text
> }
> badRead2()
java.io.FileNotFoundException: badRead2: does not exist (No such file or directory)
字段和属性
注意:对于Groovy来说,字段
和属性
是不同的!
Groovy类的成员变量,也就是字段,和Java同样有这些共同的性质。
强制的访问修饰符(
public
、protected
或private
),如果不指定,那它默认就是private
。一个或多个可选的修饰符(
static
、final
或synchronized
)。可选的数据类型。
必须指定字段名称。
而属性
和字段唯一的区别就是它没有访问修饰符(public
、protected
或private
),其他都一样。
但字段
和属性
一个很重要的区别就是:字段
是用于存储数据的,而属性
则是外部可见特征,也就是Groovy会自动给字段提供getter
和setter
方法。
// 下面是Test1类的字段
> class Test1 {
> private int id
> private String ids = "init"
> protected String description
> public static final boolean DEBUG = false
> private Map<String, String> mapping1
> private mapping2 = [:]
> }
> def test1 = new Test1()
// 执行报错
> println(test1.getIds())
groovy.lang.MissingMethodException: No signature of method: Test1.getIds() is applicable for argument types: () values: []
// 虽然这里也可以通过`.`操作符访问获取它的值,但编辑器中的代码提示却没有`ids`出现,只能说明它是运行时由Groovy动态生成的访问方式
> println(test1.ids)
init
// 下面是Test2类的属性
> class Test2 {
// 只会生成getter而不会生成setter方法
> final int count = 0
// static修饰符会生成静态的getter和setter方法
> static String name = "lixingyun"
> String ids = "init"
> }
> def test2 = new Test2()
> println(test2.getIds())
init
> test2.setIds("new init")
> println(test2.getIds())
new init
> println(test2.ids)
new init
> println(test2.getName())
lixingyun
> test2.setName("new lixingyun")
> println(test2.getName())
new lixingyun
> println(test2.name)
new lixingyun
> println(test2.getCount())
0
> println(test2.count)
0
// 这里执行会报错
> test2.setCount(1)
groovy.lang.MissingMethodException: No signature of method: Test2.setCount() is applicable for argument types: (Integer) values: [1]
如果没有创建类的属性而只有getter
和setter
方法,Groovy可以自动进行识别。
> class Person {
> String getName() {
> 'lixingyun'
> }
// 如果只有getter而没有setter,是可以读取属性的
> int getAge() { 19 }
// 但如果只有setter而没有getter,就无法读取属性
> void setGroovy(boolean groovy) {}
> }
> def p = new Person()
> println(p.name)
lixingyun
> println(p.age)
19
> p.groovy = true
// 这里执行时会报错
> println(p.groovy)
groovy.lang.MissingPropertyException: No such property: groovy for class: Person
另外,无论是字段还是属性,都可以通过构造器
+ 命名参数
的方式传值,否则会报错。
> class Person1 {
// 定义一个属性
> String name
// 定义一个字段
> private int age
> }
// 通过构造器 + 命名参数的方式传值
> def person1 = new Person1(name: 'lixingyun', age: 19)
> println person1.name
lixingyun
> println person1.age
19
> class Person2 {
> def name
> def age
> }
> def person2 = new Person2(name: 'wanglin', age: 20)
> println person2.name
wanglin
> println person2.age
20
所以对于属性来说。
如果没有
getter
和setter
方法,那么Groovy会自动创建。如果显式地创建了
getter
和setter
方法,Groovy就不会自动创建了。如果继承父类的时候将属性标注为
final
,子类就不会自动生成相应的getter
和setter
方法。
> class Parent {
> String name
> int age
> }
> final class Child extends Parent {
> def final weight
> }
> class User {
> String name
> int age
> String getName() {
> "my name is " + name
> }
> void setName(String name) {
> this.name = name + ".二世"
> }
> int getAge() {
> age * 2
> }
> void setAge(int age) {
> this.age = age + 1
> }
> }
> def parent = new Parent()
// 正常调用getter和setter方法
> parent.setName('lixingyun')
> parent.setAge(19)
> println(parent.getName())
lixingyun
> println(parent.getAge())
19
> def child = new Child()
// 没有标记为final的属性可以正常调用getter和setter方法
> child.setName('xiaoyan')
> child.setAge(2)
> println(child.getName())
lixingyun
> println(child.getAge())
2
// 标记为final的属性,没有自动创建的getter和setter方法,调用会报错
> child.setWeight(8)
groovy.lang.MissingMethodException: No signature of method: Child.setWeight() is applicable for argument types: (Integer) values: [8]
// 有了自定义的getter和setter方法,就不会创建默认的getter和setter方法
> def user = new User()
> user.setName('wanglin')
> user.setAge(18)
> println(user.getName())
my name is wanglin.二世
> println(user.getAge())
38
关于注解的说明
因为Groovy本身的定位是一种脚本语言,是与其他开发环境和工具(例如Flink、Clickhouse等)搭配起来工作的,它的脚本特性
> 开发语言特性
,所以Annotation
(注解
)就不必再了解了,脚本中也没多少使用注解的场景。
接口(trait)
Groovy除了有和Java一样的interface
(接口)关键字,还有Go和Scala中所具有的独特属性:trait
(特质)。
trait
可以被视为携带默认实现和状态的接口。
> trait Animal1 {
// trait可以添加抽象方法,如果有抽象方法那么就必须实现它的抽象方法
> abstract String something()
// trait必须提供默认实现
> String eat() { "eating ${something()}" }
> }
> interface Animal2 {
// interface不能提供默认实现
> String eat()
> }
> class Cat1 implements Animal1 {
> String something() { "fish" }
// 可以不显式实现eat()方法
> }
> class Dog1 implements Animal1 {
> String something() { "bone" }
> String eat() { "eating ${something()}" }
> }
> def cat1 = new Cat1()
> def dog1 = new Dog1()
> println(cat1.eat())
eating fish
> println(dog1.eat())
eating bone
> class Cat2 implements Animal2 {
// 接口中不能有抽象方法
// abstract String something()
// 必须显式实现eat()方法
> String eat() { "eating2" }
> }
> def cat2 = new Cat2()
> println(cat2.eat())
eating2
将trait
当作普通的接口一样使用,这是最简单的方式。
trait
可以有自己的私有方法,但这种私有方法只能从它自己内部调用,外部无法调用。
> trait Greeter {
// 从外部无法调用trait的私有方法
> private String greetingMessage() {
> 'from a private method!'
> }
> private String getMessage() {
// 内部可以调用
> greetingMessage()
> }
> String greet() {
// 内部可以调用
> getMessage()
> }
> }
> class GreetingMachine implements Greeter {}
> def g = new GreetingMachine()
> println(g.greet())
from a private method!
> try {
> println(g.greetingMessage())
> } catch (MissingMethodException e) {
> println "message is private in trait"
message is private in trait
> }
可以利用trait
的特性屏蔽
接口的方法(并不是真的屏蔽,而是让它实现接口,而其他的类就可以有了默认实现)。
> interface Named {
> String name()
> }
> trait Greetable implements Named {
> String greeting() { "Hello, ${name()}!" }
> }
> class Person implements Greetable {
> String name() { 'lixingyun' }
> }
> def person = new Person()
> println(person.greeting())
Hello, lixingyun!
> println(person instanceof Named)
true
> println(person instanceof Greetable)
true
this
的意义
> trait Person {
> String name
> def whoAmI() { this.name }
> }
> class Lixingyun implements Person {}
> def lixingyun = new Lixingyun()
> lixingyun.setName("Lixingyun")
> println(lixingyun.whoAmI())
Lixingyun
属性和字段
类可以通过构造器
+ 命名参数
的方式给属性或字段传值,但trait
就只能用这种方式给属性传值,用在字段上会报错。
> trait Animal {
> // 定义一个属性
> String name
> // 定义一个字段
> private int age
// 定义一个公共字段
> public String city
> int getAge() {
> age = 19
> }
> }
> class Person implements Animal {}
// 如果这样调用会报错
> def person = new Person(name: 'lixingyun', age: 18)
groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: age for class: Person
// 只能这样调用
> def person = new Person(name: 'lixingyun')
> println(person.name)
lixingyun
// 可以这样给公共字段赋值
> person.Animal__city = "wuhan"
// 可以这样获取
> println(person.Animal__city)
wuhan
> println(person.getAge())
19
多重继承(实现)
> trait FlyingAbility {
> String fly() { "flying" }
> String eating() { "eating bug" }
> String walking() { "walking happiness" }
> }
> trait SwimmingAbility {
> String swim() { "swimming" }
> String eating() { "eating vegetable" }
> String walking() { "walking running" }
> }
> // 实现多个trait
> class Duck implements FlyingAbility, SwimmingAbility {
// 覆盖默认的方法
> String fly() { "a flying duck" }
// 手动指定要实现哪一个trait的方法
> String walking() { FlyingAbility.super.walking() }
> }
> def duck = new Duck()
// 直接调用默认的方法
> println(duck.swim())
swimming
// 覆盖默认的方法
> println(duck.fly())
a flying duck
// 当实现多个trait中相同的方法时,默认以继承列表(implements FlyingAbility, SwimmingAbility...)中最后一个声明的trait的方法为准
> println(duck.eating())
eating vegetable
// 手动指定要实现哪一个trait的方法
> println(duck.walking())
walking happiness
如果多重继承中有相同的方法名时,
trait继承
和interface
一样,父子trait
之间的关系用继承(extends
)来表示。
> trait Named {
> String name
> }
// 继承自Named
> trait Person extends Named {
> String introduce() { "Hello, $name" }
> }
> class User implements Person {}
> def user = new User(name: 'lixingyun')
> println(user.introduce())
Hello, lixingyun
鸭子类型
所谓鸭子类型,指的是程序设计中的一种动态类型风格,它强调对象的语义不是由其继承自特定的类或实现特定的接口决定,而是由其当前的方法和属性集合决定。
换句话说,一只家禽是不是鸭子
不由它的名字决定,而是看它走路的样子,游泳的样子和发出的声音。如果这些都像鸭子,那么它就是鸭子。
> trait Duck {
// 返回一个swimming()方法
> String isDuck() { swimming() }
> }
> class Bird implements Duck {
// 通过methodMissing()来实现swimming()方法,直接输出方法的name为字符串
> String methodMissing(String name, args) {
> "${name}"
> }
> }
> def bird = new Bird()
// 当调用isDuck()方法的触发对swimming()方法的调用,而该方法会被methodMissing()处理
> println(bird.isDuck())
swimming
除了methodMissing()
,还有propertyMissing()
方法,此时实现类将继承trait
的行为。
> trait DynamicClass {
> private Map props = [:]
> def methodMissing(String name, args) {
> name.toUpperCase()
> }
> def propertyMissing(String name) {
> props.get(name)
> }
> void setProperty(String name, Object value) {
> props.put(name, value)
> }
> }
> class DynamicObject implements DynamicClass {
> String existingProperty = 'ok'
> static String existingMethod() { 'ok' }
> }
> def dynamic = new DynamicObject()
// existingProperty已经存在
> println(dynamic.existingProperty)
ok
// 并不存在identity属性
> println(dynamic.identity == null)
true
// 给一个不存在的属性赋值,触发propertyMissing()的执行
> dynamic.identity = '皇太子'
// // 触发propertyMissing()的执行
> println(dynamic.identity)
皇太子
> println(dynamic.existingMethod() == 'ok')
true
// 因为方法不存在,所以触发methodMissing()的执行:将方法名作为参数后输出结果
> println(dynamic.lixingyun())
LIXINGYUN
// 打印trait中的属性
> println(dynamic.properties)
[existingProperty:ok, DynamicClass__props:[identity:皇太子], class:class DynamicObject]
运行时实现
除了可以通过implements
关键字实现trait
,还能通过as
关键字或withtraits()
方法在运行时实现trait
。
> trait Bios {
> String active() { "all bios can active" }
> }
> trait Animal {
> String dead() { "all animal will be dead" }
> }
> class Person {
> String live() { 'living...' }
> }
> class Human{}
// 实现单个trait的继承
> def person = new Person() as Animal
> println(person.dead())
all animal will be dead
> println(person.live())
living...
// 实现多个trait的继承
> def human = new Human().withtraits(Bios, Animal)
> println(human.active())
all bios can active
> println(human.dead())
all animal will be dead
连锁行为
Groovy支持可堆叠特性,意思是:如果当前类无法处理消息,那么就将传递给另一个方法处理。
> interface MessageHandler {
> void on(String message, Map payload)
> }
> trait DefaultHandler implements MessageHandler {
> void on(String message, Map payload) {
> println "Received $message with payload $payload"
> }
> }
> trait LoggingHandler implements MessageHandler {
> void on(String message, Map payload) {
> println "Seeing $message with payload $payload"
> super.on(message, payload)
> }
> }
> class HandlerWithLogger implements DefaultHandler, LoggingHandler {}
> def loggingHandler = new HandlerWithLogger()
> // 因为LoggingHandler是列表中的最后一个,所以第一次调用的是LoggingHandler中的on()方法
> // 但因为调用了super.on(),所以在链条上的下一个trait将被调用,也就是DefaultHandler中的on()方法
> loggingHandler.on('lixingyun', [:])
Seeing lixingyun with payload [:]
Received lixingyun with payload [:]
可以把它搞得更复杂一点。
> interface MessageHandler {
> void on(String message, Map payload)
> }
> trait DefaultHandler implements MessageHandler {
> void on(String message, Map payload) {
> println "Received $message with payload $payload"
> }
> }
> trait LoggingHandler implements MessageHandler {
> void on(String message, Map payload) {
> println "Seeing $message with payload $payload"
> super.on(message, payload)
> }
> }
> trait SayHandler implements MessageHandler {
> void on(String message, Map payload) {
> if (message.startsWith("say")) {
> println "I say :“${message - 'say'} lixingyun”"
> } else {
> super.on(message, payload)
> }
> }
> }
> class Handler implements DefaultHandler, SayHandler, LoggingHandler {}
> def handler = new Handler()
> handler.on('lixingyun', [:])
Seeing lixingyun with payload [:]
Received lixingyun with payload [:]
> handler.on('sayHello', [:])
Seeing sayHello with payload [:]
I say :“Hello lixingyun”
将Groovy介绍到这种程度,在写脚本代码时已经是绰绰有余了,剩下的高级特性在99%的场景下基本用不到。
现在只剩下闭包需要了解一下了。
感谢支持
更多内容,请移步《超级个体》。