Pull to refresh

Модификаторы private и private[this] в Scala

Reading time5 min
Views11K
В Scala, помимо обычного модификатора доступа private существует также модификатор private[this]. Эти два модификатора довольно похожи друг на друга. К тому же в Java есть только простой private. Поэтому они легко могут вызвать путаницу или убежденность, что простой private — не такой уж и приватный, и везде надо использовать private[this] для пущей защищенности. Но давайте разберемся, как дела обстоят на самом деле.

В статье, для краткости, я буду в основном использовать термин приватная переменная. Но фактически, это будет означать приватное свойство класса или приватный метод класса.

private


Начнем с обычного private. Давайте посмотрим, какой уровень защищенности он нам предоставляет. Проще всего это проверить на примерах. Для запуска кода я использовал scala workseet в Intellij IDEA. Моя версия Scala — 2.11.7.

Допустим, у нас есть класс WithPrivate с приватным полем myPrivateVal и методом, который возвращает значение этого поля:

class WithPrivate {
  private val myPrivateVal = 5
  def show = myPrivateVal
}

Попробуем вызвать метод show:

val a = new WithPrivate
a.show

И видим результат:
res0: Int = 5
Все как и ожидалось — можно использовать приватное свойство внутри класса.

Теперь попробуем обратиться к приватной переменной извне класса:

val a = new WithPrivate
a.myPrivateVal

Компилятор выдает ошибку:
Error:(9, 4) value myPrivateVal in class WithPrivate cannot be accessed in A$A86.this.WithPrivate
То есть мы не можем обратиться к приватной переменной извне класса.

Проверим, как ведут себя наследники. Попробуем обратиться к приватной переменной внутри наследника:

class B extends WithPrivate {
  def usePrivate = {
    myPrivateVal
  }
}

Видим ошибку:
Error:(8, 6) not found: value myPrivateVal
Снова ожидаемый результат — мы не можем использовать приватные переменные в наследниках.

Попробуем переопределить приватную переменную в наследнике:

class C extends WithPrivate {
  override val myPrivateVal = 10
}

И снова ошибка:
Error:(22, 17) value myPrivateVal overrides nothing
Получается, что так делать нельзя. Но что если убрать override:

class D extends WithPrivate {
  val myPrivateVal = 10
}

val d = new D
d.show

В консоли видим:
res0: Int = 5
То есть переопределения не получилось. Мы просто создали переменную с таким же именем в наследнике. Таким образом, переопределить приватную переменную в наследнике мы тоже не можем.

Как видите, поведение модификатора private соответствует тому, как он ведет себя в Java. То есть мнение о том, что обычный private не слишком приватный — ошибочно. Обычный private как минимум обеспечивает такой же уровень закрытости, что и private в Java.

private[this]


Следующий на очереди — private[this]. Но перед его рассмотрением я хотел бы напомнить, что вообще означает такая запись:

class A {
  private[XXX] val someXxxVal = 5
}

На месте XXX может быть:

  1. Имя пакета. В этом случае все классы из пакета XXX имеют доступ к someXxxVal. Эту фичу довольно часто используют внутри библиотек, когда хотят, чтобы свойство или метод были видны только внутри библиотеки и не торчали наружу. Еще это может быть полезно, чтобы добираться до внутреннего состояния объекта в юнит тестах.
  2. Имя класса/объекта. В этом случае класс/объект XXX будет иметь доступ до someXxxVal.
  3. this. О нем далее.

Теперь давайте посмотрим, как же ведет себя private[this]. Самый простой способ — применить те же тесты, которые мы уже использовали для обычного private. Не буду снова расписывать каждый случай по отдельности, просто приведу общий листинг:

class WithPrivateThis {
  private[this] val myPrivateVal = 5
  def show = myPrivateVal
}

val a = new WithPrivateThis

// использование приватной переменной внутри класса
a.show

// попробуем обратиться к приватной переменной извне класса
a.myPrivateVal

// попробуем обратиться к приватной переменной внутри наследника
class B extends WithPrivateThis {
  def usePrivate = {
    myPrivateVal
  }
}

// попробуем переопределить приватную переменную в наследнике
class C extends WithPrivateThis {
  override val myPrivateVal = 10
}

// попробуем переопределить приватную переменную в наследнике без override
class D extends WithPrivateThis {
  val myPrivateVal = 10
}

val d = new D
d.show

Если запустить этот код, то можно убедиться, что эти тесты показывают абсолютно аналогичные результаты. То есть private[this] в данных тестах показывает такое же поведение, что и обычный private. Но если результаты одинаковые, то зачем вообще нужен private[this]?

