Я пытаюсь понять, как работают ссылки на методы в java. На первый взгляд это довольно просто. Но не когда дело касается таких вещей:
В классе Foo есть метод:
public class Foo {
public Foo merge(Foo another) {
//some logic
}
}
А в другом классе Bar есть такой метод:
public class Bar {
public void function(BiFunction<Foo, Foo, Foo> biFunction) {
//some logic
}
}
И используется ссылка на метод:
new Bar().function(Foo::merge);
Он соответствует и работает, но я не понимаю, как это соотносится с этим:
Foo merge(Foo another)
в метод BiFunction:
R apply(T t, U u);
???
Если merge(...) является методом экземпляра Foo, то он имеет неявный аргумент this.
Значит, я могу относиться к Foo merge(Foo another) как к Foo merge(Foo this, Foo that)? Это не очевидно.
@KirillBazarov это не на первый взгляд. Но если вы знаете, как на самом деле работают вызовы методов и где хранятся методы, становится очевидным, что для каждого метода экземпляра существует неявный аргумент this. запись someObject.doSomethingWith(someOtherObject) - удобная (и более привлекательная) альтернатива doSomethingWith(someObject, someOtherObject).
... также называется Reference to an instance method of an arbitrary object of a particular type
@KirillBazarov Дело не в том, что к Foo merge(Foo another) можно относиться как к Foo merge(Foo this, Foo another). Так оно и есть. Вы даже можете объявить свой метод merge для получения this, и он отлично скомпилируется. Это просто тот, кто явно объявит неявный аргумент this, если он не нужен ... Но на низком уровне это просто способ, которым он работает.




Мне легче разобраться с разными типами:
public class A {
public void test(){
function(A::merge);
}
public void function(BiFunction<A, B, C> f){
}
public C merge(B i){
return null;
}
class B{}
class C{}
}
Мы можем знать, что использование ссылки на метод Test::merge вместо ссылки на экземпляр будет неявно использовать this в качестве первого значения.
15.13.3. Оценка во время выполнения ссылок на методы
If the form is ReferenceType :: [TypeArguments] Identifier
[...]
If the compile-time declaration is an instance method, then the target reference is the first formal parameter of the invocation method. Otherwise, there is no target reference.
И мы можем найти пример такого поведения по следующей теме:
В JLS - 15.13.1. Объявление ссылки на метод во время компиляции упоминается:
A method reference expression of the form
ReferenceType::[TypeArguments] Identifiercan be interpreted in different ways.
- IfIdentifierrefers to an instance method, then the implicit lambda expression has an extra parameter [...]
- ifIdentifierrefers to a static method. It is possible forReferenceTypeto have both kinds of applicable methods, so the search algorithm described above identifies them separately, since there are different parameter types for each case.
Затем он показывает некоторую двусмысленность, возможную с этим поведением:
class C {
int size() { return 0; }
static int size(Object arg) { return 0; }
void test() {
Fun<C, Integer> f1 = C::size;
// Error: instance method size()
// or static method size(Object)?
}
}
Я верю 15.13.3. объясняет это, если я правильно понимаю. Затем 15.13.1 добавляет обе интерпретации синтаксиса ReferenceType :: [TypeArguments] idetifier. Подтвердите или опровергните мое обещание (я в этом сомневаюсь).
В методах экземпляра есть неявный аргумент this. Это определяется §3.7 спецификации JVM:
The invocation is set up by first pushing a reference to the current instance, this, on to the operand stack. The method invocation's arguments, int values 12 and 13, are then pushed. When the frame for the addTwo method is created, the arguments passed to the method become the initial values of the new frame's local variables. That is, the reference for this and the two arguments, pushed onto the operand stack by the invoker, will become the initial values of local variables 0, 1, and 2 of the invoked method.
Чтобы понять, почему вызов метода выполняется таким образом, нам нужно понять, как JVM хранит код в памяти. Код и данные объекта разделены. Фактически, все методы одного класса (статические и нестатические) хранятся в одном месте - область метода (§2.5.4 спецификации JVM). Это позволяет сохранять каждый метод только один раз вместо того, чтобы повторно сохранять их для каждого экземпляра класса снова и снова. Когда такой метод, как
someObject.doSomethingWith(someOtherObject);
вызывается, он фактически компилируется во что-то, что больше похоже на
doSomething(someObject, someOtherObject);
Большинство Java-программистов согласятся, что someObject.doSomethingWith(someOtherObject) имеет «более низкую когнитивную сложность»: мы делаем что-то с someObject, которое включает someOtherObject. В центре этого действия находится someObject, где someOtherObject - всего лишь средство для достижения цели.
С doSomethingWith(someObject, someOtherObject) вы не переносите эту семантику, когда someObject является центром действия.
По сути, мы пишем первую версию, но компьютер предпочитает вторую версию.
Как было указано @FedericoPeraltaSchaffner, вы даже можете явно написать неявный параметр this, начиная с Java 8. Точное определение дается в JLS, §8.4.1:
The receiver parameter is an optional syntactic device for an instance method or an inner class's constructor. For an instance method, the receiver parameter represents the object for which the method is invoked. For an inner class's constructor, the receiver parameter represents the immediately enclosing instance of the newly constructed object. Either way, the receiver parameter exists solely to allow the type of the represented object to be denoted in source code, so that the type may be annotated. The receiver parameter is not a formal parameter; more precisely, it is not a declaration of any kind of variable (§4.12.3), it is never bound to any value passed as an argument in a method invocation expression or qualified class instance creation expression, and it has no effect whatsoever at run time.
Параметр получателя должен быть типа класса и иметь имя this.
Это значит, что
public String doSomethingWith(SomeOtherClass other) { ... }
а также
public String doSomethingWith(SomeClass this, SomeOtherClass other) { ... }
будет иметь то же семантическое значение, но последнее позволяет, например, аннотации.
Очень полезное объяснение. Спасибо
Это метод из класса
Foo? Если так, то это будет (эта, другая) BiFunction.