Skip to content

Scala Data Types

Scala has a rich and powerful type system. This chapter will detail Scala's various data types, including basic types, reference types, and the type hierarchy.

Type Hierarchy

Scala's type system has a unified hierarchy:

Any
├── AnyVal (Value Types)
│   ├── Double
│   ├── Float
│   ├── Long
│   ├── Int
│   ├── Short
│   ├── Byte
│   ├── Char
│   ├── Boolean
│   └── Unit
└── AnyRef (Reference Types)
    ├── String
    ├── List
    ├── Option
    └── All other reference types

Any Type

Any is the supertype of all types:

scala
val items: List[Any] = List(1, "hello", true, 3.14)

def printAny(x: Any): Unit = {
  println(s"Value: $x, Type: ${x.getClass.getSimpleName}")
}

items.foreach(printAny)
// Output:
// Value: 1, Type: Integer
// Value: hello, Type: String
// Value: true, Type: Boolean
// Value: 3.14, Type: Double

Basic Data Types (AnyVal)

Numeric Types

Integer Types

scala
// Byte: 8-bit signed integer (-128 to 127)
val byteValue: Byte = 127
val byteMin: Byte = Byte.MinValue  // -128
val byteMax: Byte = Byte.MaxValue  // 127

// Short: 16-bit signed integer (-32,768 to 32,767)
val shortValue: Short = 32767
val shortMin: Short = Short.MinValue  // -32768
val shortMax: Short = Short.MaxValue  // 32767

// Int: 32-bit signed integer (-2^31 to 2^31-1)
val intValue: Int = 2147483647
val intMin: Int = Int.MinValue  // -2147483648
val intMax: Int = Int.MaxValue  // 2147483647

// Long: 64-bit signed integer (-2^63 to 2^63-1)
val longValue: Long = 9223372036854775807L
val longMin: Long = Long.MinValue
val longMax: Long = Long.MaxValue

Floating-point Types

scala
// Float: 32-bit IEEE 754 single precision floating-point
val floatValue: Float = 3.14159f
val floatMin: Float = Float.MinValue
val floatMax: Float = Float.MaxValue
val floatPositiveInfinity: Float = Float.PositiveInfinity
val floatNegativeInfinity: Float = Float.NegativeInfinity
val floatNaN: Float = Float.NaN

// Double: 64-bit IEEE 754 double precision floating-point
val doubleValue: Double = 3.141592653589793
val doubleMin: Double = Double.MinValue
val doubleMax: Double = Double.MaxValue
val pi: Double = math.Pi
val e: Double = math.E

Numeric Type Conversions

scala
val intVal = 42
val longVal: Long = intVal.toLong      // Safe conversion
val doubleVal: Double = intVal.toDouble // Safe conversion
val floatVal: Float = intVal.toFloat   // Safe conversion

// Conversions that may lose precision
val bigLong = 1234567890123L
val intFromLong: Int = bigLong.toInt   // May overflow

// Check if conversion is safe
def safeToInt(long: Long): Option[Int] = {
  if (long >= Int.MinValue && long <= Int.MaxValue) {
    Some(long.toInt)
  } else {
    None
  }
}

Character Type

scala
// Char: 16-bit unsigned Unicode character
val char1: Char = 'A'
val char2: Char = '\u0041'  // Unicode representation of 'A'
val char3: Char = 65.toChar // ASCII code to character

// Character operations
val isLetter: Boolean = char1.isLetter
val isDigit: Boolean = char1.isDigit
val isUpper: Boolean = char1.isUpper
val lower: Char = char1.toLower
val upper: Char = char1.toUpper

// Character range
val charMin: Char = Char.MinValue  // '\u0000'
val charMax: Char = Char.MaxValue  // '\uffff'

Boolean Type

scala
// Boolean: true or false
val isTrue: Boolean = true
val isFalse: Boolean = false

// Boolean operations
val and: Boolean = true && false   // false
val or: Boolean = true || false    // true
val not: Boolean = !true          // false

// Conditional expression
val result: String = if (isTrue) "yes" else "no"

// Boolean relationship with other types
val boolToInt: Int = if (isTrue) 1 else 0

Unit Type

scala
// Unit: Represents no meaningful value, similar to Java's void
def printMessage(msg: String): Unit = {
  println(msg)
}

val unitValue: Unit = ()  // Unit's only value
val unitFromMethod: Unit = printMessage("Hello")

// Unit usage in expressions
val result: Unit = {
  val x = 10
  val y = 20
  println(s"Sum: ${x + y}")  // Returns Unit
}

Reference Types (AnyRef)

String Type

scala
// String: Immutable character sequence
val str1: String = "Hello, World!"
val str2: String = new String("Hello")

// String operations
val length: Int = str1.length
val upper: String = str1.toUpperCase
val lower: String = str1.toLowerCase
val substring: String = str1.substring(0, 5)  // "Hello"

// String comparison
val isEqual: Boolean = str1 == "Hello, World!"  // true
val isEqualRef: Boolean = str1 eq str2          // false (reference comparison)