Но у нас же Scala, а не Java


Дело в том, что тесты, приведенные выше, покрывают только случаи, которые исходят из поведения private в Java. Но для Scala это не все случаи использования private.

Давайте посмотрим на следующий пример:

object WithPrivate {
  def foo = (new WithPrivate).myPrivateVal
}

class WithPrivate {
  private val myPrivateVal = 5
}

WithPrivate.foo

Тут мы создали companion object для нашего класса и используем приватную переменную внутри него. Этот пример нормально скомпилируется и покажет результат:
res0: Int = 5

Попробуем тоже самое с private[this]:

object WithPrivateThis {
  def foo = (new WithPrivateThis).myPrivateVal
}

class WithPrivateThis {
  private[this] val myPrivateVal = 5
}

WithPrivateThis.foo

В этом примере компилятор ругнется:
Error:(13, 36) value myPrivateVal is not a member of A$A113.this.WithPrivateThis
Вот и первое отличие — в случае с private[this] мы не можем использовать переменную в companion object. Объект не может получить доступ до myPrivateVal.

Теперь посмотрим на такой случай. Допустим, у нас есть какой-то метод в нашем классе, который на вход принимает экземпляр такого же класса. Что если мы обратимся к приватной переменной в этом объекте?

class WithPrivate {
  private val myPrivateVal = 5

  def withThat(that: WithPrivate) = {
    that.myPrivateVal
  }
}

val a = new WithPrivate
val b = new WithPrivate
b.withThat(a)

В методе withThat мы пытаемся достучаться до приватной переменной другого экземпляра (that) этого же класса (WithPrivate).
Если запустить этот код то вы увидите ожидаемый результат:
res0: Int = 5

Такой прием часто используется, например, в equals. Если опустить, что у нас myPrivateVal одинаковый для всех экземпляров класса WithPrivate, то для него equals мог бы выглядеть следующим образом:

override def equals(obj: Any) = obj match {
  case that: WithPrivate ⇒ this.myPrivateVal == that.myPrivateVal
  case _ ⇒ false
}

Теперь давайте попробуем тоже самое с private[this]:

class WithPrivateThis {
  private[this] val myPrivateVal = 5

  def withThat(that: WithPrivateThis) = {
    that.myPrivateVal
  }
}

val a = new WithPrivateThis
val b = new WithPrivateThis
b.withThat(a)

И что мы видим? Ошибку!
Error:(21, 11) value myPrivateVal is not a member of A$A151.this.WithPrivateThis
То есть компилятор не дает нам использовать приватную переменную с private[this] из другого объекта в этом же классе. И мы не сможем реализовать equals способом, представленным выше для класса WithPrivate.

В итоге


Если обобщить два предыдущих примера, то можно сказать, что обычный private можно рассматривать как private[ThisClass], где ThisClass — это класс, в котором объявлена приватная переменная. То есть уровень видимости ограничен текущим классом/объектом. Все экземпляры класса ThisClass и companion object этого класса будут видеть эту приватную переменную.

private[this] следует рассматривать как приватность на уровне конкретного экземпляра класса. То есть мы сможем обратиться к приватной переменной только внутри текущего экземпляра класса. Ни другие эклемпляры класса, ни companion object не имеют доступа до такой приватной переменной. Можно сказать, что private[this] — это более строгий вариант обычного private.

В каких же случаях какой модификатор использовать? Я стараюсь руководствоваться следующими правилами:

  • Если у вас мутабельная приватная переменная, которая хранит какое-то состояние, то лучше сделать ее private[this]. Это не даст другим экземплярам класса или компаньону поменять состояние этой переменной, что иногда может быть фатально для общего состояния объекта.
  • Если у вас метод, вызов которого меняет состояние объекта, лучше также сделать его private[this].
  • Во всех остальных случаях использовать обычный private.

В заключение хочу добавить, что в обычном проекте почти всегда хватает простого private. Если писать код внимательно, то private[this] можно вообще не использовать. Не так много классов имеют методы, которые на вход принимают экземпляры этого же класса. Да и кто будет в таких методах вызывать деструктивные приватные методы или менять приватное состояние в других объектах? То есть если везде пытаться обезопасить себя, обкладываясь private[this], то это может просто не пригодиться при должном уровне дисциплины в коде. При этом активное использование private[this] может быть очень полезно внутри библиотек.
Tags:
Hubs:
+16
Comments8

Articles