Skip to content
Advertisement

Generics code not working when i try to return concrete object

The goal i am trying to achieve, is to have different FileLoaders like CSVFileLoader, ExcelFileLoader that can load up any object of type T, as long as it know how to convert using ‘C’ and create the object of type T. Hope this makes sense.

I am trying to use generics to create a generic FileLoader that will take a Converter of type C and return a List of Object of type T. So I went about creating something like the below but it inst working as expected.

I am getting an error while trying to return object Transaction in the convert method. How should I rewrite this so that it can use generics and I can improve this code to work. I understand there is type erasure, so that is why its complaning in the below code but not sure how to fix it. Please advise

//FileLoader takes a converter of type C, Object of type T 
public interface FileLoader<T,C> {    
    List<T> load();
}

//Converter return a list of objects of type T
public interface Converter<T> {
    List<T> convert(Iterable<CSVRecord> csvRecords);
}

So with the above interfaces, i tried implementing my classes but clearly I am going wrong, so my understanding isnt great and I would like some help as to where I am going wrong.

public class TransactionConverter<T> {

    public List<T> convert(Iterable<CSVRecord> records) {
        List<T> transactions = new ArrayList<>();
        for(CSVRecord r: records){
            T t = convert(r);
            transactions.add(t);
        }
        return transactions;
    }

   private T convert(CSVRecord r){
        //TradeDate,Symbol,Buy/Sell,Quantity,TradePrice
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
        LocalDate transactionDate = LocalDate.parse(r.get(0), formatter);
        String ticker = r.get(1);
        TransactionType transactionType = TransactionType.valueOf(r.get(2));
        Double amount = Double.parseDouble(r.get(3));
        Double quantity =   Double.parseDouble(r.get(4));
        //getting ERROR HERE
        return new Transaction(ticker, "",transactionDate, transactionType, amount, quantity); 
    }
}

public class CSVFileLoader<T,C> implements FileLoader<T,C> {

    private String filePath;
    private TransactionConverter converter;
    private Logger logger = LoggerFactory.getLogger(CSVFileLoader.class);

    public CSVFileLoader(String filePath, TransactionConverter converter){
        this.filePath = filePath;
        this.converter = converter;
    }

    @Override
    public List<T> load() {
        List<T> transactions = null;
        Reader in = null;
        Iterable<CSVRecord> records;
        try {
            in = new FileReader(filePath);
            records = CSVFormat.RFC4180.withHeader("ID", "CustomerNo", "Name").parse(in);
            transactions =converter.convert(records);
        } catch (FileNotFoundException e) {
            logger.info("Unable to load file " + filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    return transactions;
    }
}

Advertisement

Answer

The source code in the question for TransactionConverter does not implement the interface Converter. Because it’s trying to implement it with the concrete type of Transaction, it should specify that in the implements clause and should not declare a type parameter for itself. It should use the concrete type of Transaction anywhere that T was used in Converter. Here’s the resulting source:

public class TransactionConverter implements Converter<Transaction> {

    @Override
    public List<Transaction> convert(Iterable<CSVRecord> records) {
        List<Transaction> transactions = new ArrayList<>();
        for(CSVRecord r: records){
            Transaction t = convert(r);
            transactions.add(t);
        }
        return transactions;
    }

   private Transaction convert(CSVRecord r){
        //TradeDate,Symbol,Buy/Sell,Quantity,TradePrice
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
        LocalDate transactionDate = LocalDate.parse(r.get(0), formatter);
        String ticker = r.get(1);
        TransactionType transactionType = TransactionType.valueOf(r.get(2));
        Double amount = Double.parseDouble(r.get(3));
        Double quantity =   Double.parseDouble(r.get(4));
        //getting ERROR HERE
        return new Transaction(ticker, "",transactionDate, transactionType, amount, quantity); 
    }
}

The type parameter C is not used in the FileLoader interface, so it is redundant. The fact that a converter is used is an implementation detail of CSVFileLoader in this code. Here’s the updated interface declaration:

public interface FileLoader<T> {
    List<T> load();
}

If I understand your intent, CSVFileLoader should be able to work with any implementation of Converter, but it currently requires a TransactionConverter. It also uses the name transactions for the list of results, but these might not be Transaction objects if another type of Converter is used. Here’s the updated implementation:

public class CSVFileLoader<T> implements FileLoader<T> {

    private String filePath;
    private Converter<T> converter;
    private Logger logger = LoggerFactory.getLogger(CSVFileLoader.class);

    public CSVFileLoader(String filePath, Converter<T> converter) {
        this.filePath = filePath;
        this.converter = converter;
    }

    @Override
    public List<T> load() {
        List<T> convertedRecords = null;
        Reader in = null;
        Iterable<CSVRecord> records;
        try {
            in = new FileReader(filePath);
            records = CSVFormat.RFC4180.withHeader("ID", "CustomerNo", "Name").parse(in);
            convertedRecords = converter.convert(records);
        } catch (FileNotFoundException e) {
            logger.info("Unable to load file " + filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return convertedRecords;
    }
}
Advertisement