Groovy/FAQ/Классы, Объекты и связи

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Groovy ·

Содержание

[править] Introduction

//----------------------------------------------------------------------------------
// 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."
//----------------------------------------------------------------------------------

[править] Constructing an Object

//----------------------------------------------------------------------------------
// 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
//----------------------------------------------------------------------------------

[править] Destroying an Object

//----------------------------------------------------------------------------------
// 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
// ...
//----------------------------------------------------------------------------------

[править] Managing Instance Data

//----------------------------------------------------------------------------------
// 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
 
}
//----------------------------------------------------------------------------------

[править] Managing Class Data

//----------------------------------------------------------------------------------
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
//----------------------------------------------------------------------------------

[править] Using Classes as Structs

//----------------------------------------------------------------------------------
// 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
//----------------------------------------------------------------------------------

[править] Cloning Objects

//----------------------------------------------------------------------------------
// 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
//----------------------------------------------------------------------------------

[править] Calling Methods Indirectly

//----------------------------------------------------------------------------------
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'
 
//----------------------------------------------------------------------------------

[править] Determining Subclass Membership

//----------------------------------------------------------------------------------
// 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"]
//----------------------------------------------------------------------------------

[править] Writing an Inheritable Class

//----------------------------------------------------------------------------------
// 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
//----------------------------------------------------------------------------------

[править] Accessing Overridden Methods

//----------------------------------------------------------------------------------
 
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
//----------------------------------------------------------------------------------

[править] Generating Attribute Methods Using AUTOLOAD

//----------------------------------------------------------------------------------
// 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 }
 
//----------------------------------------------------------------------------------

[править] Solving the Data Inheritance Problem

//----------------------------------------------------------------------------------
// 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
//----------------------------------------------------------------------------------

[править] Coping with Circular Data Structures

//----------------------------------------------------------------------------------
// 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
//----------------------------------------------------------------------------------

[править] Overloading Operators

//----------------------------------------------------------------------------------
// 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
//----------------------------------------------------------------------------------

[править] Creating Magic Variables with tie

//----------------------------------------------------------------------------------
// 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
//----------------------------------------------------------------------------------