Статьи

Типы значений в Java: почему они должны быть неизменными?

Типы значений не должны быть неизменными. Но они.

В предыдущем посте я обсуждал разницу между указателями и ссылками в Java и тем, как передаются параметры метода (передаваемые по значению или передаваемые по ссылке). Они тесно связаны с типами значений, которые не существуют в Java (пока).

Есть предложение от Джона Роуза, Брайана Гетца и Гая Стила, подробно описывающее, как типы значений будут / могут работать в Java, а также есть несколько хороших статей об этом. Я прочитал «Типы значений: обновление системы типов Java», которые мне очень понравились, и я рекомендую их прочитать. Если предложение слишком плотное для вас, чтобы следовать теме, вы можете сначала прочитать эту статью. В нем кратко изложены общие сведения, что такое типы значений, преимущества, почему проблема в том, что Java не реализует типы значений, и почему она не тривиальна. Хотя терминология «тип значения» может также использоваться для обозначения чего-то другого, я буду использовать его так же, как оно используется в предложении и в статье.

Как мы передаем аргументы против того, что мы храним в переменных

Как вы, возможно, помните из предыдущей статьи, я подробно описал, что Java передает аргументы метода по ссылке или по значению в зависимости от типа аргумента:

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

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

  1. переменные класса содержат объекты, и в этом случае мы передаем эти объекты методам по ссылке
  2. или мы можем сказать, что переменные содержат ссылку, и в этом случае мы передаем значение переменных.

Если мы следуем мышлению № 1, то передача аргумента происходит по значению и / или по ссылке, основываясь на фактической природе аргумента (объект или примитив). Если мы следуем принципу №2, то в переменных хранятся ссылки и / или значения, основанные на характере их типа. Мне лично нравится думать, что когда я пишу

1
Triangle triangle;

тогда переменный triangle является треугольником, а не ссылкой на треугольник. Но это не имеет значения, что у меня в голове. В любом из случаев № 1 или № 2 существует другой подход для типов классов и для примитивов. Если мы введем типы значений в язык, разница станет более распространенной и важной для понимания.

Типы значений неизменны

Я объяснил, что неявная передача аргументов, основанная на типе, не вызывает каких-либо проблем, потому что примитивы являются неизменяемыми, и поэтому при передаче в качестве аргумента метода их нельзя изменить, даже если они были переданы по ссылке. Поэтому нам обычно все равно. Типы значений не отличаются. Типы значений также являются неизменяемыми, поскольку они являются значениями, а значения не меняются. Например, значение PI равно 3,145926… и оно никогда не меняется.

Однако что означает эта неизменность в программировании? Значения — это действительные числа, целые числа или типы составных значений, представленные в памяти в виде битов. Биты в памяти (если память не является ПЗУ) могут быть изменены.

В случае неизменности объекта довольно просто. Где-то во вселенной есть объект, который мы не можем изменить. Может быть множество переменных, содержащих объект (имеющих ссылку на него), и код может опираться на тот факт, что биты в ячейке памяти, где представлено фактическое значение объекта, не изменяются (более или менее).

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

Типы значений не имеют идентичности

Типы значений не имеют идентичности. Вы не можете иметь две переменные типа int содержащие значение 3 и отличать одну от другой. Они имеют одинаковое значение. Это то же самое, когда тип более сложный.

Скажем, у меня есть тип значения, который имеет два поля, как

1
2
3
4
ValueType TwoFields {
  int count;
  double size;
  }

и сказать, у меня есть две переменные

1
2
Twofields tF1 = new TwoFields(1,3.14)
 Twofields tF2 = new TwoFields(1,3.14)

Я не могу отличить переменные tF1 и tF2 от других. Если бы они были объектами, они были бы equals друг другу, но не == друг другу. В случае типов значений нет == поскольку они не имеют идентичности.

Если TwoFields является неизменным классом, я не могу или не должен писать

1
2
3
TwoFields tF;
  ...
 tF.count++;

или похожая конструкция. Но я все еще могу написать

1
2
3
TwoFields tF;
  ...
 tF = new TwoFields(tF.count+1, tF.size)

который оставляет исходный объект нетронутым. Если TwoFields является типом значения, то любая из конструкций, в зависимости от того, что разрешено, создаст новое значение.

Типы значений в качестве аргументов

Как типы значений передаются как аргумент метода? Возможно копирование значения в переменную параметра. Возможно, передавая некоторые ссылки. Однако это зависит от компилятора (будь то Java или другой язык). Почему?

  • Типы значений обычно невелики. По крайней мере, они должны быть маленькими. Огромный тип значения теряет преимущества, которые предоставляют типы значений, но имеет недостатки.
  • Типы значений являются неизменяемыми, поэтому нет проблем с их копированием, как в случае с примитивами. Они могут передаваться по значению так же, как «все в Java передается по значению».
  • У них нет идентичности, на них не может быть никаких ссылок.

Но речь идет не только о передаче их в качестве аргументов. Это также, как переменные назначаются. Посмотри код

1
2
Twofields tF1 = new TwoFields(1,3.14)
 Twofields tF2 = new TwoFields(1,3.14)

и сравнить это с

1
2
Twofields tF1 = new TwoFields(1,3.14)
 Twofields tF2 = tF1

Если TwoFields является типом значения, между двумя версиями не должно быть различий. Они должны давать один и тот же результат (хотя могут не совпадать с одним и тем же кодом при компиляции). В этом отношении нет реальной разницы между передачей аргументов и назначением переменных. Значения копируются, даже если фактические переменные в виде битов содержат некоторые ссылки на некоторые области памяти, где хранятся значения.

Резюме

Когда я начал статью, типы значений не должны быть неизменными. Это не то, что решают дизайнеры языка. Они могут свободно реализовывать что-то изменчивое, но в этом случае это не будет тип значения. Типы значений неизменны.