Конструктор-локальные переменные в Scala
Я нахожусь в упражнении 5.7 из Scala для Impatient ", где мне нужно создать класс Person, который принимает имя: String on constructor и имеет 2 свойства firstName и lastName, заполненные из имени, разделенного пробелом. Мое первое испытание:
class Person(name:String) {
private val nameParts = name.split(" ")
val firstName = nameParts(0)
val lastName = nameParts(1)
}
Проблема заключается в том, что nowPart остается как частное поле, всегда видимое внутри класса, когда на самом деле должно существовать только внутри локальной среды конструктора. Явный эквивалент того, что я хочу, будет:
class Person{
private final String firstName;
private final String lastName;
Person(String name){
final String[] nameParts = name.split(" ");
firstName = nameParts[0];
lastName = nameParts[1];
}
}
Здесь nameParts существует только с конструктором, к чему я стремлюсь. Любые подсказки о том, как это можно сделать в Scala?
ПРИМЕЧАНИЕ. В итоге я нашел более "Scalesque" способ:
class Person(name:String) {
val firstName::lastName::_ = name.split(" ").toList
}
но я все равно хочу получить ответ на мой вопрос.
Ответы
Ответ 1
Существует способ избежать private val
. Просто используйте экстрактор Array
:
class Person(name: String) {
val Array(first, last) = name.split(" ")
}
изменить:
То, что вы хотите сделать, может быть достигнуто с помощью метода factory на компаньоне и конструкторе по умолчанию, который занимает первое и последнее значение как param:
class Person(val first: String, val last: String)
object Person {
def apply(name: String) = {
val splitted = name.split(" ")
new Person(splitted(0), splitted(1))
}
}
scala> Person("Foo Bar")
res6: Person = [email protected]
scala> res6.first
res7: String = Foo
scala> res6.last
res8: String = Bar
Но для этого простого случая я предпочел бы свое первое предложение.
Пример в вашей ссылке также будет работать, но это похоже на мой первый пример. Afaik не существует способа создать временную переменную в конструкторе.
Ответ 2
То, что я имел в виду, было просто
class Person(n: String) {
val firstName = n.split(" ")(0)
val lastName = n.split(" ")(1)
}
Если вы хотите разложить общий код, тогда ответ drexin с массивом очень приятный. Но это требует знания, которое читатель не имел бы в главе 5. Однако var с кортежами был рассмотрен в главе 4, поэтому доступно следующее:
class Person(n: String) {
val (firstName, lastName) = { val ns = n.split(" "); (ns(0), ns(1)) }
}
Ответ 3
Просто добавление в ответ @drexin. В вашем примере class Person(name:String)
параметр конструктора все еще сохраняется как private[this] val name: String
и может быть доступен в пределах класса, например:
class Person(name:String) {
def tellMeYourName = name
}
Если вы действительно хотите этого избежать, вы можете создать объект-компаньон и сделать основной конструктор закрытым:
class Person private (val fName: String, val lName: String)
object Person {
def apply(name: String) = {
val Array(fName, lName) = name split " "
new Person(fName, lName)
}
}
Другой способ - создать признак Person
сопутствующим объектом:
trait Person {
val lName: String
val fName: String
}
object Person {
def apply(name: String) = new Person {
val Array(lName, fName) = name split " "
}
}
Ответ 4
Подход, который позволяет избежать определения Extractors или необходимость метода сопутствующих объектов, это
class Person(name: String) {
val (firstName, lastName) = {
val nameParts = name.split(" ")
(nameParts(0), nameParts(1))
}
}
object Example {
println(new Person("John Doe").firstName)
}
A scala выражение, заключенное в {..}
, имеет значение последнего подвыражения внутри
Ответ 5
Обычный способ scala заключается в использовании сопутствующего объекта (как описано в предыдущих ответах).
Однако есть случаи, когда это может быть проблемой (например, если мы наследуем класс и хотим вызвать соответствующий конструктор, не зная внутренней логики или если мы хотим создать экземпляр класса через отображение).
Единственный способ, который я знаю, если делать это непосредственно внутри класса, немного запутан:
class Person(name: String, x: Seq[String]) {
val firstName = x(0)
val lastName = x(1)
def this(name: String) {
this(name, name.split(" "))
}
}
Идея состоит в том, что x (то же самое, что и имя) не имеет var или val и поэтому преобразуется в private [this] val.
Оптимизатор знает, что, поскольку они не используются вне конструктора, он может использовать их как обычный функциональный параметр, и они не сохраняются.
Внутренний вызов делает это возможным.
Результат javap -private Person.class:
public class com.rsa.nw_spark.Person {
private final java.lang.String firstName;
private final java.lang.String lastName;
public java.lang.String firstName();
public java.lang.String lastName();
public com.rsa.nw_spark.Person(java.lang.String, scala.collection.Seq<java.lang.String>);
public com.rsa.nw_spark.Person(java.lang.String);
}
Решение, упомянутое несколько раз, выглядит следующим образом:
class Person(name: String) {
val (firstName, lastName) = {
val nameParts = name.split(" ")
(nameParts(0), nameParts(1))
}
}
на самом деле не работает. Он создает временный кортеж, который сохраняется. Результат javap -private Person.class:
public class com.rsa.nw_spark.Person {
private final scala.Tuple2 x$1;
private final java.lang.String firstName;
private final java.lang.String lastName;
public java.lang.String firstName();
public java.lang.String lastName();
public com.rsa.nw_spark.Person(java.lang.String);
}