Scala Language
클래스와 객체
수색…
통사론
-
class MyClass{} // curly braces are optional here as class body is empty
-
class MyClassWithMethod {def method: MyClass = ???}
-
new MyClass() //Instantiate
-
object MyObject // Singleton object
-
class MyClassWithGenericParameters[V1, V2](vl: V1, i: Int, v2: V2)
-
class MyClassWithImplicitFieldCreation[V1](val v1: V1, val i: Int)
-
new MyClassWithGenericParameters(2.3, 4, 5)
또는 다른 유형의new MyClassWithGenericParameters[Double, Any](2.3, 4, 5)
-
class MyClassWithProtectedConstructor protected[my.pack.age](s: String)
클래스 인스턴스 인스턴스화
스칼라의 클래스는 클래스 인스턴스의 '청사진'입니다. 인스턴스에는 해당 클래스에서 정의한대로 상태와 동작이 포함됩니다. 수업을 선언하려면 다음 단계를 따르세요.
class MyClass{} // curly braces are optional here as class body is empty
new
키워드를 사용하여 인스턴스를 인스턴스화 할 수 있습니다.
var instance = new MyClass()
또는:
var instance = new MyClass
Scala에서는 인수가없는 생성자가있는 클래스에서 객체를 만드는 경우 괄호를 사용하지 않아도됩니다. 클래스 생성자가 인수를 취하는 경우 :
class MyClass(arg : Int) // Class definition
var instance = new MyClass(2) // Instance instantiation
instance.arg // not allowed
여기에서 MyClass
는 내부적으로 만 사용할 수있는 Int
인수를 필요로합니다. arg
는 필드로 선언되지 않은 한 MyClass
외부에서 액세스 할 수 없습니다.
class MyClass(arg : Int){
val prop = arg // Class field declaration
}
var obj = new MyClass(2)
obj.prop // legal statement
또는 생성자에서 public으로 선언 될 수 있습니다.
class MyClass(val arg : Int) // Class definition with arg declared public
var instance = new MyClass(2) // Instance instantiation
instance.arg //arg is now visible to clients
매개 변수없이 클래스 인스턴스화 : {} vs ()
생성자 인수가없는 MyClass 클래스가 있다고 가정 해 보겠습니다.
class MyClass
스칼라에서는 아래 구문을 사용하여 인스턴스를 생성 할 수 있습니다.
val obj = new MyClass()
또는 다음과 같이 작성할 수 있습니다.
val obj = new MyClass
그러나주의를 기울이지 않으면 옵션 괄호로 인해 예기치 않은 동작이 발생할 수 있습니다. 별도의 스레드에서 실행해야하는 작업을 만들고 싶다고 가정 해 보겠습니다. 샘플 코드는 다음과 같습니다.
val newThread = new Thread { new Runnable {
override def run(): Unit = {
// perform task
println("Performing task.")
}
}
}
newThread.start // prints no output
우리는이 샘플 코드가 실행되면 Performing task.
을 인쇄 할 것이라고 생각할 수 있습니다 Performing task.
, 놀랍게도, 그것은 아무 것도 인쇄하지 않을 것입니다. 여기서 무슨 일이 일어나는지 보자. 면밀히 살펴보면 new Thread
바로 뒤에 중괄호 {}
를 사용했습니다. 그것은 Thread
를 확장하는 익명 클래스를 만들었습니다 :
val newThread = new Thread {
//creating anonymous class extending Thread
}
그리고이 익명 클래스의 본문에서 우리는 작업을 정의했습니다 (다시 Runnable
인터페이스를 구현하는 익명 클래스를 작성함). 그래서 우리는 우리가 사용하는 것을 생각했을 수도 public Thread(Runnable target)
생성자를하지만 사실 (무시 옵션 ()
) 우리가 사용하는 public Thread()
의 본문에 정의 아무것도 생성자 run()
메소드를. 문제를 해결하려면 중괄호 대신 괄호를 사용해야합니다.
val newThread = new Thread ( new Runnable {
override def run(): Unit = {
// perform task
println("Performing task.")
}
}
)
즉, 여기 {}
와 ()
는 서로 바꿔 쓸 수 없습니다.
싱글 톤 및 컴패니언 객체
싱글 톤 객체
Scala는 정적 멤버를 지원하지만 Java와 같은 방식은 지원하지 않습니다. Scala는 Singleton Objects 라는 대안을 제공합니다. 싱글 톤 객체는 new
키워드를 사용하여 인스턴스화 할 수 없다는 점을 제외하고는 일반 클래스와 유사합니다. 다음은 샘플 싱글 톤 클래스입니다.
object Factorial {
private val cache = Map[Int, Int]()
def getCache = cache
}
object
키워드를 사용하여 'class'또는 'trait'대신에 싱글 톤 객체를 정의했습니다. 싱글 톤 객체는 인스턴스화 될 수 없으므로 매개 변수를 가질 수 없습니다. 싱글 톤 객체에 액세스하는 것은 다음과 같습니다.
Factorial.getCache() //returns the cache
이것은 Java 클래스에서 정적 메서드에 액세스하는 것과 똑같은 모양입니다.
동반자 개체
스칼라에서 싱글 톤 객체는 해당 클래스의 이름을 공유 할 수 있습니다. 이러한 시나리오에서 싱글 톤 객체는 컴패니언 객체 로 참조됩니다. 예를 들어, Factorial
클래스가 정의되고, 그 아래에 Factorial
이라는 이름의 컴패니언 객체가 정의됩니다. 관습에 따라 컴패니언 객체는 컴패니언 클래스와 동일한 파일에 정의됩니다.
class Factorial(num : Int) {
def fact(num : Int) : Int = if (num <= 1) 1 else (num * fact(num - 1))
def calculate() : Int = {
if (!Factorial.cache.contains(num)) { // num does not exists in cache
val output = fact(num) // calculate factorial
Factorial.cache += (num -> output) // add new value in cache
}
Factorial.cache(num)
}
}
object Factorial {
private val cache = scala.collection.mutable.Map[Int, Int]()
}
val factfive = new Factorial(5)
factfive.calculate // Calculates the factorial of 5 and stores it
factfive.calculate // uses cache this time
val factfiveagain = new Factorial(5)
factfiveagain.calculate // Also uses cache
이 예제에서 우리는 반복되는 숫자의 계산 시간을 절약하기 위해 숫자의 계승을 저장하기 위해 개인 cache
를 사용합니다.
여기에서 object Factorial
은 동반 객체이며 class Factorial
은 해당 동반 클래스입니다. 동반자 객체와 클래스는 서로의 private
멤버에 액세스 할 수 있습니다. 위의 예에서 Factorial
클래스는 동반자 객체의 private cache
멤버에 액세스하고 있습니다.
클래스의 새로운 인스턴스 생성은 여전히 동일한 컴패니언 객체를 사용하므로 해당 객체의 멤버 변수를 수정하면이 객체가 계속해서 전달됩니다.
사물
클래스는 청사진과 비슷하지만 객체는 정적입니다 (즉, 이미 인스턴스화 됨).
object Dog {
def bark: String = "Raf"
}
Dog.bark() // yields "Raf"
그들은 종종 클래스의 동반자로 사용되며, 다음과 같이 작성할 수 있습니다.
class Dog(val name: String) {
}
object Dog {
def apply(name: String): Dog = new Dog(name)
}
val dog = Dog("Barky") // Object
val dog = new Dog("Barky") // Class
인스턴스 유형 검사
유형 검사 : variable.isInstanceOf[Type]
패턴 일치 (이 형식에서는 그렇게 유용하지 않음) :
variable match {
case _: Type => true
case _ => false
}
isInstanceOf
와 패턴 일치 모두 배열을 제외하고 일반 매개 변수 (형식화 없음)가 아닌 개체의 형식 만 검사합니다.
val list: List[Any] = List(1, 2, 3) //> list : List[Any] = List(1, 2, 3)
val upcasting = list.isInstanceOf[Seq[Int]] //> upcasting : Boolean = true
val shouldBeFalse = list.isInstanceOf[List[String]]
//> shouldBeFalse : Boolean = true
그러나
val chSeqArray: Array[CharSequence] = Array("a") //> chSeqArray : Array[CharSequence] = Array(a)
val correctlyReified = chSeqArray.isInstanceOf[Array[String]]
//> correctlyReified : Boolean = false
val stringIsACharSequence: CharSequence = "" //> stringIsACharSequence : CharSequence = ""
val sArray = Array("a") //> sArray : Array[String] = Array(a)
val correctlyReified = sArray.isInstanceOf[Array[String]]
//> correctlyReified : Boolean = true
//val arraysAreInvariantInScala: Array[CharSequence] = sArray
//Error: type mismatch; found : Array[String] required: Array[CharSequence]
//Note: String <: CharSequence, but class Array is invariant in type T.
//You may wish to investigate a wildcard type such as `_ <: CharSequence`. (SLS 3.2.10)
//Workaround:
val arraysAreInvariantInScala: Array[_ <: CharSequence] = sArray
//> arraysAreInvariantInScala : Array[_ <: CharSequence] = Array(a)
val arraysAreCovariantOnJVM = sArray.isInstanceOf[Array[CharSequence]]
//> arraysAreCovariantOnJVM : Boolean = true
유형 캐스팅 : variable.asInstanceOf[Type]
패턴 매칭 :
variable match {
case _: Type => true
}
예 :
val x = 3 //> x : Int = 3
x match {
case _: Int => true//better: do something
case _ => false
} //> res0: Boolean = true
x match {
case _: java.lang.Integer => true//better: do something
case _ => false
} //> res1: Boolean = true
x.isInstanceOf[Int] //> res2: Boolean = true
//x.isInstanceOf[java.lang.Integer]//fruitless type test: a value of type Int cannot also be a Integer
trait Valuable { def value: Int}
case class V(val value: Int) extends Valuable
val y: Valuable = V(3) //> y : Valuable = V(3)
y.isInstanceOf[V] //> res3: Boolean = true
y.asInstanceOf[V] //> res4: V = V(3)
참고 : 이것은 JVM의 동작에 관한 것입니다. 다른 플랫폼 (JS, 네이티브) 유형 캐스팅 / 검사는 다르게 동작 할 수 있습니다.
생성자
기본 생성자
스칼라에서 기본 생성자는 클래스의 본문입니다. 클래스 이름 뒤에는 생성자 인수 인 매개 변수 목록이옵니다. (어떤 함수와 마찬가지로 빈 매개 변수 목록은 생략 될 수 있습니다.)
class Foo(x: Int, y: String) {
val xy: String = y * x
/* now xy is a public member of the class */
}
class Bar {
...
}
val
키워드로 인스턴스 멤버로 표시하지 않는 한 인스턴스의 생성자 매개 변수는 생성자 본문 외부에서 액세스 할 수 없습니다.
class Baz(val z: String)
// Baz has no other members or methods, so the body may be omitted
val foo = new Foo(4, "ab")
val baz = new Baz("I am a baz")
foo.x // will not compile: x is not a member of Foo
foo.xy // returns "abababab": xy is a member of Foo
baz.z // returns "I am a baz": z is a member of Baz
val bar0 = new Bar
val bar1 = new Bar() // Constructor parentheses are optional here
객체 인스턴스가 인스턴스화 될 때 수행되어야하는 모든 작업은 클래스 본문에 직접 작성됩니다.
class DatabaseConnection
(host: String, port: Int, username: String, password: String) {
/* first connect to the DB, or throw an exception */
private val driver = new AwesomeDB.Driver()
driver.connect(host, port, username, password)
def isConnected: Boolean = driver.isConnected
...
}
가능한 한 생성자에 부작용을 거의주지 않는 것이 좋습니다. 위의 코드 대신 소비자 코드가 IO 스케줄링을 담당하도록 connect
및 disconnect
방법을 고려해야합니다.
보조 생성자
클래스에는 '보조 생성자'라고하는 추가 생성자가있을 수 있습니다. 이것은 def this(...) = e
형식의 생성자 정의에 의해 정의됩니다. 여기서 e
는 다른 생성자를 호출해야합니다.
class Person(val fullName: String) {
def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}
// usage:
new Person("Grace Hopper").fullName // returns Grace Hopper
new Person("Grace", "Hopper").fullName // returns Grace Hopper
이는 각 생성자가 다른 한정자를 가질 수 있음을 의미합니다. 일부만 공개적으로 사용할 수 있습니다.
class Person private(val fullName: String) {
def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}
new Person("Ada Lovelace") // won't compile
new Person("Ada", "Lovelace") // compiles
이 방법으로 소비자 코드가 클래스를 인스턴스화하는 방법을 제어 할 수 있습니다.