Monday, February 12, 2018

Groovy: Advanced Concepts: Type Systems

Groovy implements a comprehensive type system that allows developers to define items as specific types and convert these items into other types as necessary.  The Groovy type system implements inclusion polymorphism, overloading and type conversions.  Groovy also supports an equivalent to parameterized types via its generics structure.

Inclusion Polymorphism

Groovy implements inclusion polymorphism through classes and subclasses instead of types and subtypes.  When defining a class, as in Java, the developer defines the variables in the class and the methods available in the class.  A developer can then create a second class that extends that class, creating a subclass.
The subclass inherits access to all the protected and public methods and variables provided by the parent class.  It can also be used anywhere the parent class can be used.
abstract class Shape
{
    private int x, y
   
    public int getX() { return x }
    public int getY() { return y }
 
    public void setX(int x) { this.x = x }
    public void setY(int y) { this.y = y }
   
    abstract void draw()
}
 
class Circle extends Shape
{
    private long radius
    
    void draw()
    {
        println "I drew a circle @ ${x},${y} "+
           "with radius ${radius}"
    }
}
 
class Test
{
    static void main(String[] args)
    {
        // Create instance of Circle
        Circle c = new Circle()
        c.radius = 1.4
        // Access the methods from Shape
        c.x = 1
        c.y = 5
 
        // Assign c to a variable of type
        // Shape which works because Circle
        // is a sub class of Shape
        Shape s = c
        s.draw()
    }
}
The above example defines two classes: Shape and Circle.  Shape is an abstract class, meaning it forms a prototype or base class for any subclasses, but cannot be directly instantiated.  This can be considered a partially implemented class.  Like an interface, it defines methods that need to be implemented by any class extending this abstract class.  Unlike an interface, an abstract class may include methods and variables.
In addition to the Java class and interface mechanism, Groovy adds an additional set of capabilities through its traits mechanism.  This mechanism allows a class to extend one or more traits, which adds methods and fields.  This is similar to how JavaScript allows for extending a prototype or existing object with additional fields and methods.  In actuality, traits are defined in code somewhere between an interface and a class.
trait FlyingAbility
{
    String fly() { "I'm flying!" }
}
 
class Bird implements FlyingAbility {}
def b = new Bird()         
assert b.fly() == "I'm flying!"
Groovy allows for implementing multiple traits in a given class, resulting in an equivalent of multiple inheritance in C++.  In an instance where a class implements multiple traits with the same method, the last declared trait will be executed.
trait A
{
    String exec() { 'A' }
}
 
trait B
{
    String exec() { 'B' }
}
 
// When called, the exec command will return the value of
// B.exec() because it is listed last
class C implements A, B {}

Parameterized Types

Groovy supports Java’s generics structure which can be likened to a parameterized type.  For a further description of Java generics, see Generic Abstraction.

Overloading

Groovy supports method overloading, allowing multiple definitions for a method of a given name.  Overloaded methods are differentiated by the parameters passed into the method.  The following example shows the draw method is overloaded with two implementations: one that takes no parameters and one that takes a single int parameter.  Depending on which method is invoked, the class will present two different outcomes.
class Circle
{
    private int x, y
    private float radius
   
    void draw()
    {
        println "I drew a circle @ ${x},${y} "+
           "with radius ${radius}"
    }
   
    void draw(int scale)
    {
        int x = this.x * scale
        int y = this.y * scale
        float radius = this.radius * scale
   
        println "I drew a circle @ ${x},${y} "+
           "with radius ${radius}"
    }
}
Unlike Java, Groovy also offers the ability to overload operators as well as providing method level overloading.

Type Conversions

Groovy supports converting one type into another.  The underlying Java language is strongly typed, meaning that it can define a variable as a specific primitive or class.  Once a variable has been defined, Java allows for converting that variable from one type to another, where applicable, via both casting and coercion.

Casting

Casting instructs the JRE to treat or convert a given variable as a different type of object.  This is accomplished by prefixing a variable or method with the class type to cast to.  At compile time, the compiler will attempt to determine if the cast is valid for the combination of variable/return and target variable.  For example, if the target variable is a parent class for the source variable or method, then the compiler will allow it because any subclass of the target variable will be compatible.  If the target variable is a subclass of the return, then the compiler will allow it and defer to runtime checks.  If the cast is not possible (such as Integer to String), the Java compiler will return an error.
class CastExample
{
    static void main(String[] args)
    {
        // Compile time-check
        Parent p = getChild();
        // Runtime check
        Child c = (Child)getParent();
        // Invalid cast
        Child c = new Object()
    }
 
    static Parent getParent()
    {
        // Child is cast to Parent on return
        return new Child();
    }
 
    static Child getChild()
    {
        return new Child();
    }
}
 
class Parent
{
    // No implementation for this example  
}
 
class Child extends Parent
{
    // No implementation for this example
}
Groovy adds an additional set of explicit conversions though the as <type> instruction.
String s = “42”
int i = s as Integer
This instruction tells the interpreter how to perform the conversion, eliminating any ambiguity that can arise from the Groovy automatic conversion system.

Coercion

In addition to explicit conversions via casts, Groovy also supports implicit casts via coercion.  Within primitive types, Java allows coercing values of storage to be converted into variables of higher storage.  For example, Java can automatically convert a short to an int to a long.  In each case, the second type stores more bits than the source type, allowing the JRE to represent the same value.
short s = 10
int i = s
long i = s
Groovy can also inherits Java’s implicit object to string conversion.  When any Object is assigned to a String variable, Java automatically calls the toString() method that is defined in the base Object class that all Java classes subclass.  This allows Java to get a string representation of any object, even if it’s just the class name and hashcode.

No comments: