...
| Code Block |
|---|
import org.codehaus.groovy.runtime.typehandling.GroovyCastException
def v= 3 //variable v can hold any value
v= 'helicopter'
v= false
v= new StringBuffer()
v= null
int i= 15 //variable i can only hold integer values
i= 'A'
assert i == 65 //'A' casted to its integer value
//unable to cast boolean value to integer
try{ i= false; assert 0 }catch(e){ assert e in GroovyCastException }
Closure c= {it * 3} //variable c can only hold Closures
try{ c= false; assert 0 }catch(e){ assert e in GroovyCastException }
//unable to cast boolean value to Closure
StringBuffer s= new StringBuffer('morning')
//variable s can only hold StringBuffers
try{ s= { it * 5 }; assert 0 }catch(e){ assert e in GroovyCastException }
//unable to cast Closure value to StringBuffer
|
...
| Code Block |
|---|
import org.codehaus.groovy.runtime.typehandling.GroovyCastException
int i
def toTriple= {int n -> n * 3}
i= 5
assert toTriple(5) == 15
//a float is cast to an integer when assigning to a variable, but not when
//passing as a parameter...
i= 5.0f
try{ toTriple(5.0f); assert 0 }
catch(e){assert e.class in MissingMethodException}
//a String can't cast to an integer, either when assigning to a variable or
//passing as a parameter...
try{ i= 'abc'; assert 0 }
catch(e){assert e.class in GroovyCastException}
try{ toTriple('abc'); assert 0 }
catch(e){assert e.class in MissingMethodException}
|
...
| Code Block |
|---|
def c = { int[] args ->
args.toList().inject(0){ flo, it-> flo + it }
}
assert c( 5 ) == 5
assert c( 4, 2, 3 ) == 9
try{ c( 2, 'abc' ); assert 0 }catch(e){ assert e in MissingMethodException }
|
...
| Code Block |
|---|
def f(String s, int i){ ([s]*i).join(',') }
assert f('abc', 3) == 'abc,abc,abc'
def f(int n, int i){ "$n * $i" } //another function f defined with same
//number of but different types of parameters
assert f(4, 5) == '4 * 5'
assert f('a', 5) == 'a,a,a,a,a'
//correct function selected based on parameter types...
try{ f(4, 'x'); assert 0 }catch(e){ assert e in MissingMethodException }
//...or no method selected
|
...
| Code Block |
|---|
//static methods...
class A{
static f(String s, int i){ ([s]*i).join(',') }
static f(int n, int i){ "$n * $i" } //another method f defined with same
//number of but different types of parameters
}
assert A.f('abc', 3) == 'abc,abc,abc'
assert A.f(4, 5) == '4 * 5'
assert A.f('a', 5) == 'a,a,a,a,a'
//correct method selected based on parameter types...
try{ A.f(4, 'x'); assert 0 }catch(e){ assert e in MissingMethodException }
//...or no method selected
//instance methods...
class Counter{
def count = 0
def incr( String n ){ count += new Integer(n) }
def incr( int n ){ count += n }
}
def c= new Counter(count: 5)
c.incr(3)
c.incr('4')
try{ c.incr(2.5); assert 0 }catch(e){ assert e in MissingMethodException }
assert c.count == 12
|
We can statically type the return type from a method, just as we can from a function, both static and instance methods:
| Code Block |
|---|
class A{
static String f(){ 'abc' }
static int g(){ 2.4f }
byte h(){ 200i }
}
assert A.f() == 'abc'
assert A.g() == 2i
assert new A().h() == -56
|
Property getters and setters can accept and return any statically-typed value:
| Code Block |
|---|
class Counter{
def count= 0
void setCount(int n){ count= n*2 } //set the value to twice what's supplied
String getCount(){ 'count: '+ count }
//return the value as a String with 'count: ' prepended
}
def c= new Counter()
c.count= 23
assert c.count == 'count: 46'
|
A list can be cast to a class using that class's constructor:
| Code Block |
|---|
class A{
int x,y
A(x,y){ this.x=x; this.y=y } //2-arg constructor
String toString(){ "x: $x; y: $y" }
}
A a
a= [1,2] //2-element list causes 2-arg constructor of A to be called
assert assert a.class == A && a.toString() == 'x: 1; y: 2'
|
Statically-Typed Arrays
We can statically type an Object array variable:
| Code Block |
|---|
Object[] oa= new Object[2]
assert oa.class in Object[] && oa.size() == 2 && oa[0,1] == [null, null]
oa= 7 //if we assign another scalar value, it's wrapped into an array
assert oa.class in Object[] && oa.size() == 1 && oa[0] == 7
oa= [3, 5] //if we assign another collection value, it's cast to an array
assert oa.class in Object[] && oa.size() == 2 && oa[0,1] == [3, 5]
def map= ['a':4, 'b':8, 'c':12]
oa= map
assert oa.class in Object[] && oa.size() == 3
//it's cast to an array of MapEntrys
oa.each{ assert it.key in map.keySet() && it.value == map[it.key] }
|
...
| Code Block |
|---|
int[] ia ia= 7.5 assert ia.class in int[] && ia.size() == 1 && ia[0] == 7i //assigned value above cast to an integer array try{ ia= ['abc', 'def']; assert 0 }catch(e){ assert e in ClassCastException } //can't cast Strings to Integers |
...
| Code Block |
|---|
def a= new int[3]
assert a[0] == 0 && a[1] == 0 && a[2] == 0 //default value is 0
a[0]= 7.5
assert a[0] == 7i //assigned value in above line was cast to an integer
try{ a[1]= 'abc'; assert 0 }catch(e){ assert e in ClassCastException }
//can't cast String to an Integer
|
...
| Code Block |
|---|
//a scalar value is cascadingly wrapped by arrays... Object[][] ia ia= 7.5 assert ia in Object[][] && ia.size() == 1 && ia[0] in Object[] && ia[0].size() == 1 && ia[0][0] == 7.5 //a one-dimensional vector value is array-wrapped at the innermost level... ia= ['a', 'b', 'c'] assert ia in Object[][] && ia.size() == 3 && ia[0] in Object[] && ia[0].size() == 1 && ia[0][0] == 'a' && ia[1][0] == 'b' && ia[2][0] == 'c' |
Interfaces
Groovy enables a construct known as an interface, which classes can implement. We can test for implemented interfaces with the 'in' operator:
| Code Block |
|---|
class A{} //a standard class definition, though without any fields,
//properties, or methods
def a= new A()
assert a in A
interface X{}
class B implements X{} //a class can implement an interface
def b= new B()
assert b in B && b in X
//'in' tests for the class and for interfaces implemented
assert ! (a in X)
interface Y{}
interface Z{}
class C implements X, Y, Z{} //a class can implement more than one interface
def c= new C()
assert c in C && c in X && c in Y && c in Z
|
Interfaces can contain method declarations. Each declared method must be defined in implementing classes:
| Code Block |
|---|
interface X{
String sayPies(int i)
}
class A implements X{
String sayPies(int n){ "There are $n pies!" } //sayPies(int) in X defined
String sayBirds(int n){ "There are $n birds!" }
}
def a= new A()
assert a.sayPies(24) == 'There are 24 pies!'
//class B implements X{}
//a compile error when uncommented: sayPies(int) must be implemented
//these each give a compile error when uncommented...
//class C implements X{ String sayPies(float n){ "$n" } } //wrong parameter type
//class D implements X{ Object sayPies(int n){ "$n" } } //wrong return type
|
An interface can also be composed of other interfaces, using the 'extends' keyword:
| Code Block |
|---|
interface X{
def x1(int i)
def x2()
}
interface Y{
def x1(int i)
def y()
}
interface Z extends X, Y{ }
//it's OK if a method, here x1(int), is in more than one interface
class A implements Z{
def x1(int i){ i }
def x2(){ 2 }
def y(){ 3 }
}
assert new A().x1( 1 ) == 1
|
We can implement an interface with map syntax:
| Code Block |
|---|
interface X{
int echo(int i)
def sayTarts(int i)
String sayPies(int i)
}
def a= [
echo: {n-> n},
sayTarts: {n-> "There are $n tarts!"},
sayPies: {n-> "There are $n pies!" as String},
//explicit cast from GString to String required here
] as X
assert a.echo(12) == 12
assert a.sayTarts(18) == 'There are 18 tarts!'
assert a.sayPies(24) == 'There are 24 pies!'
//when interface has only one method, we don't need a map, but can assign and
//cast the closure directly...
interface Y{
def sayCakes(int i)
}
def b= {n-> "There are $n cakes!"} as Y
assert b.sayCakes(36) == 'There are 36 cakes!'
|
Interfaces can also have fields, but their values can't be changed:
| Code Block |
|---|
interface X{
int status= 1 //constant field on interface
int changeCounter()
}
class A implements X{
int counter= 1 //updateable field on class itself
int changeCounter(){ counter++ }
int changeStatus(){ status++ }
}
def a= new A()
a.changeCounter() //field 'counter' can be changed...
try{ a.changeStatus(); assert 0 }catch(e){ assert e in IllegalAccessException }
//...but field 'status' can't
|
Static Typing with Interfaces
We can use an interface, instead of a class, to statically type a variable, field, parameter, etc:
...
| Code Block |
|---|
class A implements Serializable{}
//Serializable interface marks class A via the 'in' operator
assert A in Serializable
//class B implements Closeable{}
//compile error when uncommented: method close() must be defined
class C implements Closeable{
void close(){}
//Closeable interface signifies that this close() method is present
}
def c= new C()
if( c in Closeable ) c.close()
|
We've met the Comparator interface in the tutorial on Lists and SetsCollections, and the Iterator interface in the tutorial on Control Structures.
...
| Code Block |
|---|
import org.codehaus.groovy.runtime.typehandling.GroovyCastException List list1= new ArrayList(), list2= [], list3= new LinkedList() assert list1 in ArrayList && list2 in ArrayList && list3 in LinkedList Set set1= new HashSet(), set2= list1, set3= list3, set4= new TreeSet() assert [set1, set2, set3].every{ it in HashSet } && set4 in TreeSet SortedSet ss1= new TreeSet(), ss2 try{ ss2= new HashSet(); assert 0 }catch(e){ assert e in GroovyCastException } Map map1= new HashMap(), map2= new TreeMap(), map3= [:], map4= new LinkedHashMap() assert map1 in HashMap && map2 in TreeMap && [map3, map4].every{ it in LinkedHashMap } SortedMap sm1= new TreeMap(), sm2 try{ sm2= new HashMap(); assert 0 }catch(e){ assert e in GroovyCastException } |