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.
Related
I'm confused about the functionality of type checking and method lookup in Java.
From what I understand, type checking is done at the compile time and method lookup is done at the run time.
Type checking is based on the declared type of the reference object whereas method lookup is based on the actual type of the reference.
So suppose the class MyInt is a superclass of the class GaussianInt as follows:
class MyInt
{
private int n;
public myInt(int n)
{
this.n = n;
}
public int getval()
{
return n;
}
public void increment(int n)
{
this.n += n;
}
public myInt add(myInt N)
{
return new myInt(this.n + N.getval());
}
public void show()
{
System.out.println(n);
}
}
class GaussInt extends MyInt
{
private int m; //represents the imaginary part
public GaussInt(int x, int y)
{
super(x);
this.m = y;
}
public void show()
{
System.out.println( "realpart is: " + this.getval() +" imagpart is: " + m);
}
public int realpart()
{
return getval();
}
public int imagpart()
{
return m;
}
public GaussInt add(GaussInt z)
{
return new GaussInt(z.realpart() + realpart(), z.imagpart() + imagpart());
}
And suppose in the main method we have the following:
GaussInt z = new GaussInt(3,4);
MyInt b = z;
MyInt d = b.add(b)
System.out.println("the value of d is:"+ d.show());
Which add method would be used in the show statement inside the print statement in the end?
From what I understand, b is declared to be MyInt, but it is, in fact, GuaussInt. The type checker only sees that b is of MyInt type and that it has add(MyInt) so the code makes sense and compiles.
But then in run time, the method lookup sees that b is of type GaussInt and it has two add() methods, so it will use add(GaussInt) method by looking at method signature and it produces a GaussInt. But d is of type MyInt and method lookup will think it won't work, then will it go back to add(Myint)?
How does the mechanism behind compiling and running of a program work?
From what I understand, b is declared to be MyInt, but it is, in fact,
GaussInt
You are CORRECT. b's reference type is MyInt but it is pointing to an object of GaussInt type.
But then in run time, the method lookup sees that b is of type
GaussInt and it has two add() methods, so it will use add(GaussInt)
method by looking at method signature and it produces a GaussInt. But
d is of type GaussInt and method lookup will think it won't work, then
will it go back to add(Myint)?
As the add method in GaussInt takes a reference of GaussInt type and not of MyInt type. So b.add(b) will call add method of MyInt type. Since the gaussInt has two add methods one take the argument of type MyInt and other takes the argument GaussInt type. So it will call add method of myInt(superclass).
The thing you are trying to achieve is method overriding. For it to work the method signatures should be same. That is, the parent and child class methods should match in every respect, except that the return type of child class method can be subtype of the return type of parent class method
SO in order to achieve what you have mentioned, that is b.add(b) should call add method of gaussInt, make the argument type of add method in both classes same.
Also what you should learn about is dynamic polynorphism(run time check) and static polymorphism(compile time type check).
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?
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
Primitives are at it again, breaking rules, I learned before. Well not technically primitive but composed of them.
I learned that whenever there's no method more specific than rest, compile time error occurs as it happens here.
public static void caller(){
z5(); // Error. Neither Integer, nor String is more specific
z5(null); // Error for the same reason
}
public static void z5(Integer...integers){
System.out.println("Integer z5 called");
}
public static void z5(String...strings){
System.out.println("String z5 called");
}
Now comes primitives into the picture.
public static void caller(){
z1(null); // Error cuz [I, [J, [F all are subclass of Object.
z1(); // SURPRISINGLY works and calls the int one. WHY?
}
public static void z1(int...integers){
System.out.println("int z1 called");
}
public static void z1(long...longs){
System.out.println("long z1 called");
}
public static void z1(float...floats){
System.out.println("float z1 called");
}
Expected compile time errors occurs here.
public static void caller(){
z1(null); // Error
z1(); // Error
}
public static void z1(int...integers){
System.out.println("int z1 called");
}
public static void z1(boolean...bools){
System.out.println("bool z1 called");
}
Now my question is, int[], float[], or any array of primitives are not primitive types then Why are they treated differently than other reference types?
--UPDATE--
#john16384 You don't think I read your "Possible duplicate" Varargs in method overloading in Java
Top answer there says You cannot combine var-args, with either widening or boxing. Besides I forgot to mention, OP's code posted there, works fine on my jdk 7.
What exactly is going on which works for (int...is) & (float...fs) but not for (Integer...is) & (Float...fs) and not for (int...is) & (boolean...bool)
Quote from the JLS about varargs invocations when multiple methods are applicable:
15.12.2.5. Choosing the Most Specific Method
If more than one member method is both accessible and applicable to a
method invocation, it is necessary to choose one to provide the
descriptor for the run-time method dispatch. The Java programming
language uses the rule that the most specific method is chosen.
The informal intuition is that one method is more specific than
another if any invocation handled by the first method could be passed
on to the other one without a compile-time error. In cases such as an
explicitly typed lambda expression argument (§15.27.1) or a variable
arity invocation (§15.12.2.4), some flexibility is allowed to adapt
one signature to the other.
The important part here is how methods are defined to be more specific. It basically says that int... is more specific than long... because any values you could pass to the first method could also be passed to the second method.
This will also apply to the case where you pass no arguments. int... will be the most specific (it will even see byte... as more specific!).
public static void main(String[] args) {
bla();
}
private static void bla(long... x) {}
private static void bla(int... x) {}
private static void bla(short... x) {}
private static void bla(byte... x) {} // <-- calls this one
The reason you get an error when also creating an overload boolean... is that it now is ambigious which one to call, and the compiler stops before getting to the point where it has to pick the most specific method.
I have problem understanding the below code(commented against the line number)
class Base {
void m1(Object o) {
}
void m2(String o) {
}
}
public class Overloading extends Base {
void m1(String s) {
}
void m2(Object o) {
}
public static void main(String[] args) {
Object o = new Object();
Base base1 = new Base();
base1.m1("");//**why this works perfect**
Base base = new Overloading();
base.m2(o);// **why compile time error** - The method m2(String) in the type Base is not applicable for the arguments (Object)
Compiler always resolves the method invocation based on the declared type of the reference you invoke it on.
When you invoke the method:
base1.m1("");
compiler looks for the method signature in declared type of base1, which is Base in this case. The matching method in Base is:
void m1(Object o) { }
Since parameter Object can accept a String argument, the invocation is valid. You can pass a subclass object to a superclass reference.
Now, with 2nd invocation:
base.m2(o);
again the declared type of base is Base. And the matching method in Base class is:
void m2(String o) { }
Since you cannot pass an Object reference where a String is accepted. The compiler gives you compiler error. There is no implicit narrowing conversion.
You can understand it more clearly with a simple assignment:
Object ob = new Integer(3);
String str = ob; // This shouldn't be valid
Java doesn't perform implicit narrowing conversion. The assignment from obj to str shouldn't be valid, because else you would get a ClassCastException at runtime.
At the line base.m2(o), the compiler doesn't know that base is an Overloading -- it only knows that it is a Base. Why has it forgotten? Because you told it to.
You told the compiler to treat base as a Base, by declaring it with Base base = ....
So, as per your instructions, the compiler will treat base as a Base without knowing anything about any subclass of Base it might or might not extend, and it (correctly) points out that base might not support m2 on an arbitrary Object.