Ключевое слово this в Java
Ключевое слово this в Java: обращение к текущему объекту, разграничение полей, цепочки конструкторов и вызовы методов.
Внутри метода экземпляра или конструктора this ссылается на объект, у которого вызывается метод. Это неявный параметр, который JVM передаёт за вас. В большинстве случаев упоминать его не нужно — простые имена уже разрешаются через this — однако в ряде ситуаций приходится писать его явно. Этой теме посвящена данная глава.
Что такое this
Когда вы вызываете dog.bark(), JVM тайно передаёт dog первым аргументом. Внутри bark этот аргумент называется this. Таким образом:
public class Dog {
String name;
void bark() {
System.out.println(this.name + " says woof"); // this == dog
}
}
Dog rex = new Dog();
rex.name = "Rex";
rex.bark(); // inside, this == rexМожно опустить this. и написать просто name — Java ищет голый идентификатор в текущей области видимости, не находит локальной переменной с таким именем, затем обращается к полям экземпляра, находит name и разрешает его как this.name. Обе формы компилируются в одинаковый байткод.
Применение 1 — разграничение параметра и поля
Самая распространённая причина явно писать this. — когда параметр или локальная переменная имеет то же имя, что и поле:
public class Point {
int x, y;
public Point(int x, int y) {
this.x = x; // field = parameter
this.y = y;
}
}Без this. выражение x = x; присваивало бы параметр самому себе, оставляя поле равным 0. Компилятор не выдаёт предупреждения — это допустимый оператор, — поэтому данная ошибка популярна и незаметна.
Можно переименовать параметры (int newX, int newY), чтобы избежать конфликта, однако принято совпадение имён параметров с именами полей — это гораздо читаемее, — поэтому шаблон с this. встречается в Java-коде повсеместно.
Применение 2 — передача текущего объекта
Иногда методу нужно передать this в другой метод или конструктор в качестве аргумента:
public class Order {
List<Item> items = new ArrayList<>();
void register(Tracker t) {
t.watch(this); // pass myself to the tracker
}
}Другого способа обратиться к текущему объекту как к значению нет, поэтому this здесь обязателен.
Распространённый вариант — текучий (fluent) строитель, в котором каждый сеттер возвращает this, что позволяет выстраивать цепочки вызовов:
public class Url {
String scheme, host, path;
public Url scheme(String s) { this.scheme = s; return this; }
public Url host(String h) { this.host = h; return this; }
public Url path(String p) { this.path = p; return this; }
}
Url u = new Url().scheme("https").host("w3docs.com").path("/learn-java");Применение 3 — this(...) для вызова другого конструктора
Внутри конструктора this(args) вызывает другой конструктор того же класса — см. конструкторы. Этот вызов должен быть первым оператором, и конструктор может делегировать только одному другому конструктору:
public Rectangle() { this(1, 1); }
public Rectangle(double side) { this(side, side); }
public Rectangle(double w, double h){ this.width = w; this.height = h; }Это отличается от квалификатора this. — this(...) с аргументами является формой вызова конструктора; this.foo (или просто this) — это форма ссылки.
Когда this недопустимо
this существует только в контекстах экземпляра. В static-методе или статическом инициализаторе текущего объекта нет, поэтому написание this приводит к ошибке компиляции:
public class Counter {
static int total;
static void reset() {
this.total = 0; // ERROR: cannot use this in a static context
}
}Метод main является static, поэтому нельзя напрямую обращаться к полям экземпляра внутри него — нужно сначала создать объект.
this и неявный доступ к полям
Если конфликта имён нет, this. является сугубо стилистическим выбором:
double area() { return Math.PI * radius * radius; }
double areaThisly(){ return Math.PI * this.radius * this.radius; }Оба варианта работают. Неявная форма встречается чаще; некоторые кодовые базы используют this. везде ради единообразия или чтобы явно показать получателя. Оба подхода приемлемы — выберите один и придерживайтесь его в пределах файла.
Внутренние классы и внешний this
Нестатический внутренний класс имеет неявную ссылку на охватывающий экземпляр. Изнутри внутреннего класса this ссылается на внутренний объект; внешний экземпляр доступен как Outer.this:
public class Outer {
int x = 1;
class Inner {
int x = 2;
void demo() {
System.out.println(this.x); // 2 — Inner's x
System.out.println(Outer.this.x); // 1 — Outer's x
}
}
}Это встречается только в сценариях с вложенными классами, которые рассматриваются в главе о вложенных классах и последующих главах.
Лямбды и анонимные классы
Лямбда не вводит нового this. Внутри лямбды this является охватывающим экземпляром — тем же объектом, на котором выполняется окружающий метод. Анонимный класс, напротив, является своим собственным объектом, поэтому внутри него this ссылается на этот анонимный экземпляр:
public class Demo {
String label = "outer";
void run() {
Runnable lambda = () -> System.out.println(this.label); // "outer"
Runnable anon = new Runnable() {
String label = "inner";
public void run() { System.out.println(this.label); } // "inner"
};
lambda.run(); // prints outer
anon.run(); // prints inner
}
}Это одно из немногих поведенческих различий между двумя формами: лямбда захватывает this из окружающего контекста, тогда как анонимный класс его затеняет.
Практический пример
Что дальше
this — одна из двух неявных ссылок в Java; вторая, super, появляется после изучения наследования. Но сначала нужно поговорить о видимости — кто может видеть и вызывать методы и поля вашего класса. Переходите к главе о модификаторах доступа.