A common pattern in object oriented programming is interfacing classes that have the same methods. If these classes encapsulate a datatype and expose methods that take the datatype as a parameter, generic interfaces. The approach results in the ability to apply a generic algorithm on different encapsulated data types.
For example vector math classes have this property, they can be defined for double
and float
datatypes and also for 2- and 3-dimensional implementations with methods for doing common operations.
public final class Vec3 implements Vec<Vec3> { private double x; private double y; private double z; /** * returns the distance to another vector * @param other the other vector * @return distance */ public final double distance(Vec3 other) { return Math.sqrt( (x-other.x)*(x-other.x)+ (y-other.y)*(y-other.y)+ (z-other.z)*(z-other.z)); } } public final class Vec2 implements Vec<Vec2> { private double x; private double y; /** * returns the distance to another vector * @param other the other vector * @return distance */ public final double distance(Vec2 other) { return Math.sqrt( (x-other.x)*(x-other.x)+ (y-other.y)*(y-other.y)); } }
The generic interface that both classes implement can then be declared like this:
public interface Vec<V> { double distance(V other); }
It can contain all functions that have the same ‘abstract’ method signature that the classes have in common, ie. the more complicated cross product that returns a vector again.
The real benefit of this is not immediately obvious, consider adding up the distances of a list of items – one would naively just implement the method for each vector type Vec2 and Vec3. This results in duplicate code, reduced maintainability and is errorprone.
A class holding the algorithms operating on the Vec<V> interface can be used instead, the method needs to make use of the disance(V) method to determine the overall distance.
public class Toolbox { /** * Determines the overall distance between the points in the list */ public static <T extends Vec<T>> double getAllDistances(List<T> points) { T prevPoint = points.get(0); T point; double sum = 0.0; int len = points.size(); for (int i=1;i<len; i++) { point = points.get(i); sum+=point.distance(prevPoint); prevPoint = point; } return sum; } }
This method is type-safe and works with all types implementing the generic Vec interface due to the <T extends Vec<T>>
declaration. That way algorithms on vectors can safely be defined once for several variations of the classes.
In contrast, a declaration like below seems more natural at first, but does not work:
public static <T> double getAllDistances(List<Vec<T>> points) { Vec<T> prevPoint = points.get(0); Vec<T> point; double sum = 0.0; for (int i=1; i<points.size(); i++) { point = points.get(i); sum+=point.distance(prevPoint); prevPoint = point; } return sum; }
Here, calling the method point.distance(prevPoint)
gives a compiler error since prevPoint is of type Vec<T>
but the method signature is distance(V other)
and expects it to be T
, rather than Vec<T>
.