3

I am writing a little class and I encountered a problem.

This class reads CSV file, there will be only one value for each key. I have

class CSVMap<T> extends AbstractMap<String,T>

and I have:

public void load(String filename)
{
    try 
    {
        BufferedReader out=new BufferedReader(new FileReader(filename));
        String []tab;
        T value;

        while(out.ready());
        {
            tab=out.readLine().split(",");


        }
    } catch (IOException e) 
    {
        e.printStackTrace();
    }
}

now I need to create new instances of T to put them in my map ( I know that T has constructor that takes string).

I know about type-erasure in Java so only way out of this is a) passing T.Class to my load method or keeping it somewhere private in my class or b) keeping some private object T and using it in method to get Class object?

I am asking because I wanted to be sure if there isn't any other way to do it.

skaffman
  • 398,947
  • 96
  • 818
  • 769
Andna
  • 6,539
  • 13
  • 71
  • 120
  • Nope, that's exactly how you should be doing it - the JDK does the same thing if you look around a bit. – Voo Dec 03 '11 at 22:39
  • Related: http://stackoverflow.com/questions/1927789/why-should-i-care-that-java-doesnt-have-reified-generics – BalusC Dec 03 '11 at 22:42
  • This is the reason that reflection is a weak way to obtain information about code: you only can get what the compiler guys are willing to leave behind. And with type erasure, its unclear what to leave behind (the original type? all intermediate types?) and the class files pretty much don't have any way to talk about generics. So, the java compiler guys decided to leave it out. The only *really* good "reflection" scheme, one that enables you get any information the source code, is in essence a compiler front end. – Ira Baxter Dec 03 '11 at 23:23

3 Answers3

2

There is another way which you may wish to consider. That is getting your CSVMap to take as a parameter to its constructor an object which knows how to convert from from class to another class.

eg.

public class Test {

    public static void main(String[] args) {
        CSVMapLoader<Integer> loader = new CSVMapLoader<Integer>(new IntegerParser());
        loader.load();
    }

}

class CSVMapLoader<T> {

    private final Parser<T> parser;

    public Loader(Parser<T> parser) {
        this.parser = parser;
    }

    public CSVMap<T> load() {
            // as an example of how to get your T
        T t = parser.parse("1000");
        System.out.println("t equal to 1000? "+(t.equals(1000)));
            // and instead put your real logic to load up map here
    }
}

interface Parser<T> {
    public T parse(String str);
}

class IntegerParser implements Parser<Integer> {

    public Integer parse(String str) {
        return Integer.valueOf(str);
    }
}

As a side note of code design. You shouldn't really subclass (Abstract)Map in this case. What you want is class that knows how to load up key value pairs from a csv file. The Map interface is just about storing and accessing those pairs once loaded. So really you want a separate factory class that takes a file, parser and maybe base map type and loads up the map for you. Leaving you with an untouched map instance, unaware of how it was loaded.

Dunes
  • 37,291
  • 7
  • 81
  • 97
1

Nope, you're correct. There's absolutely no way to recover the type parameter of T at runtime unless you've passed a Class object into the instance of an instance's method at runtime. Another strategy you could try would be to pass a factory object into load() or into CSVMap. This would remove the concern of knowing the actual class from CSVMap and would make it the responsibility of the calling object (by knowing the factory implementation).

Various libraries have their own goofy work-arounds for this issue. A particularly clever one is included in Google Guice. Check out http://google-guice.googlecode.com/git/javadoc/com/google/inject/TypeLiteral.html . It uses the fact that a parameterized class DOES retain its type parameters when it's subclassed using concrete type parameters. So you can do:

TypeLiteral<List<String>> typeLiteral = new TypeLiteral<List<String>>() {};
typeLiteral.getType().toString(); // returns "java.util.List<java.lang.String>"
Avi Cherry
  • 3,996
  • 1
  • 26
  • 31
-1

Nope, no other way. I would pass the Class object

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140