- COMP.CS.140
- 8. Generics
- 8.1 Polymorphism: Generics
Polymorphism: Generics¶
Learning Outcomes
The theme of this week if different ways to create genericity and making programming more fluent. You will learn to explain the different forms of polymorphism. In addition you will learn to understand the basics of generic programming. In Java you will learn to use generics and the functional features of the language in your programs.
Polymorphism at hear meand that one name can be used to mean several different things in a program. So far we have focused on the polymorphism of objects and especially on inheritance: an object of a subclass can be handled as an object of its base class. Polymorphism as a concept however is more varied as names are used in many different roles in programming languages. All aim to the same goal in the end: code reuse and implementing the same functionality only once. This improves program quality and makes implementing them more fluent.
Let’s take a more in-depth look at polymorphism. As a concept it originates from functional programming. The simplified definition of polymorphism is that one name has several meanings in a program. Things get more complicated when we think what all are names used for in programming: for variables, functions, classes. Polymorphism can be considered in at least four different roles:
Overloading (ad hoc -polymorphism) refers to a situations where one function name has several alternative implementations. Overloaded function names are typically distinguished from each other at compile time. An example of overloaded functions:
public class Overload {
public void example( int value ){ ... }
public void example( int value1, double value2){ ... }
public void example( string value ){ ... }
}
Overriding (inclusion polymorphism) is a familiar situation from inheritance where a subclass implements its own version of a base class method. In overriding there are two implementations of the same function but unlike in overloading, the signature (return value, name, parameter types and order) must be the same both in the base class and the sub class. The selection of the implementation is also done at runtime. For example:
public class BaseClass {
public void example( int value ){ ... }
}
public class SubClass extends BaseClass {
public void example( int value ){ ... }
}
A polymorphic variable (assignment polymorphism) is a variable that is declared as one type in in reality contains a value of another type. This is a familiar type of polymorphism from inheritance, where a subclass object is handled through its base class part:
BaseClass bc = new SubClass();
Generics (templates) offers a posibility to write general purpose functions and classes that are then specialized to different use cases. Genericity is also the goal of inheritance: common functionality if collected into the base class and the subclasses specialize the functionality of their base class. In inheritace however, the specialized services are tied to the implementation defined by the base class: the parameters, return values and the name remain unchanged. This means services that would work in the same way but would handle different types, cannot be combined into one implementation. Generics offers an alternative for exactly this purpose by enabling generalizing the types handled by the services.
Generics¶
Let’s start by noting that generics are not as a whole part of the core learning outcomes of the course. Yet it is a versatile programming tool and as such one should be aware of its basics even if it would not be used in ones own programs quite yet. A generic function or a class can be parametrized by the type of the data it handles. The goal is a multi-purpose function or class.
On a design level making any general use of code and code reuse requires the ability to indentify which parts of the program and their use contexts need common functionality that remains unchanged (communality). and where their need vary (variability). This is called communality and variability analysis with the help of which a general, multi-purpose component can be designed. Communality and variablity are opposite forces in design: the more general purpose component is the goal, the more variablity there is. On the other hand, if the use cases are limited to a very narrow scope, communality can be increased, but the use of the component is at the same time limited. In programming languages mechanisms to handle communality and variability are the already familiar inheritance and generics.
A generic function of class is parametrized with a type.
A generic component –function or class – works thus in a certain uniform way for several different data types.
This means that in generic implementations a type parameter is used to replace the missing data type names in the code just like parameter namesa are used to replace values given at the time the function is called.
Genericity is in fact very familiar from the programming languages you have used: the
std::vector<double>
in C++ or ArrayList<Double>
in Java are such multi-purpose, generic data structures whose type of element is defined through a parameter.
One generic implementation can use several type parameters.
When the implementation is then used in the code, it is instantiated by givin actual values to the open types.
A generic function can hence work on several different parameter types. The name of the generic type is used in the same way as the name of any type in the function implementation. A generic class acts as a model (or template) to a group of classes that work in the same way but a certain part of their implementation differs in its type from eachother. A generic class is not in itself a class. It defines a template for a group of similar classes that are instantiated into a class by giving a concrete type to the type parameter.
In general no requirements are places on the type parameters when writing generic implementations.
It is however clear that a generic implementation cannot always work on any type are the interfaces of different types and objects differs from eachother.
A generic implementation can use some functionality that all types in the program do not have.
For example even such a simple feature as smaller than <
comparison is such a piece of functionality.
Hence it is important to document clearly the requirements set on the types by the generic implementation.
Examples of these are visible when reading the documentation of library implementations in programming languages providing generics.
In addition the generic implementation itself should be written in a way that it doesn’t require more from its type parameters that what is necessary from the point of view of the impolementation.
This requires experience and attention from the programmer.
Generic programming is not straightforward and it is easy to encounter situations where the implementation meant as generic should change its own structure based on the types of the type parameters. For example the type received through the type parameter can have features affecting the implementation decisions made in the code. This kind of reflection on code level is called metaprogramming. It is only mentioned here and thus belongs to the more advanced courses. You can read more from Czarnecki, Eisenecker: Generative Programming: Methods, Tools, and Applications if metaprogramming piques your interest.
On naming type parameters
Type parameters should be named in a way that they are easily distinguishable as such in the code. A common practice is to use single capital letters as the names for type parametes as this sets them clearly apart from both primitice types as well as class and interface names. The most common type parameter names are: T – a type, V – a value, S, U, V, etc. – the 2nd, 3rd, 4th etc. type, N – a number and E – an element of a data structure. This is only a convention. You can name the type parameters according to the style guide in use.
Genericity in different languages¶
As mentioned earlier, genericity in Java differs significantly from for example that of C++. The next section covers Java further.
C++ implements generic functions and classes through templates. The name often used for the C++ Standard Library (Standard Template Library (STL)) comes from this. The key difference to Java is that C++ does not require any kind of inheritance relationship from the type parameters.
A C++ template starts with the keyword template
followed by the type parameters in <>
.
//function template
template <typename T>
bool compare (T p1, T p2) {
return p1 < p2;
}
compare<int>(1,2);
//class template
template <typename T, typename U>
class Example
{
public:
Example(T first, U second);
T getFirst() const;
U getSecond() const;
private:
T first_;
U second_;
};
template <typename T, typename U>
T Example<T, U>::getFirst() const{ return first_;}
Example<int, int> ex(1, 2);
Unlike in Java, in C++ the compiler compiles every version instantiated from the template with different parameter values to type parameters its own code. The compiler also handles the methods of a classa as function templates: in instantiates only those methods that are actually called for the object of the class in the program. The use of templates makes the programming easier but does not affect the final executable much.
Polymorphism: generics (duration 19:11)