views:

116

answers:

6

This question is hard to phrase, so I'm going to have to use some code samples. Basically, I have an (overloaded) method that takes 3 parameters, the last of which I overloaded. As in, sometimes the last parameter is a String, sometimes it's a double, etc. I want to call this method using a ternary conditional expression as the last parameter, so that depending on a certain value, it will pass either a double or a String. Here's an example...

Overloaded method headers:

writeToCell(int rowIndex, int colIndex, double value)

writeToCell(int rowIndex, int colIndex, String value)

What I'm trying to do:

writeToCell(2, 4, myValue != null ? someDouble : someString);

This, however, causes a compilation error:

The method writeToCell(int, int, double) in the type MyType is not applicable 
for the arguments (int, int, Object&Comparable<?>&Serializable)

It seems that Java isn't "smart enough" (or just doesn't have the functionality built in on purpose) to realize that either option has a method that supports it. My question is - is there any way to do this? I know I can sort of simulate it by passing in the double as a String (e.g. writeToCell(2, 4, myValue != null ? someDouble.toString() : someString);), but the method needs to receive it as a double data type.

Logic tells me that there's no way to force Java to accept this statement... But it's worth a try, as it will result in a lot clearer code for me. Anyone know of a way to do this...?

A: 

The ternary operator has to return the same type from the "then" or "else" clause. If you can deal with Double instead of double, then you could try

Object o = (myValue != null ? someDouble : someString);
writeToCell(2, 4, o);

and change the writeToCell method to accept arguments (int, int, Object).

Tenner
Not true. You can have unlike types in those positions. Try it!
Jonathan Feinberg
I have, but in the past; was this new in Java 5 or 6?
Tenner
Since the introduction of autoboxing in Java 5, all types can be converted to Object - previously that was indeed not the case and the mixing of primitive and reference types in a ternary operator a compile-time error.
Michael Borgwardt
@Michael: Thanks... you've proven that I'm not crazy. At least in that way.
Tenner
+4  A: 

Method calls are resolved statically, at compile time, not dynamically, at runtime. Therefore, the compiler is looking for the type that matches both of the possible arguments in that expression, as you can see.

You could do what the SDK does a lot of, and define your method as

writeToCell(int rowIndex, int colIndex, Object value)

and then have the first line be

final String repr = String.valueOf(value);

There are plenty of other solutions, but it's best not to overthink this.

Demonstration:

static class WrongAgain
{
    void frob(final Object o)
    {
        System.out.println("frobo " + o);
    }

    void frob(final String s)
    {
        System.out.println("frobs " + s);
    }

}

public static void main(final String[] args)
{
    final WrongAgain wa = new WrongAgain();
    wa.frob("foo");
    Object o = "foo";
    wa.frob(o);
}

If method lookup were dynamic, then the you'd see "frobs" both times. It's static.

Jonathan Feinberg
Method calls *are* resolved dynamically, at runtime (otherwise, overriding methods would not work). It's just the choice between overloaded methods that happens at compile time.
Michael Borgwardt
Incorrect; see demo.
Jonathan Feinberg
I did think of this solution, but the overloaded methods do slightly different things depending on the data type, so I couldn't just make one generic Object method. That's why I have the different methods in the first place
froadie
Then you'll have to have your Object-accepting method redispatch to the individual methods, or just do what tloach said.
Jonathan Feinberg
@Jonathan: got me; static method dispathc is compile time as well. But you're still wrong about the most common case (non-overloaded instance methods).
Michael Borgwardt
@Jonathan - I actually ended up using a different solution - see below, I posted it as an answer. Thanks for your help!
froadie
@Borgwardt, no. Those two method calls are compiled into INVOKEVIRTUAL operations with *different* operands, one of which has the signature for a string, and the other of which has the signature for an object. I've rewritten the example to demonstrate, but I'm not sure what will convince you!
Jonathan Feinberg
@Jonathan: wrong again. Read what I wrote: *non-overloaded* instance methods (and, in my very first comment, "overriding"). Override a method and it will be resolved *at runtime*, no matter what type the reference has. Which is why the bytecode operation is named INVOKEVIRTUAL
Michael Borgwardt
Argh, we agree violently. OK, I was arguing the wrong point; I concede!
Jonathan Feinberg
A violent agreement is so much more fun than a peaceful one...
Michael Borgwardt
But is this the right room for an argument?
Jonathan Feinberg
A: 

I suspect that it chokes up because the type of object (and hence overloaded version which should be called) cannot be determined at compile time. It's similar to:

Object o = myValue != null ? someDouble : someString;
writeToCell(2, 4, o);
Konrad Garus
+2  A: 

Is there any particular reason not to just write it out?

if (myValue == null)
  writeToCell(2, 4, someString);
else
  writeToCell(2, 4, someDouble);
tloach
Yes. I have a bunch of method calls in a row that have to do the same thing (and for various reasons cannot be in a loop), and writing it out will make the code a lot longer and messier.
froadie
If _THAT_ will make your code longer and messier, you have some serious refactoring to do!
Thorbjørn Ravn Andersen
A: 

Thanks everyone for all your suggestions and explanations.

A coworker of mine took a look at my code and suggested a solution that I implemented. I inserted the following code into the beginning of my double method:

if(value == null){
   writeToCell(rowIndex, colIndex, someString)
}
else{
   ...method body... 
}

I know that this implementation might not always be the best idea, but since I almost always need to check for null when passing a double value to this method, it makes the most sense in this situation. This way I don't have to repeat the check code (if / ternary statement) each time, and my method calls are much cleaner.

froadie
A: 

The return type from the ternary operation is being upcast to java.lang.Object, as the common superclass class of both String and the autoboxed double, so you would have to provide the method signature for the object, and then use that to make the appropriate cast and call the relevant method:

public class SoCast {


 public static void main(String[] args) {
  SoCast sc = new SoCast();

  Double someDouble = 3.14;
  String someString = "foo";
  String myValue = null;

  sc.writeToCell(2, 4, myValue != null ? someDouble : someString);

  myValue = "hello";

  sc.writeToCell(2, 4, myValue != null ? someDouble : someString);

 }

 private int writeToCell(int rowIndex, int colIndex, Object value) {
  int result = -1;
  if (value instanceof String) {
   result = this.writeToCell(rowIndex, colIndex, (String) value);
  } else if (value instanceof Double) {
   result = this.writeToCell(rowIndex, colIndex, (Double) value);
  }
  return result;
 }

 private int writeToCell(int rowIndex, int colIndex, Double value) {
  return 1;
 }

 private int writeToCell(int rowIndex, int colIndex, String value) {
  return 5;
 }
}
crowne