В статье, для краткости, я буду в основном использовать термин приватная переменная. Но фактически, это будет означать приватное свойство класса или приватный метод класса.
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
}
И снова ошибка:
Получается, что так делать нельзя. Но что если убрать override:Error:(22, 17) value myPrivateVal overrides nothing
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 может быть:
- Имя пакета. В этом случае все классы из пакета XXX имеют доступ к someXxxVal. Эту фичу довольно часто используют внутри библиотек, когда хотят, чтобы свойство или метод были видны только внутри библиотеки и не торчали наружу. Еще это может быть полезно, чтобы добираться до внутреннего состояния объекта в юнит тестах.
- Имя класса/объекта. В этом случае класс/объект XXX будет иметь доступ до someXxxVal.
- 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
В этом примере компилятор ругнется:
Вот и первое отличие — в случае с private[this] мы не можем использовать переменную в companion object. Объект не может получить доступ до myPrivateVal.Error:(13, 36) value myPrivateVal is not a member of A$A113.this.WithPrivateThis
Теперь посмотрим на такой случай. Допустим, у нас есть какой-то метод в нашем классе, который на вход принимает экземпляр такого же класса. Что если мы обратимся к приватной переменной в этом объекте?
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)
И что мы видим? Ошибку!
То есть компилятор не дает нам использовать приватную переменную с private[this] из другого объекта в этом же классе. И мы не сможем реализовать equals способом, представленным выше для класса WithPrivate.Error:(21, 11) value myPrivateVal is not a member of A$A151.this.WithPrivateThis
В итоге
Если обобщить два предыдущих примера, то можно сказать, что обычный 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] может быть очень полезно внутри библиотек.
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий