Конструктор-локальные переменные в 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);
}