|
Groovy/FAQ/Классы, Объекты и связи
Материал из Wiki.crossplatform.ru
[править] 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
//----------------------------------------------------------------------------------
|