When using map with method reference in Java, I met following problem:
public class Dummy {
public static void main(String[] args) {
IntegerHolder ih = new IntegerHolder();
Optional<IntegerHolder> iho = Optional.of(ih);
iho.map(IntegerHolder::getInteger).map(Objects::toString);
iho.map(IntegerHolder::getInteger).map((Integer ii) ->ii.toString());
iho.map(IntegerHolder::getInteger).map(Integer::toString);// this line will not compile. The error is "non-static method toString() cannot be referenced from a static context"
}
private static class IntegerHolder {
private Integer i;
Integer getInteger() {return i;}
}
}
It looks to me that Integer::toString is same as the IntegerHolder::getInteger. Both are "Reference to an Instance Method of an Arbitrary Object of a Particular Type"
I do not understand why one works, but the other does not.
Could you please shed some light on this question? Thank you very much.
The error is very misleading, in java-11 for example the error would make a lot more sense:
reference to toString is ambiguous
both method toString(int) in Integer and method toString() in Integer match)
If you re-write this method via a lambda expression, you will see that both signatures can potentially match:
iho.map(IntegerHolder::getInteger).map((Integer ii) -> Integer.toString(ii));
iho.map(IntegerHolder::getInteger).map((Integer ii) -> ii.toString());
both of these can be re-written as a method reference, but in such a case, which method to call?
Related
This question already has answers here:
Why does a Java method reference with return type match the Consumer interface?
(2 answers)
Closed 4 years ago.
public class SomeClass{
public static int someFunction(int a) {
return a;
}
public static void main(String[] args) {
Consumer<Integer> c = SomeClass::someFunction;
}
}
I'm not getting why: Consumer<Integer> c = SomeClass::someFunction;
is not producing a compilation error, since the function someFunction is a method with return value, and Consumer is representing methods with no return value
From the spec:
If the body of a lambda is a statement expression (that is, an
expression that would be allowed to stand alone as a statement), it is
compatible with a void-producing function type; any result is simply
discarded.
Same is true for method references.
It's more flexible that way. Suppose it was a compiler error to not use a return value when you called a method normally - that would be incredibly annoying. You'd end up having to use fake variables you didn't care about in some cases.
public class SomeClass
{
public static int someFunction(int a) {
return a;
}
public static void main(String[] args) {
someFunction(3); // "error" - ignoring return type
int unused = someFunction(3); // "success"
}
}
If you want a the full formal definition of what is acceptable, see 15.13.2. Type of a Method Reference.
This is called special void compatibility rule. For example how many times have you actually cared about List#add return type? Even if it does return true/false.
Pretty much the same thing here, you can invoke a method, but ignore its result. If you re-write your consumer as a lambda expression, it makes more sense:
Consumer<Integer> c = x -> {
SomeClass.someFunction(x);
return;
}
If I remember correctly from the JLS there are only some types that are allowed for this.
increment/decrement operations
method invocation
assignment
instance creation
I have added three methods with parameters:
public static void doSomething(Object obj) {
System.out.println("Object called");
}
public static void doSomething(char[] obj) {
System.out.println("Array called");
}
public static void doSomething(Integer obj) {
System.out.println("Integer called");
}
When I am calling doSomething(null) , then compiler throws error as ambiguous methods. So is the issue because Integer and char[] methods or Integer and Object methods?
Java will always try to use the most specific applicable version of a method that's available (see JLS ยง15.12.2).
Object, char[] and Integer can all take null as a valid value. Therefore all 3 version are applicable, so Java will have to find the most specific one.
Since Object is the super-type of char[], the array version is more specific than the Object-version. So if only those two methods exist, the char[] version will be chosen.
When both the char[] and Integer versions are available, then both of them are more specific than Object but none is more specific than the other, so Java can't decide which one to call. In this case you'll have to explicitly mention which one you want to call by casting the argument to the appropriate type.
Note that in practice this problem occurs far more seldom than one might think. The reason for this is that it only happens when you're explicitly calling a method with null or with a variable of a rather un-specific type (such as Object).
On the contrary, the following invocation would be perfectly unambiguous:
char[] x = null;
doSomething(x);
Although you're still passing the value null, Java knows exactly which method to call, since it will take the type of the variable into account.
Each pair of these three methods is ambiguous by itself when called with a null argument. Because each parameter type is a reference type.
The following are the three ways to call one specific method of yours with null.
doSomething( (Object) null);
doSomething( (Integer) null);
doSomething( (char[]) null);
May I suggest to remove this ambiguity if you actually plan to call these methods with null arguments. Such a design invites errors in the future.
null is a valid value for any of the three types; so the compiler cannot decide which function to use. Use something like doSomething((Object)null) or doSomething((Integer)null) instead.
Every class in Java extends Object class.Even Integer class also extends Object. Hence both Object and Integer are considered as Object instance. So when you pass null as a parameter than compiler gets confused that which object method to call i.e. With parameter Object or parameter Integer since they both are object and their reference can be null. But the primitives in java does not extends Object.
I Have tried this and when there is exactly one pair of overloaded method and one of them has a parameter type Object then the compiler will always select the method with more specific type. But when there is more than one specific type, then the compiler throws an ambiguous method error.
Since this is a compile time event, this can only happen when one intentionally passes null to this method. If this is done intentionally then it is better to overload this method again with no parameter or create another method altogether.
class Sample{
public static void main (String[] args) {
Sample s = new Sample();
s.printVal(null);
}
public static void printVal(Object i){
System.out.println("obj called "+i);
}
public static void printVal(Integer i){
System.out.println("Int called "+i);
}
}
The output is Int called null and so ambiguity is with char[] and Integer
there is an ambiguity because of doSomething(char[] obj) and doSomething(Integer obj).
char[] and Integer both are the same superior for null that's why they are ambiguous.
Having the following simple method in Java 8:
public void test(){
Stream<Integer> stream = Stream.of(1,2,3);
stream.map(Integer::toString);
}
and I get two errors:
java: incompatible types: cannot infer type-variable(s) R
(argument mismatch; invalid method reference
reference to toString is ambiguous
both method toString(int) in java.lang.Integer and method toString()
in java.lang.Integer
and :
invalid method reference non-static method toString() cannot be
referenced from a static context
The first error is understandable, Integer class has two methods:
public static String toString(int i)
public String toString()
and compiler cannot infer the desired method reference.
But regarding the second one, where is the static context that the compiler refer to?
The error is related to method toString() of Integer class that is not static, but why the context that I call that method using map() is static?
Yet another question, if the compiler has to solve an ambiguity between two methods that the one causes compile time error shouldn't he choose the other one?
The second error is a red-herring. It exposes some of the inner workings of the compiler. The problem is that there is the ambiguity issue, the second one is a consequence of that and can be ignored. What it is probably doing is as follows.
It checks to see if there is a static method that matches the
"valid" signatures. There is, so it assumes that static is the way
to go. This strongly implies that there is a "preference" of sorts
in the compiler for static methods, although this is probably
arbitrary.
It then goes to find the first method that matches the signature.
It's not static, so it gets confused because it previously DID find
a static method with that signature.
Somewhere in the mix it ALSO finds that the reference is ambiguous. It's not really clear whether step 1 or 2 is where this happens, but the compiler does not abort because it is trying to be helpful and find further compile errors.
The compiler could theoretically handle this better because that second message is confusing.
NOTE: The Eclipse compiler does not show the second error.
The explanation why we get there two errors is the method reference Integer::toString can be a reference
to an instance method of an object of a particular type
to a static method
Following snippets should demonstrate what the compiler choose in the both cases for Integer::toString.
instance method i.toString() would be chosen
static class MyInteger {
int value;
public MyInteger(int i) {
this.value = i;
}
public String toMyString() {
return "instance " + value;
}
}
Stream<MyInteger> stream = ...
stream.map(MyInteger::toMyString).forEach(System.out::println);
/* which would be equivalent to
stream.map(new Function<MyInteger, String>() {
public String apply(MyInteger t) {
return t.toMyString();
}
});
// as method argument for stream.map() the compiler generates
invokevirtual MyInteger.toMyString:()Ljava/lang/String;
*/
static method Integer.toString(i) would be chosen
static class MyInteger {
int value;
public MyInteger(int i) {
this.value = i;
}
public static String toMyString() {
return "static " + value;
}
}
Stream<MyInteger> stream = ...
stream.map(MyInteger::toMyString)..forEach(System.out::println);
/* which would be equivalent to
stream.map(new Function<MyInteger, String>() {
#Override
public String apply(MyInteger t) {
return MyInteger.toMyString(t);
}
});
// as method argument for stream.map() the compiler generates
invokestatic MyInteger.toMyString:(LMyInteger;)Ljava/lang/String;
*/
In the first pass the parser tries to find a method which could be invoked on an object instance. As both methods toMyString() and toMyString(MyInteger) could be invoked on an object of type MyInteger and both fulfill the requirement for Function<? super T,? extends R> we get the first error reference to toString is ambiguous.
(see in the source: com.sun.tools.javac.comp.Resolve.mostSpecific(...)).
In the second pass the parser tries to find a static method toString. As the reference to the (previously resolved) method toString() is not static we get the second error non-static method toString() cannot be referenced from a static context.
(see in the source: com.sun.tools.javac.comp.Resolve.resolveMemberReference(...)).
edit A small example to explain the reason for the two errors. The parser of the javac cannot know what you intent to do at Integer::toString. You could mean i.toString() or Integer.toString(i) so he do the validation for both cases. It's the way he works also in other situations.
For demonstration take this example:
class Foo {
int x + y = 1;
}
The reported errors are
Scratch.java:2: error: ';' expected
int x + y = 1;
^
Scratch.java:2: error: <identifier> expected
int x + y = 1;
^
In this small snippet the parser also don't know what's your intent. See few possibilities.
int x; y = 1; // missed the semicolon and the declaration for `y`
int x = y = 1; // typo at `+`
int x = y + 1; // swapped `+` and `=` and missed declaration of `y`
... more possibilities exist
In this case the parser also don't stop right after the first error.
The default return of an object in Java is to return the toString method, is there a way to change this behavior to for example return a number or some other type or is this just a core unchangeable component of the language?
Test test = new Test();
blankMethod(test);
I am not sure why people are saying my assumption is wrong... if I Override the toString method of object it will output that new toString method...
#Override
public String toString() {
System.out.println("This method is run when the object is used as a parameter");
return "test";
}
The default return of an object in Java is to return the toString method
That's how PrintStream.println works. Any object in java extends java.lang.Object and therefore inherits Object behavior. In particular, any method which takes Object as an argument, also can take any object of another type. However, you can override any public or protected method defined in Object. In your case you should override toString() method in Test class, otherwise if you pass Test object to PrintWriter.println(), it'll use toString from superclass (Object in your case). So, if i understood your question correctly, the answer is no.
Update: your terminology is wrong. There is no default return of an object in java. Returning Object.toString is the default behavior only for PrintStream.println(Object).
Update2: more widely, Java doesn't support implicit type conversion except upcasting. In your case, Test extends Object, but it doesn't extend String (actually, String is final and therefore you can't extend it). So, if you try to pass Test object to method which only takes String, you'll get compilation error because String and Test belong to different branches of class hierarchy.
You are actually calling
PrintStream.println(Object x)
instead of
public void println(String x)
The PrintStream.println(Object x) implementation is below:
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
The method calls String.valueOf(x) whose implementation is below:
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
Note that the Object.toString() method is being called.
Notes on implicit conversion
An Object is only implicitly converted to a String using the toString method when the string concatenation operator (+) is invoked. More exactly the conversion works as follows:
A value x of primitive type T is first converted to a reference value...
This reference value is then converted to type String by string conversion.
Now only reference values need to be considered.
If the reference is null, it is converted to the string "null" (four
ASCII characters n, u, l, l). Otherwise, the conversion is performed
as if by an invocation of the toString method of the referenced object
with no arguments; but if the result of invoking the toString method
is null, then the string "null" is used instead.
The toString method is defined by the primordial class Object"
Java Language Specification 15.18.1
One language (of many) that expand on the idea of implicit conversions is Scala which allows one to bring a conversion (e.g., A -> B) into scope which is then invoked whenever some type (A) is passed as an argument when a different type (B) is required.
This answers "why people are saying my assumption is wrong".
Here is a simple program:
public class Test {
public String toString(){
return "I am a Test object.";
}
public int add(int a, int b){
return a+b;
}
public static void main(String[] args) {
Test object = new Test();
System.out.println(object);
System.out.println(object.add(2,2));
System.out.println(adder(object));
}
public static int adder(Test test){
return test.add(3,5);
}
}
Output:
I am a Test object.
4
8
The System.out.println method has to turn its argument into a String, one way or another. If it is passed an object reference, it uses its toString() method. If it is passed a primitive, such as an int expression, it converts it in an appropriate way.
It is nothing to do with what is being returned.
It also has nothing to do with any automatic conversion on passing a reference as an argument. See the third line of output, which depends on using the Test reference I passed to adder as a Test, not a String.
If you want to change what println prints, do what I did above, and pass it something different.
So here it is this example
public static void main(String[] args) {
new Stuff(null);
new Stuff("a");
new Stuff(1);
}
and class Stuff is defined as follow
public class Stuff {
Stuff(Object o){
System.out.println("object");
}
Stuff(String s){
System.out.println("string");
}
}
The output is
string
string
object
How does Java tell the null is a String? If I change Stuff to
public class Stuff {
Stuff(String s){
System.out.println("string");
}
Stuff(Integer o){
System.out.println("Integer");
}
}
I get compilation error for Stuff(null):
The constructore Stuff(String) is ambigous.
Again, why does Java "decide" null is a String?
The compiler first lists all applicable methods. In your case, both are applicable.
It then tries to find a method which is more specific than the other(s).
In your first example, String is a subclass of Object and is therefore more specific.
In your second example, both methods are applicable (String and Integer) but neither is more specific than the other (String is not a subclass of Integer which is not a subclass of String). So there is an ambiguity, hence the compiler error.
The full algorithm to determine which method should be chosen is defined in the JLS.
Because String is more specific then Object. Java always tries to use more specific match when figuring out which constructor or method to use.