//---------------------------------------------------------------------------------- // Classes and objects in Groovy are rather straigthforward class Person { // Class variables (also called static attributes) are prefixed by the keyword static static personCounter=0 def age, name // this creates setter and getter methods private alive // object constructor Person(age, name, alive = true) { // Default arg like in C++ this.age = age this.name = name this.alive = alive personCounter += 1 // There is a '++' operator in Groovy but using += is often clearer. } def die() { alive = false println "$name has died at the age of $age." alive } def kill(anotherPerson) { println "$name is killing $anotherPerson.name." anotherPerson.die() } // methods used as queries generally start with is, are, will or can // usually have the '?' suffix def isStillAlive() { alive } def getYearOfBirth() { new Date().year - age } // Class method (also called static method) static getNumberOfPeople() { // accessors often start with get // in which case you can call it like // it was a field (without the get) personCounter } } // Using the class: // Create objects of class Person lecter = new Person(47, 'Hannibal') starling = new Person(29, 'Clarice', true) pazzi = new Person(40, 'Rinaldo', true) // Calling a class method println "There are $Person.numberOfPeople Person objects." println "$pazzi.name is ${pazzi.alive ? 'alive' : 'dead'}." lecter.kill(pazzi) println "$pazzi.name is ${pazzi.isStillAlive() ? 'alive' : 'dead'}." println "$starling.name was born in $starling.yearOfBirth." //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // Classes may have no constructor. class MyClass { } aValidButNotVeryUsefulObject = new MyClass() // If no explicit constructor is given a default implicit // one which supports named parameters is provided. class MyClass2 { def start = new Date() def age = 0 } println new MyClass2(age:4).age // => 4 // One or more explicit constructors may also be provided class MyClass3 { def start def age MyClass3(date, age) { start = date this.age = age } } println new MyClass3(new Date(), 20).age // => 20 //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // Objects are destroyed by the JVM garbage collector. // The time of destroying is not predicated but left up to the JVM. // There is no direct support for destructor. There is a courtesy // method called finalize() which the JVM may call when disposing // an object. If you need to free resources for an object, like // closing a socket or killing a spawned subprocess, you should do // it explicitly - perhaps by supporting your own lifecycle methods // on your class, e.g. close(). class MyClass4{ void finalize() { println "Object [internal id=${hashCode()}] is dying at ${new Date()}" } } // test code 50.times { new MyClass4() } 20.times { System.gc() } // => (between 0 and 50 lines similar to below) // Object [internal id=10884088] is dying at Wed Jan 10 16:33:33 EST 2007 // Object [internal id=6131844] is dying at Wed Jan 10 16:33:33 EST 2007 // Object [internal id=12245160] is dying at Wed Jan 10 16:33:33 EST 2007 // ... //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // You can write getter and setter methods explicitly as shown below. // One convention is to use set and get at the start of method names. class Person2 { private name def getName() { name } def setName(name) { this.name = name } } // You can also just use def which auto defines default getters and setters. class Person3 { def age, name } // Any variables marked as final will only have a default getter. // You can also write an explicit getter. For a write-only variable // just write only a setter. class Person4 { final age // getter only def name // getter and setter private color // private def setColor() { this.color = color } // setter only } //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- class Person5 { // Class variables (also called static attributes) are prefixed by the keyword static static personCounter = 0 static getPopulation() { personCounter } Person5() { personCounter += 1 } void finalize() { personCounter -= 1 } } people = [] 10.times { people += new Person5() } println "There are ${Person5.population} people alive" // => There are 10 people alive alpha = new FixedArray() println "Bound on alpha is $alpha.maxBounds" beta = new FixedArray() beta.maxBounds = 50 println "Bound on alpha is $alpha.maxBounds" class FixedArray { static maxBounds = 100 def getMaxBounds() { maxBounds } def setMaxBounds(value) { maxBounds = value } } // => // Bound on alpha is 100 // Bound on alpha is 50 //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // The fields of this struct-like class are dynamically typed class DynamicPerson { def name, age, peers } p = new DynamicPerson() p.name = "Jason Smythe" p.age = 13 p.peers = ["Wilbur", "Ralph", "Fred"] p.setPeers(["Wilbur", "Ralph", "Fred"]) // alternative using implicit setter p["peers"] = ["Wilbur", "Ralph", "Fred"] // alternative access using name of field println "At age $p.age, $p.name's first friend is ${p.peers[0]}" // => At age 13, Jason Smythe's first friend is Wilbur // The fields of this struct-like class are statically typed class StaticPerson { String name; int age; List peers } p = new StaticPerson(name:'Jason', age:14, peers:['Fred','Wilbur','Ralph']) println "At age $p.age, $p.name's first friend is ${p.peers[0]}" // => At age 14, Jason's first friend is Fred class Family { def head, address, members } folks = new Family(head:new DynamicPerson(name:'John',age:34)) // supply of own accessor method for the struct for error checking class ValidatingPerson { private age def printAge() { println 'Age=' + age } def setAge(value) { if (!(value instanceof Integer)) throw new IllegalArgumentException("Argument '${value}' isn't an Integer") if (value > 150) throw new IllegalArgumentException("Age ${value} is unreasonable") age = value } } // test ValidatingPerson def tryCreate(arg) { try { new ValidatingPerson(age:arg).printAge() } catch (Exception ex) { println ex.message } } tryCreate(20) tryCreate('Youngish') tryCreate(200) // => // Age=20 // Argument 'Youngish' isn't an Integer // Age 200 is unreasonable //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // Groovy objects are (loosely speaking) extended Java objects. // Java's Object class provides a clone() method. The conventions of // clone() are that if I say a = b.clone() then a and b should be // different objects with the same type and value. Java doesn't // enforce a class to implement a clone() method at all let alone // require that one has to meet these conventions. Classes which // do support clone() should implement the Cloneable interface and // implement an equals() method. // Groovy follows Java's conventions for clone(). class A implements Cloneable { def name boolean equals(Object other) { other instanceof A && this.name == other.name } } ob1 = new A(name:'My named thing') ob2 = ob1.clone() assert !ob1.is(ob2) assert ob1.class == ob2.class assert ob2.name == ob1.name assert ob1 == ob2 //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- class CanFlicker { def flicker(arg) { return arg * 2 } } methname = 'flicker' assert new CanFlicker().invokeMethod(methname, 10) == 20 assert new CanFlicker()."$methname"(10) == 20 class NumberEcho { def one() { 1 } def two() { 2 } def three() { 3 } } obj = new NumberEcho() // call methods on the object, by name assert ['one', 'two', 'three', 'two', 'one'].collect{ obj."$it"() }.join() == '12321' //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // Groovy can work with Groovy objects which inherit from a common base // class called GroovyObject or Java objects which inherit from Object. // the class of the object assert 'a string'.class == java.lang.String // Groovy classes are actually objects of class Class and they // respond to methods defined in the Class class as well assert 'a string'.class.class == java.lang.Class assert !'a string'.class.isArray() // ask an object whether it is an instance of particular class n = 4.7f println (n instanceof Integer) // false println (n instanceof Float) // true println (n instanceof Double) // false println (n instanceof String) // false println (n instanceof StaticPerson) // false // ask if a class or interface is either the same as, or is a // superclass or superinterface of another class println n.class.isAssignableFrom(Float.class) // true println n.class.isAssignableFrom(String.class) // false // can a Groovy object respond to a particular method? assert new CanFlicker().metaClass.methods*.name.contains('flicker') class POGO{} println (obj.metaClass.methods*.name - new POGO().metaClass.methods*.name) // => ["one", "two", "three"] //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // Most classes in Groovy are inheritable class Person6{ def age, name } dude = new Person6(name:'Jason', age:23) println "$dude.name is age $dude.age." // Inheriting from Person class Employee extends Person6 { def salary } empl = new Employee(name:'Jason', age:23, salary:200) println "$empl.name is age $empl.age and has salary $empl.salary." // Many built-in class can be inherited the same way class WierdList extends ArrayList { def size() { // size method in this class is overridden super.size() * 2 } } a = new WierdList() a.add('dog') a.add('cat') println a.size() // => 4 //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- class Person7 { def firstname, surname; def getName(){ firstname + ' ' + surname } } class Employee2 extends Person7 { def employeeId def getName(){ 'Employee Number ' + employeeId } def getRealName(){ super.getName() } } p = new Person7(firstname:'Jason', surname:'Smythe') println p.name // => // Jason Smythe e = new Employee2(firstname:'Jason', surname:'Smythe', employeeId:12349876) println e.name println e.realName // => // Employee Number 12349876 // Jason Smythe //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // Groovy's built in constructor and auto getter/setter features // give you the required functionalty already but you could also // override invokeMethod() for trickier scenarios. class Person8 { def name, age, peers, parent def newChild(args) { new Person8(parent:this, *:args) } } dad = new Person8(name:'Jason', age:23) kid = dad.newChild(name:'Rachel', age:2) println "Kid's parent is ${kid.parent.name}" // => Kid's parent is Jason // additional fields ... class Employee3 extends Person8 { def salary, boss } //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // Fields marked as private in Groovy can't be trampled by another class in // the class hierarchy class Parent { private name // my child's name def setChildName(value) { name = value } def getChildName() { name } } class GrandParent extends Parent { private name // my grandchild's name def setgrandChildName(value) { name = value } def getGrandChildName() { name } } g = new GrandParent() g.childName = 'Jason' g.grandChildName = 'Rachel' println g.childName // => Jason println g.grandChildName // => Rachel //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // The JVM garbage collector copes with circular structures. // You can test it with this code: class Person9 { def friend void finalize() { println "Object [internal id=${hashCode()}] is dying at ${new Date()}" } } def makeSomeFriends() { def first = new Person9() def second = new Person9(friend:first) def third = new Person9(friend:second) def fourth = new Person9(friend:third) def fifth = new Person9(friend:fourth) first.friend = fifth } makeSomeFriends() 100.times{ System.gc() } // => // Object [internal id=24478976] is dying at Tue Jan 09 22:24:31 EST 2007 // Object [internal id=32853087] is dying at Tue Jan 09 22:24:31 EST 2007 // Object [internal id=23664622] is dying at Tue Jan 09 22:24:31 EST 2007 // Object [internal id=10630672] is dying at Tue Jan 09 22:24:31 EST 2007 // Object [internal id=25921812] is dying at Tue Jan 09 22:24:31 EST 2007 //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // Groovy provides numerous methods which are automatically associated with // symbol operators, e.g. here is '<=>' which is associated with compareTo() // Suppose we have a class with a compareTo operator, such as: class Person10 implements Comparable { def firstname, initial, surname Person10(f,i,s) { firstname = f; initial = i; surname = s } int compareTo(other) { firstname <=> other.firstname } } a = new Person10('James', 'T', 'Kirk') b = new Person10('Samuel', 'L', 'Jackson') println a <=> b // => -1 // we can override the existing Person10's <=> operator as below // so that now comparisons are made using the middle initial // instead of the fisrtname: class Person11 extends Person10 { Person11(f,i,s) { super(f,i,s) } int compareTo(other) { initial <=> other.initial } } a = new Person11('James', 'T', 'Kirk') b = new Person11('Samuel', 'L', 'Jackson') println a <=> b // => 1 // we could also in general use Groovy's categories to extend class functionality. // There is no way to directly overload the '""' (stringify) // operator in Groovy. However, by convention, classes which // can reasonably be converted to a String will define a // 'toString()' method as in the TimeNumber class defined below. // The 'println' method will automatcally call an object's // 'toString()' method as is demonstrated below. Furthermore, // an object of that class can be used most any place where the // interpreter is looking for a String value. //--------------------------------------- // NOTE: Groovy has various built-in Time/Date/Calendar classes // which would usually be used to manipulate time objects, the // following is supplied for educational purposes to demonstrate // operator overloading. class TimeNumber { def h, m, s TimeNumber(hour, min, sec) { h = hour; m = min; s = sec } def toDigits(s) { s.toString().padLeft(2, '0') } String toString() { return toDigits(h) + ':' + toDigits(m) + ':' + toDigits(s) } def plus(other) { s = s + other.s m = m + other.m h = h + other.h if (s >= 60) { s %= 60 m += 1 } if (m >= 60) { m %= 60 h += 1 } return new TimeNumber(h, m, s) } } t1 = new TimeNumber(0, 58, 59) sec = new TimeNumber(0, 0, 1) min = new TimeNumber(0, 1, 0) println t1 + sec + min + min //----------------------------- // StrNum class example: Groovy's builtin String class already has the // capabilities outlined in StrNum Perl example, however the '*' operator // on Groovy's String class acts differently: It creates a string which // is the original string repeated N times. // // Using Groovy's String class as is in this example: x = "Red"; y = "Black" z = x+y r = z*3 // r is "RedBlackRedBlackRedBlack" println "values are $x, $y, $z, and $r" println "$x is ${x < y ? 'LT' : 'GE'} $y" // prints: // values are Red, Black, RedBlack, and RedBlackRedBlackRedBlack // Red is GE Black //----------------------------- class FixNum { def REGEX = /(\.\d*)/ static final DEFAULT_PLACES = 0 def float value def int places FixNum(value) { initValue(value) def m = value.toString() =~ REGEX if (m) places = m[0][1].size() - 1 else places = DEFAULT_PLACES } FixNum(value, places) { initValue(value) this.places = places } private initValue(value) { this.value = value } def plus(other) { new FixNum(value + other.value, [places, other.places].max()) } def multiply(other) { new FixNum(value * other.value, [places, other.places].max()) } def div(other) { println "DEUG: Divide = ${value/other.value}" def result = new FixNum(value/other.value) result.places = [places,other.places].max() result } String toString() { //m = value.toString() =~ /(\d)/ + REGEX String.format("STR%s: %.${places}f", [this.class.name, value as float] as Object[]) } } x = new FixNum(40) y = new FixNum(12, 0) println "sum of $x and $y is ${x+y}" println "product of $x and $y is ${x*y}" z = x/y println "$z has $z.places places" z.places = 2 println "$z now has $z.places places" println "div of $x by $y is $z" println "square of that is ${z*z}" // => // sum of STRFixNum: 40 and STRFixNum: 12 is STRFixNum: 52 // product of STRFixNum: 40 and STRFixNum: 12 is STRFixNum: 480 // DEUG: Divide = 3.3333333333333335 // STRFixNum: 3 has 0 places // STRFixNum: 3.33 now has 2 places // div of STRFixNum: 40 by STRFixNum: 12 is STRFixNum: 3.33 // square of that is STRFixNum: 11.11 //---------------------------------------------------------------------------------- |
//---------------------------------------------------------------------------------- // Groovy doesn't use the tie terminology but you can achieve // similar results with Groovy's metaprogramming facilities class ValueRing { private values def add(value) { values.add(0, value) } def next() { def head = values[0] values = values[1..-1] + head return head } } ring = new ValueRing(values:['red', 'blue']) def getColor() { ring.next() } void setProperty(String n, v) { if (n == 'color') { ring.add(v); return } super.setProperty(n,v) } println "$color $color $color $color $color $color" // => red blue red blue red blue color = 'green' println "$color $color $color $color $color $color" // => green red blue green red blue // Groovy doesn't have the $_ implicit variable so we can't show an // example that gets rid of it. We can however show an example of how // you could add in a simplified version of that facility into Groovy. // We use Groovy's metaProgramming facilities. We execute our script // in a new GroovyShell so that we don't affect subsequent examples. // script: x = 3 println "$_" y = 'cat' * x println "$_" // metaUnderscore: void setProperty(String n, v) { super.setProperty('_',v) super.setProperty(n,v) } new GroovyShell().evaluate(metaUnderscore + script) // => // 3 // catcatcat // We can get a little bit fancier by making an UnderscoreAware class // that wraps up some of this functionality. This is not recommended // as good Groovy style but mimicks the $_ behaviour in a sinple way. class UnderscoreAware implements GroovyInterceptable { private _saved void setProperty(String n, v) { _saved = v this.metaClass.setProperty(this, n, v) } def getProperty(String n) { if (n == '_') return _saved this.metaClass.getProperty(this, n) } def invokeMethod(String name, Object args) { if (name.startsWith('print') && args.size() == 0) args = [_saved] as Object[] this.metaClass.invokeMethod(this, name, args) } } class PerlishClass extends UnderscoreAware { private _age def setAge(age){ _age = age } def getAge(){ _age } def test() { age = 25 println "$_" // explicit $_ supported age++ println() // implicit $_ will be injected } } def x = new PerlishClass() x.test() // => // 25 // 26 // Autoappending hash: class AutoMap extends HashMap { void setProperty(String name, v) { if (containsKey(name)) { put(name, get(name) + v) } else { put(name, [v]) } } } m = new AutoMap() m.beer = 'guinness' m.food = 'potatoes' m.food = 'peas' println m // => ["food":["potatoes", "peas"], "beer":["guinness"]] // Case-Insensitive Hash: class FoldedMap extends HashMap { void setProperty(String name, v) { put(name.toLowerCase(), v) } def getProperty(String name) { get(name.toLowerCase()) } } tab = new FoldedMap() tab.VILLAIN = 'big ' tab.herOine = 'red riding hood' tab.villain += 'bad wolf' println tab // => ["heroine":"red riding hood", "villain":"big bad wolf"] // Hash That "Allows Look-Ups by Key or Value": class RevMap extends HashMap { void setProperty(String n, v) { put(n,v); put(v,n) } void remove(n) { super.remove(get(n)); super.remove(n) } } rev = new RevMap() rev.Rojo = 'Red' rev.Azul = 'Blue' rev.Verde = 'Green' rev.EVIL = [ "No way!", "Way!!" ] rev.remove('Red') rev.remove('Azul') println rev // => // [["No way!", "Way!!"]:"EVIL", "EVIL":["No way!", "Way!!"], "Verde":"Green", "Green":"Verde"] // Infinite loop scenario: // def x(n) { x(++n) }; x(0) // => Caught: java.lang.StackOverflowError // Multiple Strrams scenario: class MultiStream extends PrintStream { def streams MultiStream(List streams) { super(streams[0]) this.streams = streams } def println(String x) { streams.each{ it.println(x) } } } tee = new MultiStream([System.out, System.err]) tee.println ('This goes two places') // => // This goes two places // This goes two places //---------------------------------------------------------------------------------- |