Let's say there's a class A with the lambda function named foo (for simplicity).
From my understanding, if there is a client serializing this foo (which would be serialized to "SerializedLambda") and send it to the server, the server would also have the class A with the lambda function named foo.
Now, my question is, what if the implementation of A.foo is different between the client and the server? (assuming the arg types and the return types are the same)
Does the server
deserialize "SerializedLambda" according to its own definition with no errors.
fail to deserialize.
Given how lambdas are created dynamically and "SerializedLambda" itself just contains signature, arguments and etc instead of the actual code, I suspect it's 1, but I am a newbie who needs to learn more about how this works.
Lambda expressions are compiled into synthetic methods with an unspecified name. There can be other unspecified subtleties, like the method’s argument order for captured variables. Further, when the lambda expression accesses the this instance, there are two options, compile it to an instance method or compile it to a static method receiving the instance like an ordinary parameter.
For the resulting behavior of the lambda expression, this makes no difference. But the serialized form depends on these details and hence, is very fragile. You don’t even need to change the class, recompiling it with a different compiler could change these details. In fact, even recompiling with the same compiler could change the outcome, e.g. when the compiler’s behavior depends on some hash map with iteration order randomization.
In practice, compiler vendors try to reduce such effects and produce stable results, even when not being mandated by the specification. But when you change the class, all bets are off. One aspect you can easily see in the compiled class file is that compilers add a number to the method name, depending on the order of occurrence in the source file. Inserting another lambda expression or removing one can change the numbers of all subsequent lambda expressions.
So, a changed A class can be incompatible to your serialized lambda expression, and the best thing that can happen, is an exception during deserialization due to a mismatch. Worse than that, it could select the wrong lambda expression, because it happened to have a compatible combination of name and signature.
A safer construct is method references, as they refer to the actual target method, which is not affected by other lambda expressions or method references within the same class. However, not every method reference is compiled as a direct reference on the byte code level. In certain cases, like when referring to a varargs method or calling a super method or a method on an intersection type whose declaring class does not match the erased type, the compiler may generate a stub method for the invocation, similar to a lambda expression. So you still have to be careful.
Related
From the documentation of java.lang.Class::getDeclaredMethods:
Returns an array containing Method objects reflecting all the declared methods of the class or interface represented by this Class object, including public, protected, default (package) access, and private methods, but excluding inherited methods. The declared methods may include methods not in the source of the class or interface, including bridge methods and other synthetic methods added by compilers.
If this Class object represents a class or interface that has multiple declared methods with the same name and parameter types, but different return types, then the returned array has a Method object for each such method...
AFAIK java doesn't allow a class or an interface to have methods differing only by their return type(return type is not part of a function's signature), so how would this happen?
To understand why this is possible, we need to understand that there are actually two worlds in Java: the Java language world, governed by the JLS, and the Java Virtual Machine world, governed by the JVM Specification.
Those worlds do not always match 1:1. You found one example. Another example are generics. Due to type erasure (oracle.com) the types are replaced with the upper bound through the compiler, thus all type information is lost at runtime. This is the reason why we cannot call .class on a generic parameter T.
Normally as programmers, we only interact with the Java language, and the compiler takes care of transforming the code into Bytecode that is executed on the JVM. Reflection, on the other hand, allows us to interact with objects at runtime. Thus, we get a glimpse into this world. In this specific case, the JVM Specification, $4.7.9.1 (docs.oracle.com) states:
...
A method signature encodes type information about a (possibly generic) method declaration. It describes any type parameters of the method; the (possibly parameterized) types of any formal parameters; the (possibly parameterized) return type, if any; and the types of any exceptions declared in the method's throws clause.
MethodSignature:
[TypeParameters] ( {JavaTypeSignature} ) Result {ThrowsSignature}
Result:
JavaTypeSignature
VoidDescriptor
ThrowsSignature:
^ ClassTypeSignature
^ TypeVariableSignature
...
I'm beginner at Lambda Expressions and I'm trying to learn them.
in this video (at 4:49) the teacher said:
the Java compiler says: "Hey. if you're writing a lambda expression,
don't tell me what the return type is. I can look at the code and
figure it out."
ok. I'm ok with that.
But in this video, after writing this line of code (myLambdaFunction = () -> System.out.println("HelloWorld");), (at 0:55) he said:
the question that we were struggling with is what's the type of this
variable (myLambdaFunction) ? it's a variable that contains a lambda
expression; a lambda function. is it a type called 'FunctionType'
in Java 8? this FunctionType if it existed would have had to provide
all the inputs for what this function is going to be like. it had to
tell what the input argument is, what the return type is. something
like this:
FunctionType<void,void> myLambdaFunction = () -> System.out.println("HelloWorld");
but the Java language designers didn't really do this. they didn't
create a new type called Functiontype. they actually said: "hey we
have this really nice system in Java to declare functions. to declare
methods. and that's the interface. " so they have reused the same
construct for declaring lambda expressions as well.
My problem is why he said:
the type would have had to provide all the inputs for what this
function is going to be like. it had to tell what the input argument
is, what the return type is
Why the type should provide input arguments and the return type? we know that the compiler is smart enough to find them from the lambda expression.
from () -> System.out.println("HelloWorld"); the java compiler can understand that we have no input arguments and our return type is void.
then why we should provide these information in the type? (I know that we provide them as signature of the single method of the Functional interface.)
I haven't gone through those videos but I scanned the first one around the time you mentioned and I think the answer is that there are two different concerns addressed in these two videos.
The first video describes how to construct a lambda. And the narrator goes through the process of stripping away all the unnecessary stuff from a function definition to create the lambda expression so it (the lambda) can be assigned to a variable. This video (in the section that I watched) had nothing to say about the variable the lambda was being assigned to.
The second video (from your description) seems to address the other side of the assignment - the variable that the lambda is being assigned to. Here you see that you need to declare the variable and it appears to be describing what is necessary to do just that. You can't just put a name out there without declaring what that name represents. It is a variable with a type. What is the type of the variable. Since FunctionType is a generic class it has to be provided the proper type parameters in the declaration.
I'm not a regular Java user so I don't know if Java has the equivalent of the auto type in C++ where the compiler can infer the type of a variable from the type of the expression used in an assignment declaration. If so then you could probably use that as well. If not, then you are stuck with declaring the variable (including its type) as described in the video. But, in either case, that variable has a type. This appears to be a description of how to determine the exact type of that variable.
And, presumably, you will need to know that type as well. If you pass that variable as a parameter to a function then the type of the variable must be the same as (or convertible to) the type of the parameter. Stating explicitly, at the point of declaration, what type you expect that variable to be is good code documentation.
Edit
After looking at the relevant section in the second video I see he is describing SAMs (interfaces with a Single Abstract Method). The principle is the same as what I said above but the syntax is a bit different. The compiler can check the types of the interface function as well as determine the type of the lambda expression itself and guarantee that they match when the variable is declared.
I am trying to understand why we have polymorphism / dynamic binding with overridden methods, but not overloaded methods. I understand that there’s indirection that allows us to index into a vtable to allow us to do this in a language like C++ or Java. I’m curious why the same isn’t the case for resolving overloaded methods — my intuition leads me to believe we could have a level of indirection that allows us to determine at runtime which overloaded method to invoke based on the runtime type.
I’m wondering if this design decision was made for performance reasons or if there’s extra complexity I’m neglecting to consider.
I have not read the minds of the language designers, so cannot really tell you. I am thinking of it in this way:
Overridden methods are different implementations of the same method at different levels in the superclass/subclass hierarchy. The subclass generally uses the same method signature (it’s allowed to return a more specific type and to declare fewer exceptions to be thrown, but it cannot completely redefine the method header, or it will no longer be an override).
Overloaded methods are really just different methods that happen to have the same name. Then the parameter types are used for distinguishing. Just like the compiler always decides on compile time which method to call, this is also the case with overloaded methods.
As an observation (possibly a minor one), with runtime resolution of overloaded methods we could no longer statically type check the return value. Imagine we have
public boolean foo(Number n);
public String foo(Integer i);
Now I would find it perfectly natural to call the former foo() like this:
boolean result = foo(myNumber);
Now if myNumber happened to be an Integer, the latter foo() would be called instead. It would return a String and I would have a type conversion error happening on runtime. I would not be amazed.
… why then we can still have runtime polymorphism and it be considered
static typing, but it wouldn't be if we did dynamic resolution of
overloaded methods.
Java has both: a static type and a runtime type. When I store an Integer into a variable declared to be a Number, then Number is the static type and Integer is the runtime type (BTW a type is not the same as a class). And you are correct, when I do myObject.foo(arg), then the runtime type of myObject decides which implementation of foo() gets called. And conceivably the runtime type of arg could have been involved in the decision too. It would get more complicated, and I am unsure about the gain.
Overloaded methods are methods that have the same name but take different parameters. You can simply see the types and order of the parameters it takes (also the type of value it returns) as part of the name.
In C++ this is actually somewhat more "exposed" - this language internally tweaks the names to match the parameters, void h(int, char) becomes something like h__Fic and void h(int) like h__Fi. These mangled names can be occasionally seen in places like error messages about them not being resolved. In Java, this is less exposed but internally comparable. This is also called the "signature".
Then you question simplifies to "if the methods have different names, why are they resolved at the compilation time and not a the run time?". The resolution between the two methods with different signatures cannot change over time, so there is simply no reason and no benefit to delay it.
I am working on this project currently. It works surprisingly well.
Yet, after re-reading the README again, I started to wonder about how to document something that is bugging me...
To quote the example, and forgetting for a moment that exceptions can be thrown, it reads:
Files.list(somePath).map(Path::toRealPath).forEach(System.out::println)
OK. Now, the method of Path involved is this one. Of course, we do not pass any LinkOption.
Again: let's forget for a moment that it throws any exception.
Stream's .map() takes a Function as an argument. This interface, for Function<T, R>, is defined as:
R apply(T t);
But the method I am using accepts no arguments. At a first glance, it does not seem to match a Function, right? Except that...
It can be written as:
path -> path.toRealPath()
It therefore looks like the mechanism used is somewhat able to invoke a method on the "stream object" if the method reference has no argument, or something like that...
I'd like to document this accordingly, and I am missing something here.
What am I missing?
Non-static methods have the receiver (this) object as an implicit first argument. Therefore, Class::nonStaticMethod has one more argument than you might expect.
Java Language Specification Section 15.13.1, Compile-Time Declaration of a Method Reference:
Second, given a targeted function type with n parameters, a set of potentially applicable methods is identified:
If the method reference expression has the form ReferenceType :: [TypeArguments] Identifier, the potentially applicable methods are the member methods of the type to search that have an appropriate name (given by Identifier), accessibility, arity (n or n-1), and type argument arity (derived from [TypeArguments]), as specified in §15.12.2.1.
Two different arities, n and n-1, are considered, to account for the possibility that this form refers to either a static method or an instance method.
Let the following be a class in my problem:
class MyClass {
String name() {
return toString();
}
}
I want to create an instance of MethodType which describes a method with a return that is "any" object. I am trying the following:
MethodType genericTypeWithObjectReturn =
MethodType.methodType(Object.class, new Class[] {});
then try to find a method using
MethodHandle mh =
lookup.findVirtual(MyClass.class, "name", genericTypeWithObjectReturn);
Using the above line, I get an exception:
Caused by: java.lang.NoSuchMethodError: MyClass.name()Ljava/lang/Object;
My observation is that the method return type should be exactly the same type; namely I should have used String.class in the above statement to define MethodType. Is this correct? Is there a way so that I can do this in the way I described and preferrably not using reflection API?
since an answer is still missing...
The MethodType has to be exact. That's a big pain point in the MethodHandles-API, but only because you control the transformation are some of the optimizations possible. The solution is to use reflection to find the method and then use MethodHandles#unreflect* to get the handle.
There's no way to use the find{Virtual,Static} methods to get method handles without specifying the exact method type, including the return type. This is a consequence of the JVM allowing overloading on return type, even though the Java programming language does not. From the documentation for Class.getMethod (emphasis mine):
To find a matching method in a class or interface C: If C declares exactly one public method with the specified name and exactly the same formal parameter types, that is the method reflected. If more than one such method is found in C, and one of these methods has a return type that is more specific than any of the others, that method is reflected; otherwise one of the methods is chosen arbitrarily.
Note that there may be more than one matching method in a class because while the Java language forbids a class to declare multiple methods with the same signature but different return types, the Java virtual machine does not. This increased flexibility in the virtual machine can be used to implement various language features. For example, covariant returns can be implemented with bridge methods; the bridge method and the method being overridden would have the same signature but different return types.
Thus JVM methods can be made inaccessible to Class.getMethod by the presence of other methods with the same signature but different (not-more-specific) return types. Given that the purpose of method handles is to support other languages on the JVM, including languages that either support overloading on return type in the source or that generate return type overloads as part of mapping to the JVM, this hole had to be fixed by requiring the exact type to be specified. (You could imagine a method pow(int, int) with overloads returning int, long, and BigInteger, selected by the source language based on the context type in foo(pow(1000, 100000));.)
Apart from that hard requirement to make all methods accessible, it's arguably better from an API design standpoint to fail fast if the desired method is not found rather than silently select an argument-compatible but different method.
If you're happy with the lookup semantics of Class.getMethod, you can use it and call MethodHandles.Lookup.unreflect on the returned Method.
yes. you should use String.class for resolving right method type.
as i know you can't implement it with method handles api, but you can use Reflection API.