Generics

The generics feature was introduced into Java in version 5. To start with an example, our Sortable interface until now was this:

package packt.java9.by.example.ch03; 
public interface SortableCollection {
Object get(int i);
int size();
}

After introducing generics, it will be as follows:

package packt.java9.by.example.ch03; 

public interface SortableCollection<E> {
E get(int i);
int size();
}

The E identifier denotes a type. It can be any type. It says that a class is a sortable collection if it implements the interface, namely the two methods— size and get. The get method should return something that is of type E, whatever E is. This may not make too much sense up until now, but you will soon get the point. After all, generics is a difficult topic.

The Sort interface will become the following:

package packt.java9.by.example.ch03; 
import java.util.Comparator;
public interface Sort<E> {
void sort(SortableCollection<E> collection);
void setSwapper(Swapper swap);
void setComparator(Comparator<E> compare);
}

This still does not provide much more value than the previous version without generics, but, at least, it does something. In the actual class implementing the Sort interface, Comparator should accept the same type that SortableCollection uses. It is not possible that SortableCollection works on strings and we inject a comparator for integers.

The implementation of BubbleSort is as follows:

package packt.java9.by.example.ch03.bubble; 
import packt.java9.by.example.ch03.*;
import java.util.Comparator;
public class BubbleSort<E> implements Sort<E> {
@Override
public void sort(SortableCollection<E> collection) {
... sort code same as before
}
private Comparator<E> comparator = null;
@Override
public void setComparator(Comparator<E> comparator) {
this.comparator = comparator;
}
... method swapper same as before
}

The real power of generics will come when we will write the tests. The first test does not change much, although with the generics, it is more definite.

@Test 
public void canSortStrings() {
ArrayList<String> actualNames = new ArrayList< >(Arrays.asList(
"Johnson", "Wilson",
"Wilkinson", "Abraham", "Dagobert"
));
ArrayList<String> expectedResult = new ArrayList<>(Arrays.asList(
"Abraham", "Dagobert",
"Johnson", "Wilkinson", "Wilson"
));
SortableCollection<String> names =
new ArrayListSortableCollection<>(actualNames);
Sort<String> sort = new BubbleSort<>();
sort.setComparator(String::compareTo);
sort.setSwapper(new ArrayListSwapper<>(actualNames));
sort.sort(names);
Assert.assertEquals(expectedResult, actualNames);
}

When we define ArrayList, we will also declare that the elements of the list will be strings. When we allocate the new ArrayList, there is no need to specify again that the elements are strings because it comes from the actual elements there. Each of them is a string; therefore, the compiler knows that the only thing that can come between the < and > character is String.

The two characters < and >, without the type definition in between, is called diamond operator. The type is inferred. If you get used to generics, this code brings you more information on the types that the collections work on and the code becomes more readable. The readability and the extra information is not the only point.

As we know that the Comparator argument is Comparator<String> now, we can use advanced features of Java available since Java 8 and can pass the String::compareTo method reference to the comparator setter.

The second test is the important one for us now. This is the test which ensures that Sort does not interfere with the exception that the comparator throws.

@Test(expected = RuntimeException.class) 
public void throwsWhateverComparatorDoes () {
ArrayList<String> actualNames = new ArrayList<>(Arrays.asList(
42, "Wilson",
"Wilkinson", "Abraham", "Dagobert"
));
SortableCollection<String> names =
new ArrayListSortableCollection<>(actualNames);
Sort<String> sort = new BubbleSort<>();
sort.setComparator((String a, String b) -> {
throw new RuntimeException();
});
final Swapper neverInvoked = null;
sort.setSwapper(neverInvoked);
sort.sort(names);
}

The thing is, that it does not even compile. The compiler says that it cannot infer the type of ArrayList<> on the third line. When all the arguments of the asList method were strings, the method returned a list of String elements and therefore the new operator was known to generate ArrayList<String>. This time, there is an integer, and thus, the compiler cannot infer that ArrayList<> is for String elements.

To change the type definition from ArrayList<> to ArrayList<String> is not a cure. In that case, the compiler will complain about the value 42. This is the power of generics. When you use classes that have type parameters, the compiler can detect when you provide a value of the wrong type. To get the value into ArrayList to check that the implementation really throws an exception, we will have to conjure the value into it. We can try to replace the value 42 with an empty String and then add the following line which will still not compile:

actualNames.set(0,42);

The compiler will still know that the value you want to set in ArrayList is supposed to be String. To get the array with the Integer element, you will have to explicitly unlock the safety handle and pull the trigger, shooting yourself:

((ArrayList)actualNames).set(0,42);

Now, the test looks like this:

@Test(expected = RuntimeException.class) 
public void throwsWhateverComparatorDoes() {
ArrayList<String> actualNames = new ArrayList<>(Arrays.asList(
"", "Wilson",
"Wilkinson", "Abraham", "Dagobert"
));
((ArrayList) actualNames).set(0, 42);
SortableCollection<String> names =
new ArrayListSortableCollection<>(actualNames);
Sort<String> sort = new BubbleSort<>();
sort.setComparator((a, b) -> {
throw new RuntimeException();
});
final Swapper neverInvoked = null;
sort.setSwapper(neverInvoked);
sort.sort(names);
}
We will set the Swapper to be null because it is never invoked. When I first wrote this code, it was evident to me. A few days later, I read the code and I stopped. Why is swapper null? Then I remembered in a second or two. But any time, when reading and understanding the code hicks up, I tend to think about refactoring.
I can add a comment to the line saying //never invoked, but comments tend to remain there even when functionality changes. I learned it the hard way in 2006, when a wrong comment prevented me from seeing how the code was executing. I was reading the comment while debugging, instead of the code, and bug fixing took two days while the system was down.
Instead of a comment, I tend to use constructs that make the code express what happens. The extra variable may make the class file a few bytes bigger, but it is optimized out by the JIT compiler so the final code does not run slower.

The comparator that throws an exception was provided as a lambda expression. Lambda expressions can be used in cases where an anonymous class or named class will be used having only one simple method. Lambda expressions are anonymous methods stored in variables or passed in argument for later invocation. We will discuss the details of lambda expressions in Chapter 8, Extending our E-Commerce Application.

For now, we will go on implementing QuickSort, and to do that, we will use the TDD methodology.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.255.187