// String interpolation
val name = "Alice"
val age = 25
val interpolated = s"Name: $name, Age: $age"
val formatted = f"Pi: ${math.Pi}%.2f"
val raw = raw"Path: C:\Users\$name"

Collection Types Overview

scala
// List: Immutable linked list
val list: List[Int] = List(1, 2, 3, 4, 5)
val emptyList: List[String] = List.empty[String]
val nilList: List[Int] = Nil

// Array: Mutable array
val array: Array[Int] = Array(1, 2, 3, 4, 5)
val arrayOfStrings: Array[String] = Array("a", "b", "c")

// Vector: Immutable vector
val vector: Vector[Int] = Vector(1, 2, 3, 4, 5)

// Set: Immutable set
val set: Set[Int] = Set(1, 2, 3, 2, 1)  // Set(1, 2, 3)

// Map: Immutable map
val map: Map[String, Int] = Map("a" -> 1, "b" -> 2, "c" -> 3)

Option Type

scala
// Option: Represents a value that may or may not exist
val someValue: Option[String] = Some("Hello")
val noneValue: Option[String] = None

// Option usage
def findUser(id: Int): Option[String] = {
  if (id > 0) Some(s"User$id") else None
}

val user1 = findUser(1)   // Some("User1")
val user2 = findUser(-1)  // None

// Option operations
val result1 = someValue.getOrElse("Default")  // "Hello"
val result2 = noneValue.getOrElse("Default")  // "Default"

val mapped = someValue.map(_.toUpperCase)     // Some("HELLO")
val filtered = someValue.filter(_.length > 3) // Some("Hello")

// Pattern matching
val message = someValue match {
  case Some(value) => s"Found: $value"
  case None => "Not found"
}

Tuple Type

scala
// Tuple: Fixed number of elements of different types
val tuple2: (String, Int) = ("Alice", 25)
val tuple3: (String, Int, Boolean) = ("Bob", 30, true)
val tuple4: (Int, String, Double, Boolean) = (1, "test", 3.14, false)

// Access tuple elements
val name: String = tuple2._1    // "Alice"
val age: Int = tuple2._2        // 25

// Tuple destructuring
val (personName, personAge) = tuple2
println(s"Name: $personName, Age: $personAge")

// Tuple methods
val swapped: (Int, String) = tuple2.swap  // (25, "Alice")

Function Types

scala
// Function types
val add: (Int, Int) => Int = (x, y) => x + y
val multiply: (Int, Int) => Int = _ * _

// Higher-order function types
val applyTwice: (Int => Int, Int) => Int = (f, x) => f(f(x))

// Using function types
val double: Int => Int = _ * 2
val result = applyTwice(double, 5)  // 20

// Functions as parameters
def processNumbers(numbers: List[Int], operation: Int => Int): List[Int] = {
  numbers.map(operation)
}

val numbers = List(1, 2, 3, 4, 5)
val doubled = processNumbers(numbers, _ * 2)
val squared = processNumbers(numbers, x => x * x)

Type Aliases

scala
// Type aliases
type UserId = Int
type UserName = String
type UserData = (UserId, UserName, Int)  // (id, name, age)

val user: UserData = (1, "Alice", 25)

// Complex type aliases
type StringToInt = String => Int
type UserMap = Map[UserId, UserName]

val userMap: UserMap = Map(1 -> "Alice", 2 -> "Bob")
val parser: StringToInt = _.toInt

Generic Types

scala
// Generic class definition
class Container[T](val value: T) {
  def get: T = value
  def map[U](f: T => U): Container[U] = new Container(f(value))
}

// Using generic class
val intContainer = new Container(42)
val stringContainer = new Container("Hello")
val doubledContainer = intContainer.map(_ * 2)

// Generic methods
def identity[T](x: T): T = x
def pair[A, B](a: A, b: B): (A, B) = (a, b)

val id1 = identity(42)        // Int
val id2 = identity("hello")   // String
val p = pair("key", 100)      // (String, Int)

Type Inference

scala
// Scala's type inference
val number = 42              // Inferred as Int
val text = "Hello"           // Inferred as String
val flag = true              // Inferred as Boolean
val decimal = 3.14           // Inferred as Double

val list = List(1, 2, 3)     // Inferred as List[Int]
val map = Map("a" -> 1)      // Inferred as Map[String, Int]

// Cases requiring explicit type annotation
val emptyList: List[String] = List()  // Cannot infer element type
val recursive: Int => Int = x => if (x <= 0) 1 else x * recursive(x - 1)

Type Checking and Casting

scala
// Type checking
val obj: Any = "Hello, World!"

val isString: Boolean = obj.isInstanceOf[String]
val isInt: Boolean = obj.isInstanceOf[Int]

// Type casting
if (obj.isInstanceOf[String]) {
  val str: String = obj.asInstanceOf[String]
  println(str.toUpperCase)
}

// Safe type casting
obj match {
  case s: String => println(s"String: ${s.toUpperCase}")
  case i: Int => println(s"Integer: ${i * 2}")
  case _ => println("Unknown type")
}

Nothing and Null Types

scala
// Nothing: Subtype of all types, represents never returns
def error(message: String): Nothing = {
  throw new RuntimeException(message)
}

