Generics with custom containers

Although Gosu generics are often used with collections and lists, there is no requirement to use generics only with Collection and List interfaces. Any class that represents a container for other objects can use Gosu generics to define the type of items in the container.

Abstract example

Suppose you need to store key-value maps. You can define a class that uses the Object class to define two types of object:

class Mymapping { 
  function put( key : Object, value : Object) {...} 
  function get( key : Object) : Object {...} 
}

Alternatively, you can use generics to define the class:

class Mymapping<K,V> { 
  function put( key : K, value : V) {...} 
  function get( key : K) : V {...} 
}

This class enforces strongly typed values at compile time. In the following code, the theValue variable is strongly typed at compile time as Integer.

var myMap = new Mymapping<String, Integer>
myMap.put("ABC", 29)

var theValue = myMap.get("ABC")

Real-world example

Consider an application that tracks the construction of vehicles in multiple factories. You can represent cars with Car objects, trucks with Truck objects, and vans with Van objects. All these classes derive from a root class Vehicle.

Your application uses a custom container object of type FactoryGroup that does not extend a collection class. For this example, assume that each factory only constructs one type of vehicle. A FactoryGroup object can contain multiple Car objects, or multiple Truck objects, or multiple Van objects.

You need an API to work with all of the following types:
  • A FactoryGroup containing one or more Car objects
  • A FactoryGroup containing one or more Truck objects
  • A FactoryGroup containing one or more Van objects
You can use generics to parameterize the definition of the class by using the following syntax:
public class FactoryGroup<T>
You can then represent the specific factory groups by using the syntax:
  • FactoryGroup<Car>
  • FactoryGroup<Truck>
  • FactoryGroup<Van>

Consider a method in your application that returns all vehicles in the last step of a multistep manufacturing process. The method definition is like the following line:

public function getLastStepVehicles(groupofvehicles : FactoryGroup<T>) : FactoryGroup<T>

By using generics, the method supports all types of FactoryGroup objects. Because the letter T appears more than once in the method signature, this syntax defines relationships between the parameter and the return value. The method getLastStepVehicles takes one argument that is a factory group containing any one vehicle type. The method returns another factory group that is guaranteed to contain the same type of vehicle.

Alternatively, you could define your method with a bounded wildcard specification for the type:

public function getLastStepVehicles(groupofvehicles : FactoryGroup<T extends Vehicle>) : FactoryGroup<T>

By using this approach, your code can make more assumptions about the type of object in the factory group. This approach also prevents some coding errors, such as accidentally passing FactoryGroup<String> or FactoryGroup<Integer>, which fail at compile time. You can discover your coding errors quickly.