Подтвердить что ты не робот

"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, объявленное компилятором, и каковы его квалификаторы. Как происходит возникшее в результате поведение за кулисами?

4b9b3361

Ответ 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: это