ceving
ceving

Reputation: 23814

How to preserve the original type of a type-variable in Java?

I have the following example:

class bounds
{
  private StringBuilder str = new StringBuilder();

  public <Type> void add (Type value)
  {
    add_specific (value);
    str.append (String.format("%n"));
  }

  private <Type extends Number> void add_specific (Type value)
  {
    str.append (value);
  }

  public String toString () { return str.toString(); }

  public static void main (String[] args)
  {
    bounds test = new bounds();
    test.add (new Integer (42));
    System.out.print (test.toString());
  }
}

When I try to compile it I get the following error:

bounds.java:7: error: method add_specific in class bounds cannot be applied to given types;
    add_specific (value);
    ^
  required: Type#1
  found: Type#2
  reason: inferred type does not conform to declared bound(s)
    inferred: Type#2
    bound(s): Number
  where Type#1,Type#2 are type-variables:
    Type#1 extends Number declared in method add_specific(Type#1)
    Type#2 extends Object declared in method add(Type#2)
1 error

This looks to me as if the original type of the argument passed to the add method gets lost in the body of add. How can I preserve the type so that the correct add_specific method can be chosen?

Update

I have simplified my example, because I thought it would be easier to understand. But it seems to me that most people do not understand the reason why it contains a generic and a specific function. So I paste a more advanced example. Maybe this makes the reasons more obvious:

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

class bounds
{
  private StringBuilder str = new StringBuilder();

  public <Type> void add (Type value)
  {
    add_specific (value);
    str.append (String.format("%n"));
  }

  private <Type extends Number> void add_specific (Type value)
  {
    str.append (value);
  }

  private void add_specific (String value)
  {
    str.append ('"');
    for (int i = 0; i < value.length(); i++) {
      char ch = value.charAt(i);
      switch (ch) {
      case '\\': str.append ("\\\\"); break;
      case '"': str.append ("\\\""); break;
      default: str.append (ch);
      }
    }
    str.append ('"');
  }

  private static DateFormat iso8601
    = new SimpleDateFormat("'\"'yyyy-MM-dd'T'HH:mm:ssZ'\"'");

  private void add_specific (Date date)
  {
    str.append (iso8601.format(date));
  }

  public String toString ()
  {
    return str.toString();
  }

  public static void main (String[] args)
  {
    bounds test = new bounds();

    test.add (new Integer (42));
    test.add ("42");
    test.add (new Date());

    System.out.print (test.toString());
  }
}

I have a generic function called add. This generic function does something generic and calls a specific function to do something specific. The problem is, that the type to select the specific function gets lost in the generic function. And the question is how to fix this? How do I have to write the generic function so that it is still possible to select the right specific function in the body of the generic function?

Upvotes: 0

Views: 136

Answers (4)

wolfcastle
wolfcastle

Reputation: 5930

Get rid of the add and add_specific methods, and just overload the add method. Beware, however, that the choice of which method to make is done statically at compile time, NOT dynamically at runtime.

Like Jon said, what you are trying to do won't work. If you want all client code to look the same, then do something like this; although in Effective Java, Bloch suggests you avoid this kind of confusing API.

class Bounds {   
   private StringBuilder str = new StringBuilder();

  private void appendNewline() {
       str.append (String.format("%n"));
  }
  public  void add (Number value)   {
    str.append (value);   
    appendNewline();
  }

  public void add (String value)   {
    str.append ('"');
    for (int i = 0; i < value.length(); i++) {
      char ch = value.charAt(i);
      switch (ch) {
      case '\\': str.append ("\\\\"); break;
      case '"': str.append ("\\\""); break;
      default: str.append (ch);
      }
    }
    str.append ('"');  
  appendNewline();
 }

  private static DateFormat iso8601
    = new SimpleDateFormat("'\"'yyyy-MM-dd'T'HH:mm:ssZ'\"'");

  public void add (Date date)   {
    str.append (iso8601.format(date));   
    appendNewline();
  }

  public String toString ()   {
    return str.toString();   }

  public static void main (String[] args)   {
    bounds test = new bounds();

    test.add (new Integer (42));
    test.add ("42");
    test.add (new Date());

    System.out.print (test.toString());   } 
}

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500155

This looks to me as if the original type of the argument passed to the add method gets lost in the body of add.

Well, type erasure means that Type won't be known at execution time, but that's not the immediate reason you're getting a compile-time error. You can call add with any type - whereas you can only call add_specific with a type which is compatible with Number. So for example, consider:

// TODO: Fix your naming to meet Java naming conventions
bounds b = new bounds();
b.<String>add("foo");

How would you expect that to call add_specific? String doesn't extend Number.

(As an aside, it's also a really bad idea to name a type parameter Type - it's too easy to confuse it with java.lang.reflect.Type.)

Options:

  • Add the extends Number bound to Type in add
  • Remove the bound from Type in add_specific

EDIT: Okay, so it sounds like you'd only given us half the picture - you're expecting overload resolution to be performed at execution time based on Type. That's not going to work, for two reasons:

  • Java always performs overload resolution at compile-time
  • Type erasure means that the information isn't present at execution time to base an overload decision on anyway. If you want that information, you'll need another parameter of type Class<Type>.

Upvotes: 3

Eugen Halca
Eugen Halca

Reputation: 1785

the compiler is screaming because reference value in your add method is not necessarily a Number

to solve this :

change add method to :

public <Type extends Number> void add (Type value)
{

}

or add :

public <Type> void add (Type value)
  {
    if (value instanceof Number){
        add_specific ((Number)value);
        str.append (String.format("%n"));
    }
  }

Upvotes: 0

Thomas
Thomas

Reputation: 88707

The problem is this part:

public <Type> void add (Type value)
{
   add_specific (value);
   ...
}

add_specific requires the value to be of a type that extends Number but add doesn't require/ensure that. Hence the error.

To fix it, you'd need to change add to use <Type extends Number>, but I doubt that's what you want. It seems like a design error to me: add_specific should call the more generic looking add and not the other way round.

Btw, as John Skeet already said, please adhere to the coding conventions, which will make it easer to spot bugs and read your code.

Upvotes: 1

Related Questions