Как переменная 'this' в Java фактически установлена на текущий объект?
Рассмотрим:
class TestParent{
public int i = 100;
public void printName(){
System.err.println(this); //{[email protected]} according to the Debugger.
System.err.println(this.i); //this.i is 100.
}
}
class TestChild extends TestParent{
public int i = 200;
}
public class ThisTest {
public static void main(String[] args) {
new TestChild().printName();
}
}
Я знаю, что были заданы подобные вопросы, но я не смог получить четкое представление о переменной 'this' в Java.
Позвольте мне объяснить, как я понимаю результат вышеупомянутого изображения.
-
Поскольку объект new TestChild()
, вызывающий метод printName()
, переменная this
в строке 6 устанавливается в объект TestChild
- {TestChild @428} в соответствии с отладчиком.
-
Однако, поскольку Java не имеет виртуального поля - я не совсем уверен, что это значит, но я концептуально понимаю это как противоположность Java-методам, которые поддерживают Полиморфизм - this.i
до 100 из TestParent
во время компиляции.
-
Итак, независимо от того, что this
, this.i
в методе TestParent
всегда будет переменной i
в классе TestParent
.
Я не уверен, что мое понимание правильное, поэтому, пожалуйста, исправьте меня, если я ошибаюсь.
А также, мой главный вопрос:
Как переменная this
установлена на текущий объект, вызывающий метод? Как это реализовано?
Ответы
Ответ 1
В сущности, нет разницы между
this.foo()
и
anyObject.foo()
поскольку оба реализованы одинаково. Имейте в виду, что "в конце концов" объектная ориентация - это только абстракция, а в "реальности" происходит что-то вроде:
foo(callingObject)
Другими словами: всякий раз, когда вы используете какую-либо ссылку на объект для вызова метода... в конце концов нет объекта на. Потому что в глубине ассемблера и машинного кода чего-то вроде "вызова на что-то" не существует.
Что действительно происходит, это вызов функции; и первым (неявным/невидимым по уровню исходного кода) является этот объект.
Кстати: вы можете записать это на Java, например:
class Bar {
void foo(Bar this) { ... }
а затем используйте
new Bar().foo();
И для this.fieldA, в конце: у вас есть ссылка на какое-то место в памяти; и таблицу, в которой говорится о том, на каком "смещении" вы найдете поле А.
Изменить - только для записи. Если вас интересует более подробная информация о foo (Bar this), вы можете обратиться к этому question; предоставляя детали в спецификации Java за ней!
Ответ 2
Что здесь происходит, так это два совершенно разных поля, называемых i
; для использования их полных имен, один TestParent::i
, а один - TestChild::i
.
Поскольку метод printName
определяется в TestParent
, когда он ссылается на i
, он может видеть только TestParent::i
, который установлен в 100.
Если вы установили i
в 200 в TestChild
, оба поля, называемые i
, видны, но поскольку они имеют одинаковое имя, TestChild::i
скрывает TestParent::i
, и вы заканчиваете установку TestChild::i
и оставляете TestParent::i
нетронутой.
Ответ 3
Хорошо, когда создается новый объект, у объекта есть адрес в памяти, поэтому вы можете думать об этом, как будто у объекта был закрытый член this
, который задан адресом при создании объекта. Вы также можете думать об этом так: obj.method(param)
- это просто синтаксический сахар для method(obj, param);
и this
- фактически параметр method
.
Ответ 4
Чтобы напрямую обращаться к тому, что вы видите на выходе: вызов для печати "this.i" передается как аргумент "print()" значение поля "i" в текущей области действия, которое является областью родительский класс. Напротив, вызов для печати 'this' переводится под капотом на вызов для печати 'this.getClass(). GetName()' [грубо говоря], а вызов getClass() получает фактический объект класса, который предназначен для дочернего класса.
Ответ 5
Добавление дополнительной информации поверх ответа @Tom Anderson, в котором объясняется скрытая концепция.
Я добавил еще один конструктор в Child (TestChild
), который печатает значения я в родительском и дочернем.
Если вы хотите получить значение i
от child (TestChild
), переопределите метод в TestChild
.
class TestParent{
public int i = 100;
public void printName(){
System.err.println("TestParent:printName()");
System.err.println(this); //{[email protected]_NUM} according to the Debugger.
System.err.println(this.i); //this.i is 100.
}
}
class TestChild extends TestParent{
public int i = 200;
public TestChild(){
System.out.println("TestChild.i and TestParent.i:"+this.i+":"+super.i);
}
public void printName(){
//super.printName();
System.err.println("TestChild:printName()");
System.err.println(this); //{[email protected]_NUM} according to the Debugger.
System.err.println(this.i); //this.i is 200.
}
}
public class ThisTest {
public static void main(String[] args) {
TestParent parent = new TestChild();
parent.printName();
}
}
Случай 1: если я прокомментирую вызов super.printName()
от дочернего, дочерняя версия TestChild.printName()
печатает значение я в TestChild
Выход:
TestChild.i and TestParent.i:200:100
TestChild:printName()
[email protected]
200
Случай 2: TestChild.printName() вызывает super.printName() как первую строку в методе printName(). В этом случае значение я из родительского и дочернего элементов отображается в соответствующих методах.
Выход:
TestChild.i and TestParent.i:200:100
TestParent:printName()
[email protected]
100
TestChild:printName()
[email protected]
200