views:

69

answers:

4

I'm wondering what are the options to specialize generic types in Java, i.e. in a templated class to have specific overrides for certain types.

In my case I was a generic class (of type T) to return null usually, but return "" (the empty string), when T is the String type, or 0 (zero) when its the Integer type, etc.

Merely providing a type-specific overload of a method produces a "method is ambiguous" error:

e.g.:

public class Hacking {

  public static void main(String[] args) {
    Bar<Integer> barInt = new Bar<Integer>();
    Bar<String> barString = new Bar<String>();

    // OK, returns null
    System.out.println(barInt.get(new Integer(4)));

    // ERROR: The method get(String) is ambiguous for the type Bar<String>
    System.out.println(barString.get(new String("foo")));
  }

  public static class Bar<T> {

    public T get(T x) {
      return null;
    }

    public String get(String x) {
      return "";
    }  
  }
}

Is the only option to subclass the generic class with a specific type (see StringBar in the following example?

  public static void main(String[] args) {
    Bar<Integer> barInt = new Bar<Integer>();
    StringBar barString2 = new StringBar();

    // OK, returns null
    System.out.println(barInt.get());

    // OK, returns ""
    System.out.println(barString2.get());
  }

  public static class Bar<T> {

    public T get() {
      return null;
    }
  }

  public static class StringBar extends Bar<String> {
    public String get() {
      return "";
    }
  }
}

Is this is the only way, it's a bit of a pain to have to create a subclass for every type I want to specialize instead of an overload of get() in the Bar class.

I'm guessing I could check the instanceof in the Bar.get() method, e.g. T get(T t) { if (t instanceof String) return ""; if (t instanceof Integer) return 0; else return null; }

However I've been taught to avoid instanceof and use polymorphism when possible.

+1  A: 

Generics in Java aren't made for specialization. They're made for generalization! If you want to specialize for certain types, you should be specializing...through a subclass.

Often you don't need to do something in a specialized manner however. Your StringBar example is kind of contrived because you could have this:

public class Bar<T> {
     private final T value;
     public T get() {
        return value;
     }
}

I don't see why you need to specialize for a String here.

Mark Peters
So my only option is the StringBar method?
AshirusNW
Basically, get() retrieves a instance of type T from elsewhere which isn't stored in the class, but if there's an error it returns null, but I would like it to return "" for String if there's an error to prevent extra null-checking for the caller in the case of String specialization.
AshirusNW
Btw, of course the example is contrived - that's me having mercy on you by not showing you the real source which is to long to post and detracts from the real question. The real source code has a specific need for specialization.
AshirusNW
+1  A: 

The compiler is actually correct, because the following code is compile-time checked (Bar<String> barString = new Bar<String>();) when compiled, from

public static class Bar<T> {

    public T get(T x) {
      return null;
    }

    public String get(String x) {
      return "";
    }  
  }

to

public static class Bar<String> {

    public String get(String x) {
      return null;
    }

    public String get(String x) {
      return "";
    }  
  }

and is ambiguous as you can't have 2 identical methods with the same return types and the same parameter arguments.

See an explanation by Jon Skeet's:


You can subclass Bar<T> and create StringBar (note I removed the static keyword) and override get() method.

public class BarString extends Bar<String> {

    @Override
    public String get(String x) {
        return "";
    }
}
The Elite Gentleman
Aye, I'm aware of why the compiler throws an error - this has been covered adequately on SO. What I want to know is the various solutions that *do* work. I've suggested the subclassing (StringBar) method, I'm wondering if there's any other way of specializing.
AshirusNW
My suggestion is to remove `public String get(String x)` method. Why is it needed if you have `Bar<String> barString`?
The Elite Gentleman
barString doesn't work as I'd like it to. I want barString.get() to return the empty string, not null.
AshirusNW
See updated post.
The Elite Gentleman
Your BarString is for all intents and purposes the same as my StringBar in my question. (Barring the String parameter.) The @Override does make the intent slightly clearer
AshirusNW
@AshirusNW, I didn't see that you updated your post....sorry!
The Elite Gentleman
A: 

Generics in Java are very different from templates in C++ in this respect. It is not possible to write a specific version of a generic class to do something different for a particular case, as C++ can do. It is also not possible to determine at run time what T is - this is because that information is not passed into the byte code (object code) and so doesn't even exist at runtime. This due to something called "type erasure".

BarString and BarInt would be the obvious way of doing this, but there are improvements you can make. For example you can write a generic Bar to cover the common cases, and then write specialized BarString and BarInt to implement special cases. Ensure that the instances can only be created through a factory, which takes the class of the object to be processed:

class Bar<T> {
  class BarString extends Bar<String> {
    // specialist code goes here
  }


static Bar<T> createBar(Class<T> clazz) {
  if (clazz==String.class) {
    return new BarString();
  } else {
    return new Bar<T>;
}

That probably won't compile, but I don't have the time to work out the exact syntax. It does illustrate the principle.

DJClayworth