We can restrict the types of values a variable may hold by specifying some restricting class instead of 'def':
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
When we assign values not of a variable's type to the variable, sometimes it may be 'cast' to the type, other times an exception is thrown:
import org.codehaus.groovy.runtime.typehandling.GroovyCastException int i i= 45L; assert i == 45i i= 45.1f; assert i == 45i try{ i= '42'; assert 0 }catch(e){assert e in GroovyCastException} try{ i= false; assert 0 }catch(e){assert e in GroovyCastException} //long similar to int byte by by= 200i; assert by == -56 //short similar to byte float f f= 123i; assert f == 123.0f try{ f= '42.1'; assert 0 }catch(e){assert e in GroovyCastException} //double similar to float BigInteger bi bi= 42L; assert bi == 42g try{ bi= '421'; assert 0 }catch(e){assert e in GroovyCastException} BigDecimal bd bd= 42.1f; assert bd == 42.1g try{ bd= '4.21'; assert 0 }catch(e){assert e in GroovyCastException} boolean b b= 0; assert ! b b= 1i; assert b b= 1g; assert b b= 1.1g; assert b b= 1.1f; assert b b= ''; assert ! b b= 'a'; assert b b= 'abc'; assert b b= null; assert ! b char c c= 'a'; assert c == ('a' as char) try{ c= 'abc'; assert 0 }catch(e){assert e in GroovyCastException} String s s= 42i; assert s == '42' s= 42L; assert s == '42' s= 42g; assert s == '42' s= 42.1g; assert s == '42.1' s= 42.100g; assert s == '42.100' s= 42.1f; assert s == '42.1' StringBuffer sb try{ sb= 'abc'; assert 0 }catch(e){ assert e in GroovyCastException }
We can statically type Closure parameters. The casting is more restrictive than for assigning to variables:
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}
We can also statically type the variable-numbered parameters in a closure:
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 }
We can statically type function parameters:
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
We can statically type the return type from a function. Casting a returned value of a different type follows the same rules as for assigning to variables:
String f(){ 'abc' } assert f() == 'abc' int g(){ 2.4f } assert g() == 2i
We can statically type method parameters just like we do with function parameters, including selecting a method based on its parameter types, for both static methods and instance methods:
//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:
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:
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:
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 a.class == A && a.toString() == 'x: 1; y: 2'
Statically-Typed Arrays
We can statically type an Object array variable:
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] }
We can statically type a variable not only as an array, but as a certain type of array:
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
We can instead statically type each array element:
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
Statically typing both the variable and each element allows both array assignments and element assignments to be cast or disallowed:
int[] ia= new int[3] ia[0]= 7.5 assert ia[0] == 7i ia= 7.5 assert ia.class in int[] && ia.size() == 1 && ia[0] == 7i
A multidimensional array type casts its assigned value in various ways:
//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:
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:
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:
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:
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:
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:
import org.codehaus.groovy.runtime.typehandling.GroovyCastException interface X{} class A implements X{} class B{} X a a= new A() try{ a= new B(); assert 0 }catch(e){ assert e in GroovyCastException }
Groovy supplies many interfaces we can use to statically type variables. Some have no methods, eg, Serializable, while others have one or more:
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 Collections, and the Iterator interface in the tutorial on Control Structures.
Many Groovy classes we've met implement interfaces, which we can use to statically type variables:
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 }






