xuhai
xuhai

Reputation: 77

How to use scala class member name as variable

In scala, is there a way to access a class member,but the member name is a var? Below is a code snippet, there is a class member called "one_star". I have a var whose value is "one_star", and I want use this var as the member name of the "case class".

case class Features(
  // star about
  var one_star: String = "0",
  var two_star: String = "0",
  var three_star: String = "0",
  var four_star: String = "0",
  var five_star: String = "0"

  // other about
)

object Features {
  def apply(): Features = {
    val features = new Features()
    var testVar = "one_star"
    features.${testVar} = "1"
    features
  }
}

Upvotes: 3

Views: 3976

Answers (3)

Ra Ka
Ra Ka

Reputation: 3055

If you want to change field name dynamically, i.e. provide class variable name as value, find field that match given variable name and finally change the value for that field, there are several ways: the simple one is to use pattern match to check the field value and change instance value by yourself and return instance. However, it can be quite messy as you need to handle for every fields defined in your class and in case you have many fields, it can be quite cumbersome. Therefore, you will need some generic way to solve this problem.

Another approach is to use scala reflection. Scala reflection is designed for this, i.e. modifying your codes at runtime like your case and in more generic way. Following is a code snippet that change value of your instance for given field name.

  case class Features(
                       // star about
                       var one_star: String = "0",
                       var two_star: String = "0",
                       var three_star: String = "0",
                       var four_star: String = "0"

                       // other about
                     ) {
    def copyInstance(variableName: String, value: String): Features = {
      // 1. Initialize Features instance
      val instance = Features()

      // 2. Import scala reflection api.
      import scala.reflect.runtime.{universe => ru}

      // 3. Get the mirror of instance of Features class.
      // Mirror will reflect to instance of Features case class, and we will use this instance mirror to modify its fields value.
      val instanceMirror = ru.runtimeMirror(instance.getClass.getClassLoader).reflect(instance)

      // 4. Now, Get the field whose value need to be changed - i.e. name you provide - variableName.
      val field = ru.typeOf[Features].decl(ru.TermName(variableName)).asTerm

      // 5. Get the mirror for that field so that we can read and write to this field.
      val fieldMirror = instanceMirror.reflectField(field)

      // 6. Finally, set the value to this field.
      fieldMirror.set(value)

      // 7. Return the changed instance.
      instance
    }
  }

  val features = Features()
  val changedFeatures = features.copyInstance("one_star", "changed")

  println(features)
  println(changedFeatures)

//Result
Features(0,0,0,0)
Features(changed,0,0,0)

Also, note that you may need to handle the Exception for cases where invalid variable name and value is provided. In addition, if your case class contains >22 field parameters, certain features of case class are not available.

Upvotes: 4

ephemient
ephemient

Reputation: 204798

This is possible using scala-reflect, although under most circumstances I would not recommend it.

import scala.reflect.runtime.universe._

val field = typeOf[Features].decl(TermName(testVar)).asTerm.accessed.asTerm
val mirror = runtimeMirror(classOf[Features].getClassLoader)
mirror.reflect(features).reflectField(field).set("1")

Are you sure you don't want to use or extend Map[String, String] for your class? So many properties is not typical.

Upvotes: 0

Ruslan K.
Ruslan K.

Reputation: 142

Scala is static type language and doesn't allow these language constructions. But you can use reflection (hard way) or pattern matching with code like this one (simple way):

class Features (
  var one_star: String = "0",
  var two_star: String = "0",
  var three_star: String = "0",
  var four_star: String = "0",
  var five_star: String = "0") {

  def setField(field: String, value: String): Unit = {
    field match {
      case "one_star" => one_star = value
      case "two_star" => two_star = value
      case "three_star" => three_star = value
      case "four_star" => four_star = value
      case "five_star" => five_star = value
    }
  }
}

Upvotes: 0

Related Questions