Looking for java Keywords? Try Ask4Keywords

Java Language Pitfall: мышление переменных как объектов


пример

Никакая переменная Java не представляет объект.

String foo;   // NOT AN OBJECT

Ни один Java-массив не содержит объектов.

String bar[] = new String[100];  // No member is an object.

Если вы ошибочно считаете переменные как объекты, то реальное поведение языка Java удивит вас.

  • Для переменных Java, которые имеют примитивный тип (например, int или float ), переменная содержит копию значения. Все копии примитивного значения неразличимы; т.е. для номера один существует только одно значение int . Примитивные значения не являются объектами, и они не ведут себя как объекты.

  • Для переменных Java, которые имеют ссылочный тип (либо класс, либо тип массива), переменная содержит ссылку. Все копии ссылки неразличимы. Ссылки могут указывать на объекты, или они могут быть null что означает, что они указывают на отсутствие объекта. Однако они не являются объектами, и они не ведут себя как объекты.

Переменные не являются объектами в любом случае, и они не содержат объектов в любом случае. Они могут содержать ссылки на объекты , но это говорит что-то другое.

Пример класса

В следующих примерах используется этот класс, который представляет точку в 2D пространстве.

public final class MutableLocation {
   public int x;
   public int y;

   public MutableLocation(int x, int y) {
       this.x = x;
       this.y = y;
   }

   public boolean equals(Object other) {
       if (!(other instanceof MutableLocation) {
           return false;
       }
       MutableLocation that = (MutableLocation) other;
       return this.x == that.x && this.y == that.y;
   }
}

Экземпляр этого класса представляет собой объект, который имеет два поля x и y которые имеют тип int .

У нас может быть много экземпляров класса MutableLocation . Некоторые из них будут представлять одинаковые местоположения в 2D-пространстве; т.е. соответствующие значения x и y будут совпадать. Другие будут представлять разные местоположения.

Несколько переменных могут указывать на один и тот же объект

 MutableLocation here = new MutableLocation(1, 2);
 MutableLocation there = here;
 MutableLocation elsewhere = new MutableLocation(1, 2);

В приведенном выше MutableLocation мы объявили here три переменные, there и в elsewhere , elsewhere могут храниться ссылки на объекты MutableLocation .

Если вы (неправильно) считаете эти переменные объектами, то вы, скорее всего, неправильно понимаете утверждения:

  1. Скопируйте местоположение «[1, 2]» here
  2. Скопируйте местоположение «[1, 2]» there
  3. Скопируйте местоположение «[1, 2]» в elsewhere

Из этого вы можете сделать вывод, что у нас есть три независимых объекта в трех переменных. Фактически, только два объекта созданы выше. Переменные here и there действительно относятся к одному и тому же объекту.

Мы можем это продемонстрировать. Предполагая объявления переменных, как указано выше:

System.out.println("BEFORE: here.x is " + here.x + ", there.x is " + there.x +
                   "elsewhere.x is " + elsewhere.x);
here.x = 42;
System.out.println("AFTER: here.x is " + here.x + ", there.x is " + there.x +
                   "elsewhere.x is " + elsewhere.x);

Это выведет следующее:

BEFORE: here.x is 1, there.x is 1, elsewhere.x is 1
AFTER: here.x is 42, there.x is 42, elsewhere.x is 1

Мы присвоили новое значение here.x и изменили значение, которое мы видим через there.x . Они относятся к одному и тому же объекту. Но значение, которое мы видим через elsewhere.x , не изменилось, поэтому в elsewhere должно быть ссылка на другой объект.

Если переменная была объектом, тогда присваивание here.x = 42 не изменилось бы there.x . there.x

Оператор равенства НЕ проверяет, что два объекта равны

Применение оператора равенства ( == ) для сравнения значений значений, если значения относятся к одному и тому же объекту. Он не проверяет, являются ли два (разных) объекта «равными» в интуитивном смысле.

 MutableLocation here = new MutableLocation(1, 2);
 MutableLocation there = here;
 MutableLocation elsewhere = new MutableLocation(1, 2);

 if (here == there) {
     System.out.println("here is there");
 }
 if (here == elsewhere) {
     System.out.println("here is elsewhere");
 }

Это напечатает «здесь есть», но он не будет печатать «здесь где-то еще». (Ссылки here и в elsewhere предназначены для двух разных объектов.)

Напротив, если мы назовем метод equals(Object) который мы реализовали выше, мы будем тестировать, если два экземпляра MutableLocation имеют одинаковое расположение.

 if (here.equals(there)) {
     System.out.println("here equals there");
 }
 if (here.equals(elsewhere)) {
     System.out.println("here equals elsewhere");
 }

Это напечатает оба сообщения. В частности, здесь here.equals(elsewhere) возвращает true потому что семантические критерии, которые мы выбрали для равенства двух объектов MutableLocation , были выполнены.

Вызов метода НЕ пропускает объекты вообще

Вызов метода Java использует pass по значению 1 для передачи аргументов и возврата результата.

Когда вы передаете ссылочное значение методу, вы фактически передаете ссылку на объект по значению , а это значит, что он создает копию ссылки на объект.

Пока обе ссылки на объекты все еще указывают на один и тот же объект, вы можете изменить этот объект из любой ссылки, и это то, что вызывает путаницу для некоторых.

Однако вы не передаете объект по ссылке 2 . Различие заключается в том, что если копия ссылки на объект модифицирована, чтобы указать на другой объект, исходная ссылка на объект все равно укажет на исходный объект.

void f(MutableLocation foo) {  
    foo = new MutableLocation(3, 4);   // Point local foo at a different object.
}

void g() {
    MutableLocation foo = MutableLocation(1, 2);
    f(foo);
    System.out.println("foo.x is " + foo.x); // Prints "foo.x is 1".
}

Также вы не передаете копию объекта.

void f(MutableLocation foo) {  
    foo.x = 42;
}

void g() {
    MutableLocation foo = new MutableLocation(0, 0);
    f(foo);
    System.out.println("foo.x is " + foo.x); // Prints "foo.x is 42"
}

1 - В таких языках, как Python и Ruby, термин «pass by sharing» является предпочтительным для «pass by value» объекта / ссылки.

2 - Термин «передавать по ссылке» или «вызов по ссылке» имеет очень специфическое значение в терминологии языка программирования. Фактически это означает, что вы передаете адрес переменной или элемента массива , так что, когда вызываемый метод присваивает новое значение формальному аргументу, он изменяет значение в исходной переменной. Java не поддерживает это. Для более подробного описания различных механизмов передачи параметров см. Https://en.wikipedia.org/wiki/Evaluation_strategy .