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); }); } }

 

  1. Modify TextFileSource so that it has a built-in functor (we are going to embed this into the class)

  2. We will provide an overloaded version of loadData()

  3.  

  4. We will need to update the IDataSource interface

  5. Modify the TextFileSource so that it is a generic class (only public ArrayList<String> loadData( String name ) for now)

  6. Notice lines 1, 4, and 10. Observe how the generics are being used.

  7. 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 following

  8. We don’t know what collection type will be used this is why the return type has been declared as generic

  9. The class TextFileSource is still problematic, consider the method public <T> T loadData(String fileName, ICollectionLoader func), it creates an ArrayList<String> object at line 5. We need to remove this constraint.

  10. 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

  11. The method public <T> T loadData( String fname ) is now out of sync with public <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 method

  12. So the completed code as generics now looks like this

  13. TextFileSource

  14. 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