1.4-Software-Development-Principles

Generics

Learning goals

How not to solve this problem.

You might think: “Wait, can’t I just store Object? That can hold any other type I assign to it.” You are of course correct, through Polymorphism all Java objects can be assigned to an Object type variable.

There is however a bit problem with this. If your list allows objects you have the ability to add all kinds of different types in that list, and you have to figure out what they were before you can take them out again.

Even if you intend to only store one specific type in your ObjectList you have no guarantee that no mistakes were made.

A generic list forces the use of only a single type, and guarantees that the list will return only entries of that specific type. No mistakes CAN BE made.

A small generic example

Sometimes you would like a function to return two values and not one. For instance, the index in the list in which something was found, and also the item that was found at that location. Since you had a hold of it anyway, why look it up using that index later?

We present the idea of a Tuple (Two-pull), a small class that contains two fields. We slightly adapt the definition here. (Since Java will not allow classes named 1Tuple, 2Tuple, 3Tuple)

We will define a Tuple specifically to contain only two fields. We leave it to the student to build their own Triple.

The bad design

public class Tuple {
	private Object left;
	private Object right;
	
	public Tuple(Object left, Object right) {
		this.left = left;
		this.right = right;
    }
	
	// Getters and setters for left and right
} 

This class can do what we intended. It stores two values both of which can be any type.

To use this class we create a Tuple with two fields:

public class Example {
	public void example() {
		Tuple myBadTuple = new Tuple("Text", new Student(12345, "John", "Doe"));
		
		String textFromTuple = (String)myBadTuple.getLeft();
		Student studentFromTuple = (Student)myBadTuple.getRight();
    }
}

The lines that read data from the tuple have to cast that data back to the correct type. In this case we can see that the correct types are used, but if this tuple is given to another function that information might get lost or confused, and mistakes can be made.

The generic design

When using generics we want to express and force the use of a specific type. The odd thing is that we (the designers of this class) choose not to decide which type that actually is.

Rather than deciding on the type now, we will choose a marker to represent that type. This is called a type parameter and looks like this <T>, or like this <P, Q>

Usually only a single capital letter is used for each type. (Regrettably that doesn’t enhance the readability.) And this requires some getting used to. This letter T will now replace every location where you would have placed the type of that variable:

public class Tuple<L,R> {
    private L left;
    private R right;
    
    public Tuple(L left, R right) {
        setLeft(left);
        setRight(right);
    }
    
    public L getLeft() { return left; }
    public R getRight() { return right; }
    public void setLeft(L left) { this.left = left; }
    public void setRight(R right) { this.right = right; }     
} 

Since I (the designer) don’t know what will be stored I use the variables ‘left’ and ‘right’. This is also the reason why I use <L,R> as my type parameters.

When I return the left variable the type L is used. And when I set a new value for the right variable the type R is used.

To create an instance of this class use the following code:

Tuple<String, Integer> result = new Tuple<>("Frederik", 17);
Tuple<Double, Student> other = new Tuple<>(7.5, new Student(...));

Please note that, because you specified the type of tuple that is used, you don’t need to specify the actual types when using the new statement. Java knows what types should go there.

One you have store a tuple in a variable, when taking stuff out, you know (and must use) the correct type.

For example, in the first case:

String name = result.getLeft();

and in the second:

Student student = other.getRight();

Using a generic class on purpose

To show the use of this idea we present the following situation:

public class StudentManager {
    // I need a list that can only contains Students.
    private ArrayList<Student> students;
    
    public StudentManager() {
        students = loadStudentData();
    }
    
    // Returns both the index at which the student was found and
    // the student object instance.
    public Tuple<Integer, Student> findStudentWithIndex(String firstName) {
        for (int i=0;i<students.size();i++) {
            Student student = students.get(i);
            if (student.matches(firstName)) {
                return new Tuple<Integer, Student>(i, student);
            }
        }
        return null;
    }
	
    public static void test() {
        StudentManager mgr = new StudentManager();
        Tuple<Integer, Student> found = mgr.findStudentWithIndex("John");
        // No need to cast since we KNOW the actual type used.
        int index = found.getLeft();
        Student student = found.getRight();
    }
}

This code defines a search function that guarantees a tuple with two specific types. When assigning the resulting tuple, the java language forces us to be aware of that decision. We can also easily switch between int and Integer automatically.

Review

Now read the Tuple class code again. We didn’t choose to use an Integer and a Student. In fact any other type can replace the two type parameters:

Any possible combination is allowed.

Additional information

Can I store a generic type in an ArrayList? (Or another generic type?)

Yes you can! The following is allowed:

ArrayList<Tuple<Integer, Student>> myList;

This stores an ArrayList that requires its entries to be of the type: Tuple<Integer, Student>.

Please note that this works both ways:

Tuple<Chicken, ArrayList<String>> myVariable;

Can I extend from a generic class?

Yes you can:

public class Student implements Comparable<Student> {
	@Overide
	public int compareTo(Student other) {
		return Integer.compare(this.getStudentNumber(), other.getStudentNumber());
    }
}

This class implements the generic interface Comparable and since Students should be compared to other students we fill in <Student> so that the compareTo function expects a Student

Can I limit the types that are allowed?

Yes you can:

public class MyOrderedList<T extends Comparable<T>> {
	// ...
}

This class definition allows any type as long as it implements the Comparable interface. And since we want to compare items of type T to each other the precise definition is Comparable<T>.

Of course you can also limit to abstract classes, not only interfaces.

// Only allow Person instances and their descendants.
public class MyListOfPeople<P extends Person> {
	// ...
}

This means that each item in the list at least implements the Person interface even though the actual type can be a descendant like Teacher or Student.