"this" ключевое слово: рабочий механизм в Java
После изучения Java в какой-то момент его использование ключевого слова this
в первый раз сбило меня с толку.
Вот как я запутался. Я написал следующий код:
class BasicInheritanceTest3Base{
private int x = 0;
public int y;
public void a() {
x++;
this.x++;
System.out.println("BasicInheritanceTest3Base.a()");
b();
this.b();
System.out.println(x);
System.out.println(y);
}
public void b(){
System.out.println("BasicInheritanceTest3Base.b()");
}
}
public class BasicInheritanceTest3 extends BasicInheritanceTest3Base {
private int x = 3;
public int y = 2;
public void b() {
System.out.println("BasicInheritanceTest3.b()");
}
public static void main(String[] args){
BasicInheritanceTest3 bit2 = new BasicInheritanceTest3();
bit2.a();
}
}
Я получил следующий вывод:
BasicInheritanceTest3Base.a()
BasicInheritanceTest3.b()
BasicInheritanceTest3.b()
2
0
Теперь первый вопрос: Почему x
и this.x
указывают на x
базового класса, а не на класс Child? И если this.x
указывает на x
базового класса, почему this.b()
вызывает b()
дочернего класса? Является ли поведение этого разного для полей и методов?
Однако основная проблема связана с механизмом ключевого слова this
.
Я имею ввиду, что вы знаете, this
указывает (относится) к текущему объекту. Если вы думаете, это не волшебное поведение. Где-то должно быть поле this
. Например, литерал .class
для класса невидим, но присутствует в испускаемом байт-коде. Аналогично, эта ссылка должна присутствовать в байте-коде.
Хорошо, считая выше, верно, this
должен быть public final
(пустой финал), который создается при каждом создании объекта и его экземплярах. Это означает, что это переменная экземпляра, а не статическая переменная.
Теперь, если этот экземпляр ссылается на текущую ссылку на объект (который является только конкретным объектом), то почему выше использование this
отличается для полей и методов? Итак, в целом, каков механизм this
? Поддерживается ли этот механизм для ключевого слова super
?
РЕДАКТИРОВАТЬ:. Все читают вопрос, а затем комментарии, о которых я хочу спросить, где указано поле this
, объявленное компилятором, и каковы его квалификаторы. Как происходит возникшее в результате поведение за кулисами?
Ответы
Ответ 1
Другие ответы и комментарии объяснили, как поля не являются полиморфными и как разрешаются выражения доступа к полю на основе типа времени компиляции ссылки на экземпляр. Ниже я объясню, как байтовый код обрабатывает ссылку this
.
В главе "Получение аргументов" "Спецификация виртуальной машины Java" говорится
Если n аргументов передается методу экземпляра, они принимаются по соглашение, в локальных переменных с номерами от 1 до n кадра созданный для нового вызова метода. Аргументы получены в порядок, в котором они были приняты. Например:
int addTwo(int i, int j) {
return i + j;
}
скомпилируется:
Method int addTwo(int,int)
0 iload_1 // Push value of local variable 1 (i)
1 iload_2 // Push value of local variable 2 (j)
2 iadd // Add; leave int result on operand stack
3 ireturn // Return int result
По соглашению метод экземпляра передается ссылка на его экземпляр в локальной переменной 0. На языке программирования Java экземпляр доступен с помощью ключевого слова this
.
В классах (статических) методах нет экземпляра, поэтому для них это использование локальной переменной 0 не требуется. Метод класса начинает использовать локальный переменные с индексом 0. Если метод addTwo был методом класса, его аргументы будут переданы аналогично первой версии:
static int addTwoStatic(int i, int j) {
return i + j;
}
скомпилируется:
Method int addTwoStatic(int,int)
0 iload_0
1 iload_1
2 iadd
3 ireturn
Единственное различие заключается в том, что аргументы метода появляются начиная с локальная переменная 0, а не 1.
Другими словами, вы можете либо просмотреть this
, либо не объявляться нигде, либо объявить как первый параметр для каждого метода экземпляра. Локальная запись таблицы переменных создается для каждого метода экземпляра и заполняется при каждом вызове.
В главе Вызов методов указано
Обычный вызов метода для метода экземпляра отправляется на тип времени выполнения объекта. (Они являются виртуальными, на языках С++). вызов выполняется с помощью команды invokevirtual
, которая принимает в качестве аргумента индекс для записи пула постоянного времени выполнения давая внутреннюю форму двоичного имени типа класса объект, имя вызываемого метода и этот дескриптор метода (§4.3.3). Чтобы вызвать метод addTwo
, определенный ранее как экземпляр метод, мы можем написать:
int add12and13() {
return addTwo(12, 13);
}
Скомпилируется для:
Method int add12and13()
0 aload_0 // Push local variable 0 (this)
1 bipush 12 // Push int constant 12
3 bipush 13 // Push int constant 13
5 invokevirtual #4 // Method Example.addtwo(II)I
8 ireturn // Return int on top of operand stack;
// it is the int result of addTwo()
Вызов настраивается первым нажатием ссылки на текущий instance, this
, в стек операнда.. Вызов метода аргументы, int
значения 12 и 13, затем толкаются. Когда рамка для создается метод addTwo
, аргументы, переданные методу становятся начальными значениями локальных переменных нового кадра. То есть, ссылка для this
и два аргумента, нажата на операнд стек вызывающим, станут начальными значениями локальных переменные 0, 1 и 2 вызываемого метода.
Ответ 2
Почему x и this.x указывают на x базового класса, а не на класс Child?
Поскольку поля в Java не являются полиморфными. Связывание полей разрешается во время компиляции. Если вы хотите использовать инкремент в качестве полиморфизма, вы можете сделать это с помощью метода. Для правильной работы вам необходимо определить его в родительском и дочернем.
public void increment(){
x++; //this.x++; would do the same;
}
И если this.x указывает на x базового класса, почему this.b() вызывает b() дочернего класса?
Поскольку методы с другой стороны являются полиморфными, это означает, что их привязка разрешена во время выполнения и что почему this.b() вызывает метод из дочернего класса, в вашем случае это экземпляр BasicInheritanceTest3 и вызывается соответствующий метод.
Является ли поведение этого разного для полей и методов?
Как вы видите.
Super - это ссылка на базовый класс, поэтому вы можете получить к нему доступ, когда, например, нужно вызвать переопределенные методы и/или скрытые поля.
EDIT Ответить:
это ссылка, которая означает, что это только адрес объекта вместе со всеми его данными в памяти JVM, как JVM обрабатывает это ключевое слово, не очень известное или важное, оно, вероятно, объявлено при создании экземпляра. Но все, что вам нужно знать, в конце концов - это ссылка на экземпляр самого объекта.
Ответ 3
1. Почему x и this.x указывают на x базового класса, а не на класс Child?
мы можем увидеть этот пример:
class TestBase {
private int x;
public void a() {
this.x++;
}
public int getX() {
return x;
}
}
public class Test extends TestBase{
private int x;
public int getX() {
return this.x;
}
}
и сгенерированный байт-код:
public class Test extends TestBase{
public Test();
Code:
0: aload_0
1: invokespecial #1; //Method TestBase."<init>":()V
4: return
public int getX();
Code:
0: aload_0
1: getfield #2; //Field x:I
4: ireturn
public void a();
Code:
0: aload_0
1: invokespecial #3; //Method TestBase.a:()V
4: return
}
В нем Test
extends
TestBase
и метод a
скомпилирован в класс Test
, он назовет его отцом 1: invokespecial #3; //Method TestBase.a:()V
.
метод Test
getX
вызовет 1: getfield #2; //Field x:I
из него собственный constant pool table
, http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
байт-код класса TestBase
:
class TestBase extends java.lang.Object{
TestBase();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public void a();
Code:
0: aload_0
1: dup
2: getfield #2; //Field x:I
5: iconst_1
6: iadd
7: putfield #2; //Field x:I
10: return
public int getX();
Code:
0: aload_0
1: getfield #2; //Field x:I
4: ireturn
}
метод a()
также получит x
из собственного собственного пула getfield #2; //Field x:I
.
так что есть и другая вещь: Java getter
и setter
- зло.
Ответ 4
Собственно говоря, свойство полиморфизма в языке программирования JAVA может применяться только для методов, которые имеют достаточную квалификацию, чтобы быть полиморфными членами. Вы не должны думать о Поля как члены, у которых есть указанное свойство. Таким образом, вы больше не будете путаться в таких проблемах.
Ответ 5
[EDIT ответ]
Я провел немного исследований и получил следующую информацию, чтобы ответить на ваш вопрос дальше.
Мы действительно можем убедиться, что this
является частью байт-кода, используя обратный инженерный инструмент для преобразования байт-кода в исходный код java.
Почему мы находим this
в байт-коде?
Потому что, поскольку java - это многопроходный компилятор, и, поскольку ожидается, что байт-код будет запущен на любой другой платформе и на любой другой машине, вся информация должна быть в байте-коде, достаточно информации, чтобы иметь возможность перепроектировать байт-код в исходный код. Furhter, поскольку исходный код должен быть таким же, как исходный источник для байт-кода, все, включая точные имена переменных и полей, должно быть "каким-то образом" хорошо организовано со всей информацией в байт-коде.
В то время как С++ или pascal, в отличие от java, которые используют компилятор с одним пропуском, в основном не будут содержать точные имена полей, и поскольку такие языки выводят окончательный "исполняемый" файл, который должен быть готов к запуску, может быть меньше, чтобы поддерживать точное имена (инструкция не должна проходить через другой "проход" ). Если кто-нибудь обратит инженеров исполняемый файл (С++ или Pascal), имена переменных не будут доступны для чтения. Таким образом, в байт-кодексе "this" может быть представлен как нечитаемый для чтения формат, но то же самое можно было бы вернуть обратно в "this". То же самое не относится к однопроходному компилятору.
Многопользовательский компилятор
Методы класса не могут напрямую обращаться к переменным экземпляра или методам экземпляра - они должны использовать ссылку на объект.
Кроме того, методы класса не могут использовать ключевое слово this
, так как для this
для экземпляра нет экземпляра.
Теперь первый вопрос: почему x и this.x указывают на x of базовый класс, а не класс Child?
Это потому, что полиморфное поведение неприменимо для полей, поэтому результаты получены из базового класса.
почему this.b() вызывает b() дочернего класса? Является ли поведение этого разные для полей и методов?
С этой строкой: BasicInheritanceTest3 бит2 = новый BasicInheritanceTest3();
Единственным объектом в куче (в терминах базового и дочернего классов) является объект типа BasicInheritanceTest3
. Поэтому, независимо от this
, вызов будет применяться к методу дочернего класса. bit2
ссылается на свою собственную иерархию (наследования) в куче.
Теперь - как компилятор справляется с ним так же, как и другие ключевые/зарезервированные слова обрабатываются jdk. этот не разрешен в контексте методов класса
Методы класса не могут напрямую обращаться к переменным экземпляра или методам экземпляра - они должны использовать ссылку на объект. Кроме того, методы класса не могут использовать это ключевое слово, так как для этого нет экземпляра. В самом деле, интригующий вопрос, следовательно, дал ОП за голосование по этому вопросу.
Более полезная информация, которую я читал, была следующей:
Идентификаторы и зарезервированные ключевые слова - это токены, такие как одиночные символы, как +, и последовательности символов, подобных! =.
Я хотел бы попросить сообщество сохранить эту тему. Я не изучал, как jdk (как компилятор, так и среда выполнения) обрабатывают ключевые слова и зарезервированные слова.
Java Api Docs: это