Best thing that yo can do for you is:
- Use byte code viewer
- Use IntelliJ
Desugar scala code
tool.
Scala base OOP components are: class
, trait
, object
class DemoClassA
trait DemoTraitA
object DemoObjectA
Some basic differences between Java
Java | Scala |
---|---|
class (only non statics) | class |
class (only static) | object |
field, method, constructor | field (var, val), method (def), constructor |
inner class | inner class (type projection) |
- | inner class (type dependent) |
- | inner class (type) |
object = class instance | class instance |
abs. class, interface (methods, Java 8) | abs. class, trait, (def, var, constructor) |
- | case class = class + auto-generated code |
- | case object = object + auto-generated code |
- | package object |
- | var (mutable) |
final | val (immutable) |
In general, Scala
programming language mostly based on syntax sugar.
For example there is no "real" functions, scala has Function1
which accepts 1 argument,
Function2
with 2 arguments and so on... Until Function22
.
Same situation with tuples Tuple1
, Tuple2
, ..., Tuple22
.
Of course this is expected behavior, and there is no magic...
At the end of the day it compiles to Bytecode
which execution platform is JVM
.
Scala field sugar
class Person {
var age: Int = 1
}
Public by default. Generates age()
accessor and age_$eq
mutator (Avoid using symbol $
for naming)
The Java
code below:
public class Person {
// property
private int age = 1;
// constructor
public Person() {}
// getter
public int getAge() {
return age;
}
// setter
public void setAge(final int age) {
this.age = age;
}
}
Notice that in Scala
terminology, the Java
getter is called accessor and the setter is called mutator
// This will produce compile time error, the class should be abstract
// In scala fields are not default initialized as in Java
class Person {
var age: Int
}
If you want default initialization you sould use _
So the right one is either
class Person {
var age: Int = _
}
or make the class abstract
abstract class Person {
var age: Int
}
Because of type inference we can write class properties and methods like this
class Person {
// not recommended
// because scala's default access modifier is public
// that mean the property is part of public API
// and the user of your class should guess (at least read/open class) to work with it
val age = 1
// not recommended - read reason above
def printAge() = {
print(age)
}
}
The recommended way
class Person {
val age: Int = 1
def printAge(): Unit = {
print(age)
}
}
We can't have val
and var
keywords in arguments. Also we can't infer the method argument types.
class Person {
var age: Int = 10
// try to add val or var to `by` argument
def increaseAge(by: Int): Int = {
age += by
age
}
}
Scala is no ceremony
language.
Notice in the code above there is no ;
at the end of age += by
statement
and return
keyword at the end of a def
In some cases we can avoid even {}
braces for def
like def foo(x) = x + 1
We can rewrite code above like this:
class Person {
var age: Int = 10
def increaseAge(by: Int): Int = {
age += by;
return age
}
}
Make field private
Try 1 (fail try)
class Person {
// will be private mutator and private accessor
private var age: Int = _
}
Try 2
class Person {
// no accessor no mutator
private[this] var age: Int = _
}
Try 3
class Person {
private[this] var privateAge: Int = _
// accessor
def age: Int = {
println("hello from accessor")
privateAge
}
// mutator: fieldName_$eq(...)
def age_$eq(value: Int): Unit = {
println("hello from mutator")
privateAge = value
}
}
Lets write client code for Try 3
object Demo extends App {
val person = new Person
// accessor
person.age
// mutator
person.age = 4
// `parasite` mutators
person.age_$eq(4)
// another `parasite` mutator
person.age_=(4)
}
The interesting output...
hello from accessor
res1: Int = 0
hello from mutator
hello from accessor
person.age: Int = 4
hello from mutator
res2: Unit = ()
hello from mutator
res3: Unit = ()
Note. The age_=
is a method name. But the JVM
do not allow to do this.
After compilation it will take name like age_$eq
. And here where parasites comes from :)
Also the age_=
is recommended way to do that.
It's easy to notice that we can not declare both of them age_$eq
and age_=
in same class.
Assume were writing Scala class which will be used from Java, and we want to keep Java conventions.
Method 1
class Person {
private [this] var age: Int = _
def getAge(): Int = this.age
def setAge(age: Int): Unit = this.age = age
}
Method 2
import scala.beans.BeanProperty
class Person {
@BeanProperty var age: Int = _
}
Method 3
import scala.beans.BeanProperty
class Person(@BeanProperty var age: Int)
And welcome back to ugly world of Java
Primary constructor (no fields)
class Person(age: Int)
If we try to access age
with accessor or mutator we will get compile time error.
val person = new Person(45)
// accessor
person.age
// mutator
person.age = 15
Useful if
class Person(_age: Int) {
var age: Int = _age
}
It's so common that we have following
// var or val, both are acceptable
class Person(var age: Int)
It also worth to mention that we can have access modifiers as well
// here we have
// protected class Person
// which has protected constructor
// which has private field age, and public field name
protected class Person protected (private var age: Int, val name: String)
Auxiliary constructors
// primary constructor
class Person(var name: String, var age: Int) {
// auxiliary 1
def this(name: String) {
this(name, Person.DEFAULT_AGE)
}
// auxiliary 2
def this(age: Int) {
this(Person.DEFAULT_NAME, age)
}
// auxiliary 3
def this() {
this(Person.DEFAULT_NAME, Person.DEFAULT_AGE)
// also we can call this(Person.DEFAULT_AGE) which will call primary constructor
}
}
// companion object of class Person
object Person {
val DEFAULT_NAME = "Arthur"
val DEFAULT_AGE = 45
}
def main(args: Array[String]): Unit = {
val person = new Person()
person.name
person.age
}
Here the object Person
is called companion object which is constant holder in out case.
We can not avoid primary constructor call.
For this common construction we can:
// default parameters
class Person(var name: String = "Arthur", var age: Int = 45)
new Person("Vasya")
new Person(age = 15)
new Person("Vasya", 15)
new Person(age = 15, name = "Vasya")
new Person()
Notice the new Person(age = 15, name = "Vasya")
so we can call constructors and methods in any order, just by mentioning the parameter name.
This is called named parameters
.
def foo(name: String, age: Int): String = {
s"name = $name, age = $age"
}
println(foo(age = 45, name = "Arthur"))
Scala objects | Java static |
---|---|
companion object = companion module | class static members (Person.MAX_AGE) |
single object (not companion) | utility functions/constants (Math.sin, Math.PI) |
singleton | - |
package object | - |
Note: The companion object should be at the same file with companion class or trait.
Utility functions/constants
object Demo {
import IntLib.max
def main(args: Array[String]): Unit = {
println(max(7, 3))
}
}
object IntLib {
val MAX_INT = java.lang.Integer.MAX_VALUE
def max(x: Int, y: Int): Int = if (x > y) x else y
}
Notice max()
function. In Java you would need to import static IntLib.max
. In scala it's just import IntLib.max
If in companion object we have method called apply
, and the apply
method returns companion class instance, then we have following syntax sugar.
class Person(val name: String, val age: Int)
object Person {
def apply(name: String, age: Int): Person = {
new Person(name, age)
}
}
var personWithoutSugar1 = new Person("Mike", 45)
var personWithoutSugar2 = Person.apply("Mike", 45)
val personWithSugar = Person("Mike", 45)
The Java alternative could be:
package com.biacode.scala_cheatsheet;
import static com.biacode.scala_cheatsheet.JPerson.JPerson;
public class JPerson {
public final String name;
public final int age;
public JPerson(final String name, final int age) {
this.name = name;
this.age = age;
}
public static JPerson JPerson(final String name, final int age) {
return new JPerson(name, age);
}
}
final JPerson person0 = new JPerson("Mike", 45);
final JPerson person1 = JPerson.JPerson("Mike", 45);
final JPerson person2 = JPerson("Mike", 45);
The visibility:
// companion class - can see private `objectPrivate` of companion object
class PrivateDemo {
private val classPrivate = 0
val tmp: Int = PrivateDemo.objectPrivate
}
// companion object - can see private `classPrivate` of companion class
object PrivateDemo {
private val objectPrivate = 0
val tmp: Int = new PrivateDemo().classPrivate
}
What is interesting in scala that we can do following:
// companion class
class Person {
val name: String = _
val age: Int = _
def getName: String = this.name
}
// companion object
object Person {
val name: String = _
val age: Int = _
def getName: String = this.name
}
Which alternative in scala is:
public class JPerson {
public final String name;
public final int age;
public static final String name;
public static final int age;
public JPerson(final String name, final int age) {
this.name = name;
this.age = age;
}
}
Which will throw compile time exception like - variable name is already defined in scope blah blah...
I guess it's mainly because we can access the class static properties either:
new MyClass().THE_STATIC_PROPERTY
and/or
MyClass().THE_STATIC_PROPERTY
And the Java compiler can not assume which property we calling (in case of new MyClass().THE_STATIC_PROPERTY
).
But this is just language dessign and nothing else. The static properties and instance properties has their own memory location, which is separate.
You can not use object name as type:
trait T
class C
object O
val x: T = null
val y: C = null
val z: O = null // Illegal
val w: O.type = null
def f(x: T): T = ???
def g(x: C): C = ???
def h(x: O): O = ??? // Illegal
def q(x: O.type): O.type = ???
def r(arg: Any): String = arg match {
case _: T => "T"
case _: C => "C"
case _: O => "O" // Illegal
case _: O.type => "O"
}
In example above the val z: O = null
, def h(x: O): O = ???
and case _: O => "O"
are illegal.
Since the objects are not types itself. If we want to use them as type, we should use Object.type
construction.
We can have constructions like case O => "O"
which is fullly acceptable.
Methods - with and without parenthesis:
class Demo {
def f0 = 1
def f1: Int = 1
def g0() = 1
def g1(): Int = 1
}
new Demo().f0
new Demo().f0() // Illegal
new Demo().g0
new Demo().g0()
In example above, we can say f0 = f1
and g0 = g1
but {f0, f1} != {g0, g1}
.
The new Demo().f0()
expression is illegal.
Read the Uniform access principle and Scala Glossary for more details.
In short. Suppose we have class person, which has property age
like in example below:
class Person {
var age: Int = 45
}
val person = new Person
println(person.age)
If some day we decide to make age
computable and not just stored value, we can simple replace var
with def
:
class Person {
def age: Int = {
val x = 44
val y = x + 1
y
}
}
val person = new Person
println(person.age)
Notice that the client code will not crush, and everything works as expected.
For example we can return cached value of age
computation etc...
Imports.
What is really make me happy in scala, is imports. There is various ways to import components
in scala:
import java.util.ArrayList
import java.util.{HashMap, TreeSet}
import java.util._
The last one analog in java is import java.util.*
.
And finally the feature we're really missing in Java is aliasing, or let's call it rename.
import java.lang.Double.{isInfinite => isInf, isNaN}
Here the isInfinite
will be renamed to isInf
.
We can apply same aliasing to classes, traits etc...
Also we can use import
in any scope. For instance:
def foo(): Unit = {
import java.util.ArrayList
println("boo")
}
We can do even this:
class Person(val name: String, val age: Int)
object Demo {
def f(person: Person): Unit = {
import person._
println("name: " + name + " age: " + age)
}
}
Also this:
import java.util.{ArrayList => _, _}
Which means import everything from java.util
excluded ArrayList
.
We can even rename package names and so on...
In java we have following overloaded +
operator:
public class DemoApp {
public static void main(String[] args) {
// overloaded versions of '+' operator
String s = "A" + "B";
int k = 123 + 456;
long l = 12L + 34L;
float f = 1.f + 2.f;
double d = 1.2 + 3.4;
}
}
Of course the String + String
is not same as int + int
.
We can assume that there +
operator has lot of overloaded versions for different types.
For instance. In strings it's often called concatenation
which concates two string literals etc...
In many cases, operators can be:
Operation | Description |
---|---|
prefix ++1 |
(when operator is before operand) |
infix 1+2 |
(when operator is between operands) |
postfix 1++ |
(when operator is after operand) |
In scala we call them operations
.
See Prefix, Infix, and Postfix Operations
If we try to investigate Scala's scala.Int class, we can find all our known "operators" as method definitions.
For instance:
def /(x: Int): Int
def %(x: Short): Int
def *(x: Byte): Int
def -(x: Float): Float
// ...
So we can say.
- In scala there is no predefined 38 operators like in Java.
- We can "overload operations" as we needed.
- Also we can make our "new" operators like -
<<:><<
(just make sure the guy who will read this can not find you...)
If we try to reflect the class and print the method names.
import java.lang.reflect.Method;
for (final Method method : MainApp.class.getDeclaredMethods()) {
System.out.println(method);
}
The output will be something like this:
$plus
$minus
$times
$div
$bslash
$colon$colon
$minus$greater
$less$tilde
Because the JVM does not accepts method names with such symbols.
In scala, method calls that contains only 1 arity can be called as infix form with pointless style
.
case class I(k: Int) {
def add(that: I): I = I(this.k + that.k)
def +(that: I): I = I(this.k + that.k)
}
val x0 = I(1).add(I(2))
val x1 = I(1) add I(2) // pointless
val x2 = I(1).+(I(2))
val x3 = I(1) + I(2) // pointless
In scala standard lib you can see a lot of examples of this semantics. For instance:
1.to(10)
1 to 10 // pointless infix form
for (k <- 1 to 10) {
println(s"k = $k")
}
or
var map0 = Map("France" -> "Paris")
map0 += "Japan" -> "Tokyo" // pointless
var map1: Map[String, String] = Map.apply(new ArrowAssoc("France").->("Paris"))
map1.+=(new ArrowAssoc("Japan").->("Tokyo"))
NOTE: The pointless style
is not same as point free style
which is widely used in functional programming languages.
In point free style we're not using arguments at all.
val cos: Double => Double = Math.cos
val sin: Double => Double = Math.sin
val f: Double => Double = x => cos(sin(x))
val g: Double => Double = cos compose sin // point free style
or
// not pointless, not point free
val f0: Int => Int = x => 1.+(x)
// pointless, not point free
val f1: Int => Int = x => 1 + x
// not pointless, point free
val f2: Int => Int = 1.+(_)
// pointless, point free with placeholder
val f3: Int => Int = 1 + _
// pointless, point free without placeholder
val f4: Int => Int = 1 +
Operator precedence in scala.
case class I(k: Int) {
def add(that: I): I = I(this.k + that.k)
def mul(that: I): I = I(this.k * that.k)
}
// 1 add 2 mul 3 = 1 + 2 * 3 = 7
println(I(1) add I(2) mul I(3))
// (1 add 2) mul 3 = (1 + 2) * 3 = 9
println((I(1) add I(2)) mul I(3))
// 1 add (2 mul 3) = 1 + (2 * 3)
println(I(1) add (I(2) mul I(3)))
As you can see we have problem in println(I(1) add I(2) mul I(3))
which returns 9
and not 7
as we expected.
We have several ways to fix this.
First:
//NOT recommended
println(I(1) add I(2).mul(I(3)))
// recommended
println(I(1).add(I(2) mul I(3)))
In scala method calls with .
dot has more priority relatively to infix calls.
Which means in println(I(1) add I(2).mul(I(3)))
example I(2).mul(I(3))
will be called first,
and then the result will be applied to I(1) add ...
. But using this is not recommended,
because it's hard for your code users to read and understand it. It's not really natural.
So what is really best solution for this? As you already may guess, scala solves this problems in mathematical manner as well.
case class I(k: Int) {
def add(that: I): I = I(this.k + that.k)
def mul(that: I): I = I(this.k * that.k)
def +(that: I): I = I(this.k + that.k)
def *(that: I): I = I(this.k * that.k)
}
// 1 add 2 mul 3 => (1 add 2) mul 3
println(I(1) add I(2) mul I(3)) // res = 9
// 1 + 2 * 3 => 1 + (2 * 3)
println(I(1) + I(2) * I(3)) // res = 7
Even though the scala point less
and infix form
calls are left associative the priority of predefined symbols are differs.
For more details please read scala specification about infix operations
Operator associativity in scala.
In infix operations specification page we can see
The associativity of an operator is determined by the operator's last character. Operators ending in a colon `:' are right-associative. All other operators are left-associative.
In two words we cab say. If the priority is about first symbol, the associativity is about last symbol of infix operation.
If last character of operation ending with a :
then we have right association. Anything else is left associative.
case class I(k: Int) {
def ++(that: I): I = I(this.k + that.k)
def +:(that: I): I = I(this.k + that.k)
}
// 1 ++ 2 ++ 3 ++ 4
// ((1 ++ 2) ++ 3) ++ 4
println(I(1) ++ I(2) ++ I(3) ++ I(4))
// 1 +: 2 +: 3 +: 4
// 1 +: (2 +: (3 +: 4))
println(I(1) +: I(2) +: I(3) +: I(4))
If you are nerd enough and have reade this part of specification.
A left-associative binary operation e1;op;e2e1;op;e2 is interpreted as e1.op(e2)e1.op(e2). If opop is right-associative, the same operation is interpreted as { val xx=e1e1; e2e2.opop(xx) }, where xx is a fresh name.
// 1 ++ 2 ++ 3 ++ 4
// ((1 ++ 2) ++ 3) ++ 4
println(I(1) ++ I(2) ++ I(3) ++ I(4))
println(I(1).++(I(2)).++(I(3)).++(I(4)))
// 1 +: 2 +: 3 +: 4
// ((4 +: 3) +: 2) +: 1
println(I(1) +: I(2) +: I(3) +: I(4))
println(I(4).+:(I(3).+:(I(2) +: I(1))))
If operators are consecutive infix operations with same precedence operators, then all these operators must have same associativity.
case class I(k: Int) {
def ++(that: I): I = I(this.k + that.k)
def +:(that: I): I = I(this.k + that.k)
def **(that: I): I = I(this.k * that.k)
def *:(that: I): I = I(this.k * that.k)
}
// diff precedence, same assoc (left)
// (1 ++ (2 ** 3)) ++ 4
println(I(1) ++ I(2) ** I(3) ++ I(4))
// diff precedence, same assoc (right)
// ((3 *: 2) +: 4) +: 1
println(I(1) +: I(2) *: I(3) +: I(4))
// diff precedence, diff assoc (left + right)
// ((3 *: 2) ++ 1 ++ 4)
println(I(1) ++ I(2) *: I(3) ++ I(4))
// same precedence, diff assoc (left + right)
// error - we can not mix left and right assoc with same precedence
println(I(1) ++ I(2) +: I(3) ++ (I(4)))
// same precedence, diff assoc (left + right)
// error - we can not mix left and right assoc with same precedence
println(I(1) ** I(2) *: I(3) ** (I(4)))
If you'l tried to test code above, then you will notice that
- Can mix diff precedence and same assoc (right)
- Can mix diff precedence and same assoc (left)
- Can mix diff precedence and diff assoc (left + right)
- Can NOT mix same precedence and diff assoc (left and/or right)
Again, do this only if you are sure that you too far from a guy who will read your code.
From Infix Types specification.
// ab, ++ = type constructor
// (type constructor ab) + (type A) + (type B) => type ab[A, B] == type A ab B
case class ab[A, B](a: A, b: B)
// (type constructor ++) + (type A) + (type B) => type ++[A, B] == type A ++ B
case class ++[A, B](a: A, b: B)
val x0: ab[Int, String] = ???
val x1: Int ab String = ???
val y0: ++[Int, String] = ???
val y1: Int ++ String = ???
val y2: List[Int ++ String] = ???
class X extends (Int ++ String)
val f: Int ++ String => String ++ Int = ???
The case class ab[A, B](a: A, b: B)
and the case class ++[A, B](a: A, b: B)
are called type constructors.
All type infix operators have the same precedence; parentheses have to be used for grouping. The associativity of a type operator is determined as for term operators: type operators ending in a colon ‘:’ are right-associative; all other operators are left-associative.
// type constructors
case class ++[A, B](a: A, b: B)
case class **[A, B](a: A, b: B)
// no precedence
val x0: Int ++ String ** Boolean = ???
val x1: **[++[Int, String], Boolean] = x0
// with parenthesis precedence
val y0: Int ++ (String ** Boolean) = ???
val y1: ++[Int, **[String, Boolean]] = y0
If the last symbol of type constructor is :
it's still have some magic on association, but does not swaps right to left.
// type constructors
case class ++[A, B](a: A, b: B)
case class ::[A, B](a: A, b: B)
val x0: Int ++ String = ??? // Int ++ String == ++[Int, String]
val x1: Int :: String = ??? // Int :: String != ::[String, Int]
val y0: Int = x0.a
val y1: Int = x1.a // NOT string!
But affecting association
val x0: Int ++ String ++ Boolean = ???
val x1: ++[++[Int, String], Boolean] = x0
val a0: ++[Int, String] = x0.a
val a1: Boolean = x0.b
val y0: Int :: String :: Boolean = ???
val y1: ::[Int, ::[String, Boolean]] = y0
val b0: Int = y0.a
val b1: ::[String, Boolean] = y0.b
Because all type constructors have same precedence, then we can not mix type constructors with different associations.
// type constructors
case class ++[A, B](a: A, b: B)
case class **[A, B](a: A, b: B)
case class +:[A, B](a: A, b: B)
case class *:[A, B](a: A, b: B)
val x0: Int ++ String ** Boolean = ???
val x1: Int +: String *: Boolean = ???
val x2: Int +: String ** Boolean = ??? // error
val x3: Int ++ String *: Boolean = ??? // error
As you may guess we can fix x2
or x3
if we put parenthesis. For example:
val x2: Int +: (String ** Boolean) = ???
val x3: (Int ++ String) *: Boolean = ???
Prefix operator is the operator which located before it's operand + A
. And in general may have any number of arity (Polish notation).
But in scala we have only 4 possible prefix operators.
+ - ! ~
And can have only one arity.
case class I(k: Int) {
def unary_+(): I = I(2 * this.k)
def unary_-(): I = I(3 * this.k)
def unary_!(): I = I(4 * this.k)
def unary_~(): I = I(5 * this.k)
}
val x0 = +I(0)
val x1 = -I(0)
val x2 = !I(0)
val x3 = ~I(0)
val y0 = I(0).unary_+()
val y1 = I(0).unary_-()
val y2 = I(0).unary_!()
val y3 = I(0).unary_~()
val z0 = +I(0).unary_+()
val z1 = -I(0).unary_-()
val z2 = !I(0).unary_!()
val z3 = ~I(0).unary_~()
After compilation the unary_+()
will be renamed to unary_$plus()
and so on.
For postfix operators:
case class I(k: Int) {
def +(): I = I(2 * this.k)
def -(): I = I(3 * this.k)
def !(): I = I(4 * this.k)
def ~(): I = I(5 * this.k)
}
val x0 = I(0)+
val x1 = I(0)-
val x2 = I(0)!
val x3 = I(0)~
val y0 = I(0).+()
val y1 = I(0).-()
val y2 = I(0).!()
val y3 = I(0).~()
val z0 = I(0) +()
val z1 = I(0) -()
val z2 = I(0) !()
val z3 = I(0) ~()
Scala is pure object-oriented language. Every value is an object.
Scala is functional language because every function is a value.
In general, in computer science there is some terms describing programming languages as well as paradigms.
In scala terms we have
Kind, Type, Class, Object, Value, Function etc...
We can compress those terms to class
, type
, kind
.
class
is a template for building objects.
In scala the classes can be class
, trait
, object
, array
.
In java class
, interface
, array
.
The type
is a bit complicated.
But in general, the type is set of operations on parts of memory, module, expression, variable etc...
The kind
is a type of a type, or meta type.
Almost in evey programming language, the class has type or family of types, in many cases infinite number of sub types (generics for example). But there is some types, which has no class.
In java specification 8.1 Class declarations.
A class declaration specifies a new named reference type.
And later fixes this thesis like.
A generic class declaration defines a set of parametrized types, one for each possible parametrization of the type parameter section by type arguments. All of these parametrized types share the same class at runtime.
Same situation with interfaces
specification 9.1.
From java specification. The java programming language is statically typed language, which means that every variable and every expression has a type that is known at compile time.
There are two kinds of types in the java programming language: primitive types and reference types. There are, correspondingly, two kinds of data values that can be stored in variables, passed as arguments, returned by methods, and operated on: primitive values and reference values.
There are four kind of reference types: class types, interface types, type variables and array types.
There is also a special null type, the type of expression null, which has no name.
Because the null type has no name, it is impossible to declare a variable of the null type or to cast to the null type.
The null type has one value, the null reference, represented by null literal null
.
A null literal is always of the null type.
The direct supertype of null type are all reference types other than the null type itself.
Our custom type should be something in between reference types and null type.
Scala as well as other functional languages has rich type system.
Which is drastically differs from the Java types.
The diagram below shows hierarchy of the scala types.
We can compare value types as Java primitives and reference types.
In terms of methods. I think it can be more less wrong to say they have their own memory.
Methods are part of object, and the object has it's own allocated memory.
Of course in runtime we have stack allocation which lives as long as the stack frame which owns that method call.
But it's different story.
Concrete type constructor is a factory of types. And as the method types, it does not have it's own memory.
It's more like instructions rather than memory.
Value types is something that can have it's own memory.
Abstract types are something like interfaces. They have memory, but we can not create direct instances of them.
In scala value classes are like java primitive types.
The reference classes are like java ref types.
In scala we don't have term primitive
.