March towards Generics
In this article, I want to describe my thought process as I worked towards the design of a Java Generic class
I will start with some pre-existing code
package com.celestial.generics;
/**
*
* @author selvy
*/
public interface IDataSource
{
public Iterable<String> loadData( String fname, ICollectionLoader func );
}
And the implementation class
package com.celestial.generics;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* We want to able to load data from any medium, not just of the disk, so we are going to turn the
* Iterable<String> loadFile(String fileName, ICollectionLoader func) into a contract,
* see the IDataSource interface
*/
public class TextFileSource implements IDataSource
{
@Override
public Iterable<String> loadData(String fileName, ICollectionLoader func)
{
// Currently, the collection used by the functor is hardcoded, but we change this shortly
ArrayList<String> lines = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
String line;
while ((line = br.readLine()) != null) {
// Process the line
// we use a lambda here to do the heavy lifting
func.addElement(lines, line);
}
} catch (FileNotFoundException ex) {
Logger.getLogger(App.class.getName()).log(Level.SEVERE, "File not found: " + fileName, ex);
} catch (IOException ex) {
Logger.getLogger(App.class.getName()).log(Level.SEVERE, "I/O error occurred", ex);
}
return lines;
}
}
Here is the target test
package com.celestial.generics;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import static org.junit.jupiter.api.Assertions.*;
class TextFileSourceTest
{
@Test
void howto_use_loadData_method_with_single_param()
{
TextFileSource<ArrayList tfl = new TextFileSource();
String fname = "C:\\tmp\\KeyboardHandler.java.txt";
ArrayList<String> lines = tfl.loadData(fname);
lines.forEach((element) ->{
System.out.println(">> " + element);
});
}
}
Modify
TextFileSource
so that it has a built-in functor (we are going to embed this into the class)We will provide an overloaded version of
loadData()
We will need to update the
IDataSource
interfaceModify the TextFileSource so that it is a generic class (only
public ArrayList<String> loadData( String name )
for now)Notice lines 1, 4, and 10. Observe how the generics are being used.
You’ll get quite a few errors. This is because the method declaration no longer matches the method declaration in the IDataSource interface. Currently the operations on IDataSource all return
ArrayList<String>
. This is a highly constrained implementation, so we are going to change the interface to the followingWe don’t know what collection type will be used this is why the return type has been declared as generic
The class
TextFileSource
is still problematic, consider the methodpublic <T> T loadData(String fileName, ICollectionLoader func)
, it creates an ArrayList<String> object at line 5. We need to remove this constraint.We need to modify the method so that the collection is passed in and not created inside the method. We will also need to update the interface
IDataSource
The method
public <T> T loadData( String fname )
is now out of sync withpublic <T> T loadData( String fname, T lines, ICollectionLoader<T> func )
because the latter method requires a collection to be passed in that will be the recipient of the data loaded from a source. We will temporarily create an ArrayList<String> in the former method and pass it to the latter methodSo the completed code as generics now looks like this
TextFileSource
TextFileSourceTest
Deeper Generics
I still have a problem with the codebase. Line 21 of TextFileSource file above. The hard-coded creation of the ArrayList<String>
. I really want the collection object that is created in TextFileSource.loadData( String name)
to be of the type <T> by the method itself.
You can’t instantiate generics like this T var = new T()
where T is a generic parameter.
There is an interesting article on this subject at this location. We will gradually move towards one of the solutions shown in this article.
Firstly we will update IDataSource
to the following
Now we need to update TextFileSource
to the following (we will break it down)
The loadData( String fname, T lines, ICollectionLoader<T> func )
method is now
And the loadData( String fname )
method is now
We want to create the collection object based on the class type bound to T
. Java does not allow us to create an object using the new keyword on generic types
Generics in Java lose their type info (type erasure) once the code has been compiled - all generic types become Object
once the code is compiled. This means the compile cycle is where type checking is accomplished.
We need some way of injecting the type info into the code for runtime use. We are going to use a factory object to hold the type info
TextFileSource
is now modified to look like this