def divide(x: Int, y: Int): Int = {
  if (y == 0) error("Division by zero")
  else x / y
}

// Null: Subtype of all reference types
val nullString: String = null  // Not recommended
val nullList: List[Int] = null // Not recommended

// Use Option instead of null
def safeDivide(x: Int, y: Int): Option[Int] = {
  if (y == 0) None else Some(x / y)
}

Practical Application Examples

Type-Safe Configuration System

scala
// Define configuration types
sealed trait ConfigValue
case class StringConfig(value: String) extends ConfigValue
case class IntConfig(value: Int) extends ConfigValue
case class BooleanConfig(value: Boolean) extends ConfigValue

class Configuration {
  private var configs: Map[String, ConfigValue] = Map()

  def set(key: String, value: ConfigValue): Unit = {
    configs = configs + (key -> value)
  }

  def getString(key: String): Option[String] = {
    configs.get(key).collect {
      case StringConfig(value) => value
    }
  }

  def getInt(key: String): Option[Int] = {
    configs.get(key).collect {
      case IntConfig(value) => value
    }
  }

  def getBoolean(key: String): Option[Boolean] = {
    configs.get(key).collect {
      case BooleanConfig(value) => value
    }
  }
}

// Usage example
val config = new Configuration()
config.set("app.name", StringConfig("MyApp"))
config.set("app.port", IntConfig(8080))
config.set("app.debug", BooleanConfig(true))

val appName = config.getString("app.name").getOrElse("DefaultApp")
val port = config.getInt("app.port").getOrElse(3000)
val debug = config.getBoolean("app.debug").getOrElse(false)

Type-Safe JSON Processing

scala
// Simplified JSON types
sealed trait Json
case object JsonNull extends Json
case class JsonBoolean(value: Boolean) extends Json
case class JsonNumber(value: Double) extends Json
case class JsonString(value: String) extends Json
case class JsonArray(values: List[Json]) extends Json
case class JsonObject(fields: Map[String, Json]) extends Json

// JSON builder
object Json {
  def obj(fields: (String, Json)*): JsonObject = JsonObject(fields.toMap)
  def arr(values: Json*): JsonArray = JsonArray(values.toList)
  def str(value: String): JsonString = JsonString(value)
  def num(value: Double): JsonNumber = JsonNumber(value)
  def bool(value: Boolean): JsonBoolean = JsonBoolean(value)
  val `null`: JsonNull.type = JsonNull
}

// Usage example
val json = Json.obj(
  "name" -> Json.str("Alice"),
  "age" -> Json.num(25),
  "active" -> Json.bool(true),
  "hobbies" -> Json.arr(
    Json.str("reading"),
    Json.str("swimming")
  ),
  "address" -> Json.obj(
    "city" -> Json.str("New York"),
    "zipcode" -> Json.str("10001")
  )
)

Exercises

Exercise 1: Type Conversion and Checking

scala
object TypeExercise {
  def processValue(value: Any): String = {
    value match {
      case i: Int if i > 0 => s"Positive integer: $i"
      case i: Int if i < 0 => s"Negative integer: $i"
      case i: Int => "Zero"
      case s: String if s.nonEmpty => s"Non-empty string: $s"
      case s: String => "Empty string"
      case b: Boolean => s"Boolean: $b"
      case d: Double => f"Double: $d%.2f"
      case _ => "Unknown type"
    }
  }

  def main(args: Array[String]): Unit = {
    val values: List[Any] = List(42, -10, 0, "Hello", "", true, 3.14159, 'A')
    values.foreach(v => println(processValue(v)))
  }
}

Exercise 2: Option Type Application

scala
object OptionExercise {
  case class Person(name: String, age: Int, email: Option[String])

  val people = List(
    Person("Alice", 25, Some("alice@example.com")),
    Person("Bob", 30, None),
    Person("Charlie", 35, Some("charlie@example.com"))
  )

  def findPersonByName(name: String): Option[Person] = {
    people.find(_.name == name)
  }

  def getEmailDomain(person: Person): Option[String] = {
    person.email.map(_.split("@").last)
  }

  def main(args: Array[String]): Unit = {
    val alice = findPersonByName("Alice")
    val domain = alice.flatMap(getEmailDomain)

    println(s"Alice's email domain: ${domain.getOrElse("No email")}")

    // Chained operations
    val result = for {
      person <- findPersonByName("Charlie")
      email <- person.email
      domain = email.split("@").last
    } yield s"${person.name}'s domain is $domain"

    println(result.getOrElse("Person not found or no email"))
  }
}

Summary

This chapter detailed Scala's data type system:

  • Type Hierarchy: Relationship between Any, AnyVal, AnyRef
  • Basic Types: Numeric, character, boolean, Unit types
  • Reference Types: Strings, collections, Option, etc.
  • Composite Types: Tuples, function types
  • Type System Features: Generics, type inference, type checking
  • Special Types: Use cases for Nothing and Null

Understanding Scala's type system is key to mastering the language. In the next chapter, we will learn Scala Literals to deeply understand literal representations of various data.

Content is for learning and research only.