Scala 언어 문법책 공부 후 핵심만 정리함

 

 

Scala 는 JVM 언어임. 자바 런타임을 사용하여 실행됨

 

 

 

Literal : 숫자 5, 문자 'A', 문자열 "eyeballs" 처럼 소스 코드에 바로 등장하는 데이터

값(value) : 불변의 타입을 갖는 저장 단위. 정의될 때 데이터가 할당되며 재할당 불가능

변수(variable) : 가변의 타입을 갖는 저장 단위. 정의될 때 데이터가 할당되며 재할당 가능

타입(type) : 데이터의 종류, 정의, 분류. Scala 의 모든 데이터는 특정 타입에 대응하며, 모든 Scala 타입은 그 데이터를 처리하는 메소드를 갖는 클래스로 정의됨

 

불변의 값(value) 를 사용하면, 다른 어떤 코드에서 접근하더라도 같은 값을 유지하는 안정성을 갖출 수 있어

코드를 읽고 디버깅하는 일이 더 쉬워짐

동시 또는 멀티 스레드 코드에서 값을 사용하여 에러 발생 가능성을 낮출 수 있음

 

 

 

 

이름 설명 크기 최솟값 최댓값
Byte 부호 있는 정수 1byte -128 127
Short 부호 있는 정수 2byte -32768 32767
Int 부호 있는 정수 4byte -2^31 2^31 - 1
Long 부호 있는 정수 8byte -2^63 2^63 - 1
Float 부호 있는 부동 소수 4byte n/a n/a
Double 부호 있는 부동 소수 8byte n/a n/a

 

 

 

 

 

 

Literal Type 설명
5 Int 접두사/접미사 없는 정수 리터럴은 기본이 Int
0x0f Int 접두사 0x : 16진수
5l Long 접미사 l : Long 타입
5.0 Double 접두사/접미사 없는 소수 리터럴은 기본이 Double
5f Float 접미사 f : Float 타입
5d Double 접미사 d : Double 타입

 

 

 

 

정규 표현식

 

matches : 정규 표현식이 전체 문자열과 맞으면 true

"abc" matches ".*" : true

 

replaceAll : 일치하는 문자열을 모두 치환

"abc" replaceAll ("b|c","a") : "aaa"

 

replaceFirst : 첫번째로 일치하는 문자열을 치환

"abc" replaceFirst ("b|c","a") : "aac"

 

 

 

 

bool 비교 연산자인 &&,|| 은 게을러서 첫 번째 인수로 충분하다면 두 번째 인수는 평가하지 않음

&, | 은 결괏값 반환하기 전에 늘 두 인수를 모두 검사함

 

 

 

 

데이터 타입 연산

예제 설명
val aa = 5
myVal.asInstanceOf[Long]
원하는 타입으로 전환
val aa = 5
myVal.getClass
해당 값의 타입(=Class) 반환
val aa = 5
myVal.isInstanceOf[Int]
해당 값이 넣어준 타입에 해당하는지 확인
val aa = 5
myVal.hashCode
해당 값의 해시코드 반환
val aa = 5
myVal.toDouble
형변환
val aa = 5
myVal.toString
해당 값을 string 으로 변환

 

 

 

 

 

 

Tuple 은 선언 방법이 두 가지이며, 인덱스는 1부터 시작함

0부터 시작하지 않는 문제아. 아주 자기 멋대로야..

 

val myTuple = (1, "2", '3', "four", 5)

myTuple._1 // 1

myTuple._2 // "2"

 

val myTuple = 1 -> "2"

myTuple: (Int, String) = (1, "2")

 

 

 

 

 

 

표현식을 아래처럼 활용 가능

 

val result = { val x = 6*25; x*100 }

result: Int = 15000

 

여기서 x는 표현식 내에서만 사용 가능하며

표현식 바깥에서는 접근 불가

 

 

 

 

 

if ( true ) println("a")

결과 : a

 

if (1 > 2) println("a") else println("b")

결과 : b

 

if 문과 대체하여 사용 가능한 match 표현식은 아래처럼 사용 가능

 

val max = (1>2) match {

  case true => 1

  case false => 2

}

결과 : 2

 

val message = 500 match {

  case 200 => "ok"

  case 400 => { println("400"); "error" }

  case 500 => { println("500"); "error" }

}

결과 : 500 이 출력되고 "error" 가 message 에 들어감

 

val kind = "WED" match {

  case "MON" | "TUE" | "WED" | "THU" | "FRI" => "weekday"

  case "SAT" | "SUN" => "weekend"

}

결과 : weekday

 

val ifstatement = -1 match {

  case x if x > 0 => "plus"

  case x if x < 0 => "minus"

  case x => "zero"

}

결과 : minus

 

val other = 300 match {

  case 200 => "ok"

  case other => "error"

}

결과 : error

 

val wilecard = 300 match {

  case 200 => "ok"

  case _ => "error"

}

결과 : error

 

타입으로도 매칭이 가능

val x:Int = 123

x match {

  case x: String => "String"

  case x: Int => "Int"

}

결과 : Int

 

 

 

 

 

 

for 루프는 일정 범위의 데이터를 반복하며, 반복할 때마다 표현식을 실행함

yield 를 추가하면 반환값들을 컬렉션으로 돌려줌

(각 원소마다 특정 표현식을 적용하고 반환하는 것이 마치 map 과 비슷함)

 

for ( i <- 1 to 5 ) { print(i+" ") }

결과 : 1 2 3 4 5

 

for ( i <- 1 until 5 ) { print(i+" ") }

결과 : 1 2 3 4

 

val result = for ( i <- 1 to 5 ) yield { i }
결과 : results: scala.collection.immutable.IndexedSeq[Int] = Vector(1,2,3,4,5)

 

for ( i <- result ) { print(i+" ") }
결과 : 1 2 3 4 5

 

for ( x <- 1 to 2 ; y <- 1 to 3 } { print(s"($x,$y)") }

결과 : (1,1)(1,2),(1,3)(2,1)(2,2)(2,3)

 

 

아래와 같이 for문 안에 조건식을 넣을 수 있음

 

for ( i <- 1 to 10 if i%2!=0) print(i+" ")

결과 : 1 3 5 7 9

 

아래와 같이 여러개의 조건절을 넣을 수 있으며, 조건들은 서로 and 로 엮임

 

for {

  c <- "a,bc,,d,,ef".split(",")

  if c != null

  if c.size > 1

} print(c+" ")

결과 : bc ef

 

아래와 같이 for 루프 안에서만 쓰이는 임시 변수를 지정하여 사용 가능

아래 예제의 x 는 for 루프가 반복 할 때마다 매번 정의되고 할당됨

 

for ( i <- 1 to 5 ; x = i * i ) print(x+" ")

결과 : 1 4 9 16 25

 

while 루프도 사용 가능함

 

var x = 5

while( x > 0 ) x-=1

결과 : x 는 0이 됨

 

 

 

 

 

 

 

순수 함수는 아래의 조건들을 만족하는 함수임

- 하나 이상의 입력 매개변수를 가짐

- 입력 매개변수만을 이용하여 계산 수행

- 값을 반환함

- 동일 입력에 대해 항상 같은 값을 반환

- 함수 외부의 어떤 데이터도 사용하거나 영향을 주지 않음

- 함수 외부 데이터에 영향을 받지 않음

 

순수 함수는 상태 정보를 유지하지 않으며, 외부 데이터에 관계없이 독립적임

본질적으로 순수함수는 변경될 수 없어서 안정적임

(마치 수학에서 사용하는 함수와 동일한 성질을 갖음)

Scala 프로그래밍을 하면서 순수 함수 비율을 많이 늘리는 게 좋다고 함

 

 

 

함수는 def 를 사용하여 선언하며, 선언문 다음에는 표현식이 옴

 

선언 : def hi = "hi"

사용 : hi

 

선언 : def hi = {"hi"}

사용 : hi

 

선언 : def hi:String = {val result = "eyeballs", result}

사용 : hi

결과 : eyeballs


선언 : def hi() = "hi"

사용 : hi 혹은 hi()

 

선언 : def hi(name:String) = "hi "+name

사용 : hi("eyeballs")

결과 : hi eyeballs

 

선언 : def hi(name:String):String = {

  if (name != null) return "hi "+name

  else return "it's null"

}

사용 : hi("eyeballs"), hi(null)

결과 : "hi eyeballs", "it's null"

 

선언 : def hi(name:String):String = {

  if (name != null) return "hi "+name

  "it's null"

}

사용 : hi("eyeballs"), hi(null)

결과 : "hi eyeballs", "it's null"

 

선언 : def hi(name:String, ch:Char) = "hi "+name+ch

사용 : hi("eyeballs", '!')

결과 : hi eyeballs!

 

선언 : def hi(name:String, ch:Char) = "hi "+name+ch

사용 : hi(name = "eyeballs", ch = '!') , hi(ch = '!', name = "eyeballs")

결과 : hi eyeballs!

 

선언 : def hi(name:String, ch:Char = '!') = "hi "+name+ch

사용 : hi("eyeballs"), hi(name = "eyeballs")

결과 : hi eyeballs!

 

선언 : def hi(name:String)(ch:Char) = "hi "+name+ch

사용 : hi("eyeballs")('!')

결과 : hi eyeballs!

 

아래처럼 인수 개수가 가변적일 때 * 를 사용하여 처리 가능

 

선언 : def sum(items: Int*): Int = {

  var total = 0

  for ( i <- items) total += i

  total

}

사용 : sum(0), sum(1,2), sum(1,2,3,4,5)

결과 : 0, 3, 15

 

아래처럼 사용시 표현식을 사용할 수 있음

아래 에제에서 myname 은 호출할 때만 잠깐 사용되는 값

 

선언 : def hi(name:String) = "hi "+name

사용 : hi {val myname="eyeballs"; hi(myname)}

결과 : hi eyeballs

 

프로시저란 반환값을 갖지 않는 함수이며, 이 때 함수의 반환값은 Unit 이 됨

예를 들어 아래 두 함수는 동일한 함수임

def log(d:Double) = println("value : "+d)

def log(d:Double):Unit = println("value : "+d)

 

아래처럼 함수 안에 함수 중첩 가능

Scala 는 오버로딩이 가능하기 때문에, 아래 예제의 파라미터 3개짜리 max와 파라미터 2개짜리 max 는 서로 다른 함수임

파라미터 2개짜리 max는 파라미터 3개짜리 max 내에서만 사용 가능 (max 함수 바깥에서 사용 불가)

 

선언 : def max(a:Int, b:Int, c:Int) = {

  def max(x:Int, y:Int) = if (x>y) x else y

  max(a, max(b, c))

}

사용 : max(1,2,3)

결과 : 3

 

Generic 처럼 함수 내에서 다루는 타입 자체를 사용시 직접 넣어줄 수 있음

이것을 타입 매개변수라고 부르며 [ ] 를 사용하여 선언함

마지막 예제처럼 타입 매개변수를 넣어주지 않아도 추론이 가능한 경우는 에러가 나지 않음

 

선언 : def identity[TYPE] (x:TYPE):TYPE = x

사용 : identity[Int](1), identity[String]("eyeballs"), identity[Double](0.1), identity("eyeballs")

결과 : 1, eyeballs, 0.1, eyeballs

 

 

 

 

 

 

클래스 내 메소드를 호출할 때 dot 을 사용할 수 있지만, white scape 를 사용하는 것도 가능

아래 세 가지는 모두 같은 의미를 갖음

"eyeballs".endsWith("s")

"eyeballs" endsWith("s")

"eyeballs" endsWith "s"

 

 

 

 

 

Scala 에서 함수는 일급 객체임

일급 객체란, 일반적인 데이터 타입처럼 언어의 모든 부분에 사용 가능한 객체를 말 함

일급 함수는 값, 변수 등의 컨테이너에 저장될 수 있고

다른 함수의 매개변수로 사용되거나

다른 함수의 반환값으로 사용될 수 있음

 

다른 함수를 매개변수로 받아들이거나

다른 함수를 반환값으로 반환하는 함수를 고차함수 라고 함

 

함수는 일급객체이기 때문에, 아래처럼 함수를 값에 넣을 수 있음

 

def hi = "hi"

val copyHi = hi

사용 : copyHi

 

def hi() = "hi"

val copyHi = hi

사용 : copyHi()


def hi(name:String) = "hi "+name

val copyHi = hi

사용 : copyHi("eyeballs")


def hi(name:String, ch:Char) = "hi "+name+ch

val copyHi = hi _

사용 : copyHi("eyeballs", '!')

 

def hi(name:String)(ch:Char) = "hi "+name+ch

val copyHi = hi _

사용 : copyHi("eyeballs")('!')

 

함수는 일급객체이기 때문에, 아래처럼 함수의 파라미터에 함수를 넣을 수 있음

 

def reverser(s: String) = s.reverse

def hi(name: String, f:String => String) = {

  if(name!=null) f(name)

  else name

}

사용 : hi("abcde" reverser), hi(null, reverser)

결과 : edcba, null

 

아래처럼 함수 리터럴(익명함수) 를 사용하여 바로 값에 할당 가능

 

선언 : val hi = (s: String) => s.reverse

사용 : hi("abc")

 

선언 : def hi(name: String, f:String => String) = { if(name!=null) f(name) else name }

사용 : hi("abcde", s => s.reverse) , hi("abcde", (s:String) => s.reverse)

 

선언 : val logging = () => "logging..."

사용 : println(logging())

 

하지만 아래처럼 함수 리터럴을 선언에 사용하는 것은 안 됨

def hi(name:String, s=>s.reverse) = {....} //안 됨 

def hi(name:String, (s:String)=>s.reverse) = {....} //안 됨

 

 

 

 

자리표시자 구문은 함수 리터럴의 축약형임

지정된 매개변수를 와일드카드( _ )로 대체함

함수의 명식적 타입이 리터럴 외부에 지정되어 있고, 매개변수가 한 번 이상 사용되지 않는 경우에만 자리표시자 사용 가능

 

예를 들어 다음과 같이 사용 가능

val double: Int => Int = _*2

Int => Int 를 통해 함수에 입력값이 int 인 것을 알려주었고,

함수 내부에서 _ 가 한 번만 사용되었음

 

비슷한 예로 아래와 같이 사용 가능

선언 : val rev: String => String = _.reverse

사용 : rev("eyeballs")

 

선언 : def hi(name: String, f:String => String) = { if(name!=null) f(name) else name }

일반 사용 : hi("abcde", s => s.reverse) , hi("abcde", (s:String) => s.reverse)

자리표시자 사용 : hi("abcde", _.reverse)

 

선언 : def combination(x:Int, y:Int, f:(Int,Int)=>Int) = f(x,y)

일반 사용 : combination(2,3, (x,y) => x*y)

자리표시자 사용 : combination(2,3, _ * _)

 

선언 : def combination[A,B](x:A, y:A, f:(A,A)=>B) = f(x,y)

일반 사용 : combination[Int,Double](2,3, (x,y) => x/y*1.0)

자리표시자 사용 : combination[Int,Double](2,3, _ / _ * 1.0)

 

 

 

함수를 val 에 넣을 때 파라미터를 고정시킨 후에 넣을 수 있음

이것을 부분 적용 함수라고 함

예를 들어, 아래 함수에 들어가는 x, y  두 파라미터 중 하나는 고정하고 싶다면,

자리표시자를 사용한 부분에만 파라미터를 받을 수 있게 만들면 됨

 

def factorOf(x:Int, y:Int) = y%x == 0

val multipleOf3 = factorOf(3, _:Int)

사용 : factorOf(3, 10), multipleOf3(10)

 

아래처럼 부분 적용 함수를 만들어서 val 에 넣을 수 있음

 

val f = factorOf _

val f = factorOf(_, _)

val f = factorOf(3, _)

val f = factorOF(3, _:Int)

 

def factorOf(x:Int)(y:Int) = y%x == 0

val multipleOf3 = factorOf(3) _

사용 : multipleOf3(4), multipleOf3 {val a=2; a*2}

 

 

 

 

 

 

함수에 "이름에 의한 매개변수(call by name)"를 사용하면

리터럴 값이 와도 되고, 함수가 와도 됨

예를 들어 f 라는 함수의 매개변수로 이름에 의한 매개변수를 넣음

def doubles(x: => Int) = { print(s"got ${x} from doubles"); x*2 }

 

이 doubles 함수에 넣을 파라미터로 2, 5 등의 리터럴 값이 들어갈 수 있음

doubles(2) //결과 got 2 from doubles, 4

doubles(5) //결과 got 5 from doubles, 10

 

또한 함수 자체가 들어갈 수 있음

def f(a:Int) = {println(s"got ${a} from f"); a}

doubles(f(4)) //결과 got 4 from f, got 4 from doubles, for 4 from f, 8

 

이렇게 들어간 f 는 doubles 내부에서 사용 될 때마다 호출됨

위 출력 결과에도 from f 가 두 번 떴음, 왜냐하면 doubles 내부에 x 가 두 번 사용되기 때문

 

 

 

 

case 를 추가하여 함수에 파라미터를 특정지어 처리할 수 있음

 

선언 :

val caseFunc : Int=>String = {

  case 1 => "one"

  case -1 => "minus one"

  case _ => "the others"

}

사용 : caseFunc(1), caseFunc(-1), caseFunc(3)

 

 

 

 

 

 

 

 

 

Scala 의 class 는 Java 와 마찬가지로 new 로 생성할 수 있음

 

선언 : class User

생성 : val user = new User

확인 : user.isInstanceOf[User]  //true

user.isInstanceOf[AnyRef]  //true

user.isInstanceOf[Any]  //true

 

선언 : 

class User {

  val name : String = "eyeballs"

  def greet : String = s"hello ${name}"

  override def toString = s"user name is ${name}"

}

생성 : val user = new User

사용 : user.greet, new User().greet

 

아래처럼 생성자를 넣을 수 있으며, 이를 클래스 매개변수라고 함

클래스 매개변수 n 은 메소드(greet, toString 등) 내부에서 사용 불가능함

클래스 매개변수n 은 단지 필드를 초기화하거나 메소드에 전달되는 용도로만 사용됨

 

class User(n: String) {

  val name : String = n

  def greet : String = s"hello ${name}"

  override def toString = s"user name is ${name}"

}

 

클래스 매개변수 n 앞에 val, var 를 붙이면

n 은 클래스의 필드가 되기 때문에, 내부 메소드에서도 사용 가능

 

class User(val n: String) {

  def greet : String = s"hello ${n}"

  override def toString = s"user name is ${n}"

}

 

class A {

  override

}

class B extends A

class C extends B {

  override def toString = "C : "+getClass.getName

}

val a:A = new B

bal b:B = new A //에러남. 자식은 부모를 받아줄 수 없음

 

선언 : 

class Car( val make: String, var reserved: Boolean ) {

  def reserve(r: Boolean): Unit = {reserved = r}

}

생성 : val t = new Car("eyeballs company", false), val t = new Car(make = "eyeballs company", reserved = false)

사용 : t.reserve(true)

 

선언 :

class Car( val make: String = "eyeballs company", var reserved: Boolean = true, val year: Int = 2024) {

  def reserve(r: Boolean): Unit = {reserved = r}

}

생성 : val t = new Car(), val t = new Car(year=2025)

 

선언 :

class myClass[A](element: A) {

  val a:A = element

  print(a.isInstanceOf[A])

}

생성 : 아래 모두 true 를 출력

val myClass = new myClass(1)

val myClass = new myClass[Int](1)
val myClass = new myClass("eyeballs")

val myClass = new myClass[String]("eyeballs")

 

 

추상 클래스는 abstract 를 사용하여 선언 가능하며

자기 자신은 인스턴스를 생성하지 않고 오로지 다른 클래스에 의해 상속되어지기만 하는 클래스임

 

선언 : 

abstract class Car {

  val year: Int

  val color: String

}

사용 : 

class myCar extends Car {

  val year = 2024

  val color = "Red"

}

class myCar(val year:Int = 2024) extends Car {

  val color = "Red"

}

 

추상 클래스를 상속하지 않고도, 생성함과 동시에 내용을 구현하는 것으로 사용 가능함

 

선언 : abstract class Car (val year:Int) { def show }

생성 : val myCar = new Car(2024) {

  def show { println(s"this car is ${year}" years old) }

  }

사용 : myCar.show

혹은

new Car(2024) { def show { println(s"this car is ${year}" years old) } }.show

 

class 내 오버로딩도 가능함

class MyClass {

  def print(a:String) = println(a)

  def print(a:Int) = println(a)

  def print(a:String, b:Int) = println(a+" "+b)

}

 

apply 라는 메소드를 구현하면, 사용할 때 메소드 이름을 사용하지 않고 생성한 클래스 이름 그대로 사용 가능

 

선언 : 

class Multiple(factor: Int){

  def apply (input: Int): Int = input * factor

}

생성 : val triple = new Multiple(3)

사용 : triple(7), triple.apply(7)   //둘 다 결과 21

 

필드에 lazy 를 사용하면, 그 필드가 인스턴스 될 때만 생성(구현)되도록 할 수 있음

즉, lazy 필드에 처음 접근 할 때 초기화(initial) 됨

 

선언 :

class Lazy {

  val x = { println("now initial"); 1 }

  lazy val y = { println("lazy initial"); 2 }

}

생성 :

val l = new Lazy()  // 여기서 "now initial" 이 출력됨

println(l.y)   // 여기서 lazy val y 가 사용되었으므로, "lazy initial" 이 출력됨

 

 

 

 

 

 

 

기본적으로 Scala 는 프라이버시 제어를 추가하지 않음

우리가 작성한 모든 클래스는 누구나 인스턴스를 생성할 수 있고

클래스 내부 필드와 메소드에 접근 가능

 

하지만 원한다면 프라이버시 제어를 추가할 수 있음

바로 필드 메소드 앞에 protected 혹은 private 을 추가하는 것임

 

protected 가 붙은 필드와 메소드는 동일 클래스 혹은 그 클래스의 자식 클래스에서만 접근 가능하게 됨

 

선언 :

class User { protected val password = "12345" }

class CheckUser extends User { def isValid = ! password.isEmpty }

사용 : 

new User().password   // 접근 불가 에러

new CheckUser().isValid   // 결과 true. 자식 클래스인 CheckUser 에서는 User 의 password 에 접근 가능

 

private 이 붙은 필드와 메소드는 이를 정의한 클래스에서만 접근 가능하게 됨

 

선언 :

class User { private val password = "12345" }

사용 : 

new User().password   // 접근 불가 에러

class CheckUser extends User { def isValid = ! password.isEmpty }   // 자식 클래스에서 접근 불가하여 선언 에러가 발생

 

 

패키지 단위로 접근을 제어할 수 있도록 할 수 있음

 

선언 : 

package com.eyeballs {

  private[eyeballs] class Config {   // com.eyeballs 패키지 내부에서만 접근 가능

    val url = "eyeballs.tistory.com"

  }

  class Test { println(new Config().url) }

}

 

사용 : 

new com.eyeballs.Test   // 결과 : eyeballs.tistory.com

new com.eyeballs.Config   // com.eyeballs 가 아닌 외부 패키지에서 Config 에 접근 불가하기 때문에 에러 발생

 

 

final 을 이용하여, 어떤 클래스의 자식 클래스를 만들지 못하도록 하거나

자식이 부모의 필드, 메소드를 재정의 할 수 없도록 할 수 있음

 

final class A

class B extends A   // A 의 자식을 만들 수 없어 에러 발생

 

class A { final val a = "a" }

class B extends A { val a = "b" }   // 부모 클래스의 필드인 A.a 를 재정의 할 수 없어 에러 발생

 

 

 

 

 

 

 

 

class 와 비슷하지만, 용도가 다른 object, case class 에 대해 설명함

 

object 는 하나 이상의 인스턴스를 가질 수 없는 형태의 class

singleton 이 적용된 class 라고 보면 됨

object 는 new 키워드로 인스턴스를 생성하지 않음

대신 이름으로 직접 해당 객체에 접근함

object 에 최초로 접근할 때 (JVM 내에서) 자동으로 인스턴스화 됨

인스턴스화는 자동으로 생성되므로, 초기화를 위한 매개변수는 갖지 않음 (대신 apply 메소드에 넣을 매개변수는 갖을 수 있음)

 

object 는 다른 class 를 상속받을 수 있음

하지만 다른 class 가 object 를 상속받을 수 없음

왜냐면 object 의 필드, 메소드는 전역에서 접근 가능하므로, 자식 클래스를 만들 이유가 없기 때문

 

선언 : object Hi { println("call Hi"); def hi = "hi" }

사용 : println(Hi.hi)

결과 : call Hi, hi

 

Hi.hi 를 여러번 사용시, 최초 생성된 인스턴스가 재사용됨

singleton 성격을 갖기 때문에, 순수 함수를 구현하거나 DB 를 사용하는 I/O 함수, sparkSession 을 설정하는 용도 등으로 사용

 

class 와 이름이 같은 object 를 동반 객체 라고 부름

동반 객체에서는 class 의 private, protected 필드 및 메소드에 접근 가능함

 

선언 : 

class Multiplier(val x: Int) { def product(y:Int) = x*y }

object Multiplier { def apply(x: Int) = new Multiplier(x) }

 

사용 : 

val tripler = Multiplier(3)   // object 사용

val result = tripler.product(10)   // 결과 30

 

 

 

 

 

 

 

 

case class 는 자동으로 생성된 메소드 몇 가지를 갖은 상태로 (인스턴스가) 생성되는 클래스

case class 는 동반 객체도 자동으로 생성하며, 이 동반 객체도 자신만의 메소드를 자동으로 생성해둠

case class 는 주로 데이터를 저장하고 전송하는 역할로 사용되며

계층적인 클래스 구조를 위해 사용되지 않는 편

왜냐하면 자동으로 만든다는 그 메소드들은 상속받은 필드들은 고려하지 않기 때문

 

자동으로 만들어진다는 메소드들은 다음과 같음

 

이름 위치 설명
apply object (동반 객체) case class 를 인스턴스화하는 팩토리 메소드
copy class 요청받은 변경사항이 반영된 인스턴스의 사본을 반환. 매개변수는 현재 필드값으로 설정된 기본값을 갖는 클래스의 필드들
equals class 다른 인스턴스의 모든 필드가 이 인스턴스의 모든 필드와 일치하면 true 반환. 연산자 == 로도 호출 가능
hashCode class 인스턴스의 필드들의 해시 코드를 반환. 해시 기반의 컬렉션에 유용..
toString class 클래스명과 필드들을 모아 String 으로 반환
unapply object (동반 객체) 인스턴스를 그 인스턴스의 필드들의 튜플로 추출하여 패턴 매칭에 케이스 클래스 인스턴스를 사용할 수 있도록 함

 

선언 : case class Character (name: String, age: Int)

사용 : 

val a = Character ("AA", 2)

val b = a.copy(name="BB")

a == b   // false

 

 

 

 

 

 

 

 

리스트(List). 한 번 생성되면 내부 값을 바꿀 수 없음

내부에서 Linked List 로 구현되어 있음

 

val myList = List()

val myList = List(1,2,3)

val myList = List("a", "b", 1, 2)

myList(0)   // "a"

myList(1)   // "b"

myList(-1)   // error

myList(10)   // error

myList.size  // 4

myList.isEmpty  // false

myList == Nil   // false. 여기서 Nil 은 빈 값을 갖는 리스트인 List() 의 싱글톤 인스턴스

Nil == List()   // true

myList.head   // "a"

val tailList = myList.tail   // "b", 1, 2

for ( l <- myList ) { print(l+" ") }   // a b 1 2

 

foreach 는 함수를 취하고, 그 함수를 리스트의 모든 항목으로 호출함

myList.foreach( l => print(l+" ") )   // a b 1 2

 

map 은 단일 리스트 요소를 다른 값이나 타입으로 전환하는 함수를 취함

val newList = myList.map(l => "["+l+"]")   // [a], [b], [1], [2]

 

reduce 는 리스트 요소들을 앞에서부터 차례대로 두 개씩 선택한 후, 단일 항목으로 결합하는 함수를 취함

val combination = myList.reduce((a,b) => a+" "+b)   // "a b 1 2"

 

리스트를 생성하는 또 다른 방법은 :: 를 사용하는 것

val myList = 1 :: 2 :: 3 :: Nil

val newList = 0 :: myList   //0,1,2,3

 

혹은 두 리스트를 ::: 로 붙이는 것

val twoList = List(1,2) ::: List(3,4)   // 1,2,3,4

 

리스트에 ++ 를 사용하여 다른 컬렉션(이를테면 Set)을 붙일 수 있음

val twoCollections = List(1,2) ++ Set(3,3,3)  // List(1,2,3)

 

:+, +: 를 사용하여 간단하게 List 에 요소를 늘릴 수 있음

List(1,2,3) :+ 4   // List(1,2,3,4). Linked List 의 마지막까지 도달해야하기 때문에 성능 이슈 발생 가능

1 +: List(2,3,4)   // List(1,2,3,4)

 

== 를 사용하여 컬렉션(리스트, 집합.. 등) 간 비교 가능. 두 컬렉션의 타입과 내용이 같으면 true

List(1,2) == List(1,3)   // false

 

drop 으로 List 에서 처음의 n 개 요소를 제외함

val droppedList = List(1,2,3,4) drop 2   //List(3,4)

 

dropRight 로 List 에서 마지막의 n 개 요소를 제외함. Linked List 의 마지막 요소까지 순회해야하므로, 성능 이슈 발생 가능

val droppedList = List(1,2,3,4) dropRight 2   //List(1,2)

 

List 에서 distinct 로 중복 제거

List(1,2,1,2).distinct   // 1, 2

 

List 에 filter 추가하여 true 인 것만 남길 수 있음

val filteredList = List(1,2,3,4,5) filter (_>2)   // 3,4,5

 

nested List 가 포함된 경우, flatten 을 이용하여 내부 요소들을 모두 포함하는 단일 리스트를 만들 수 있음

List(List(1,2), List(3)).flatten   //1,2,3

 

근데 List 가 아닌 리터럴 값이 포함되어 있으면 에러가 발생함

List(List(1,2), List(3), 4).flatten   // error

 

partition 을 사용하여 조건의 참에 해당하는 리스트와 거짓에 해당하는 리스트 두 개를 만듦 (결과는 튜플이 됨)

val part = List(1,2,3,4,5) partition (_<3)

part._1 은 List(1,2)  // true 인 값들

part._2 는 List(3,4,5)   // false 인 값들

 

splitAt 을 사용하여 인덱스 기준으로 List 를 좌우로 쪼갬. 결과는 튜플이 됨

val split = List(1,2,3,4) splitAt 2

split._1 은  List(1,2)

split._2 는 List(3,4)

 

reverse 를 사용하여 List 요소의 순서를 뒤집음

List(1,2,3).reverse   // List(3,2,1)

 

slice 를 사용하여 List 요소의 단편만 가져옴. <=_<

List(1,2,3,4,5) slice (0,0)   // List()

List(1,2,3,4,5) slice (0,1)   // List(1)

List(1,2,3,4,5) slice (0,2)   // List(1,2)

List(1,2,3,4,5) slice (1,2)   // List(2)

List(1,2,3,4,5) slice (2,1)   // List()

 

take 를 사용하여 List 의 처음 n 개 요소만 추출함

List(1,2,3) take 2   // List(1,2)

 

takeRight 를 사용하여 List 의 마지막 n 개 요소만 추출함. Linked List 마지막 요소까지 순회해야 하므로 성능 이슈 발생 가능

List(1,2,3) takeRight 2   // List(2,3)

 

sorted 를 사용하여 List 요소를 정렬함. 사전 순 혹은 오름차순

List(3,2,1).sorted   // List(1,2,3)

List('c','b','a').sorted   // List('a','b','c')

 

sortBy 를 사용하여 원하는 기준으로 List 요소를 정렬함

List("abc","de","f") sortBy (_.size)   // List("f", "de", "abc")

 

zip 을 사용하면, 두 List 를 각 인덱스에 해당하는 요소들끼리 묶인 튜플의 리스트로 만들 수 있음

val z = List(1,2) zip List('a','b')

z 는 List( (1,a), (2,b) )

 

collect 와 case 를 사용하면, List 안의 요소들을 case 에 매칭된 것만 남기고, case 의 내용대로 변환함

List("a", "b", "c") collect {

  case "a" => "A"

}

결과 : List("A")

 

List("a", "b", "c") collect {

  case "a" => "A"

  case "b" => "B"

  case _ => "nothing"

}

결과 : List("A", "B", "nothing")

 

map 을 사용하면, List 안의 모든 요소들에 특정 함수를 적용한 결과값으로 치환함

List("a", "b", "c").map(_.toUpperCase)

결과 : List("A", "B", "C")

 

flatMap 을 사용하면, map 처럼 List 안의 각 요소들에 특정 함수를 적용한 결괏값으로 치환하지만, 

map 과 다르게 모든 결과를 평탄화하여 하나의 List 로 만들어줌

 

List("a,b,c","d,e,f").flatMap(_.split(","))

결과 : List("a","b","c","d","e","f")

 

List("a,b,c","d,e,f").map(_.split(","))

결과 : List(Array("a","b","c"), Array("d","e","f"))

 

List(1,2,3).max  // 3 최댓값

List(1,2,3).min  // 1 최솟값

List(1,2,3).product  // 6 모두 곱하기

List(1,2,3).sum  // 6 모두 더하기

 

contains 를 사용하여 List 내 요소를 포함하고 있는지 확인 가능

List(1,2,3) contains 2   // true

 

exists 를 사용하여 List 내 최소 하나의 요소가 조건자에 성립하는지 확인 가능

List(1,2,3).exists(_<2)   // true

List(1,2,3).exists(_<1)   // false

 

forall 을 사용하여 List 내 모든 요소가 조건자에 성립하는지 확인 가능

List(1,2,3).exists(_<2)   // false

List(1,2,3).exists(_<=3)   // true

 

startsWith 를 사용하여 List 의 처음 요소들이 특정 값을 갖는 List 로 시작하는지 확인 가능

List(1,2,3) startsWith List(1)   // true

List(1,2,3) startsWith List(1,2)   // true

List(1,2,3)startsWith List(1,3)   // false

 

endsWith 를 사용하여 List 의 마지막 요소들이 특정 값을 갖는 List 로 끝나는지 확인 가능

List(1,2,3) endsWith List(3)   // true

List(1,2,3) endsWith List(2,3)   // true

List(1,2,3) endsWith List(1,3)   // false

 

아래서부터 reduce 처럼 List의 값을 하나로 축소하는 함수에 대해 설명함

 

foldLeft 를 사용하여, List 를 주어진 시작값과 함께 왼쪽에서부터 축소

List(1,2,3).foldLeft(0)(_-_)   // -6

이유 :

0 - 1 = -1

-1 - 2 = -3

-3 - 3 = -6

List(1,2,3).foldLeft(1)(_-_)   // -5

List(1,2,3).foldLeft(2)(_-_)   // -4

 

foldRight 를 사용하여, List 를 주어진 시작값과 함께 오른쪽에서부터 축소

List(1,2,3).foldRight(0)(_-_)   // 2

이유 : 

3 - 0 = 3

2 - 3 = -1

1 - -1 = 2

 

List(1,2).foldRight(0)(_-_)   // -1

이유 : 

2 - 0 = 2

1 - 2 = -1

 

reduceLeft 를 사용하여 List 를 첫번째 요소값과 함께 왼쪽에서부터 축소

List(1,2,3).reduceLeft(_-_)   // -4

이유 : 

1 - 2 = -1

-1 - 3 = -4

 

reduceRight 를 사용하여 List 를 마지막 요소값과 함께 오른쪽에서부터 축소

List(1,2,3).reduceRight(_-_)   // 2

이유 : 

2 - 3 = -1

1 - -1 = 2

 

scanLeft 를 사용하여 List 를 주어진 시작값과 함께 왼쪽에서부터 처리한 각 누곗값의 List 를 반환

List(1,2,3).scanLeft(0)(_-_)   // List(0, -1, -3, -6)

이유 : 

처음 주어진 값 = 0 

0 - 1 = -1

-1 - 2 = -3

-3 - 3 = -6

 

scanRight 를 사용하여 List 를 주어진 시작값과 함께 오른쪽에서부터 처리한 각 누곗값의 List 를 반환

List(1,2,3).scanRight(0)(_-_)   // List(2, -1, 3, 0)

이유 : 

처음 주어진 값 = 0

3 - 0 = 3

2 - 3 = -1

1 - -1 = 2

 

reduceLeft, reduceRight 처럼 방향성이 있는 것과

그냥 reduce 처럼 방향성이 없는 것에 차이가 존재함

이를테면, 아래와 같은 연산을 진행할 때

방향이 존재하는 foldLeft, foldRight 는 실행 가능하고 

방향이 존재하지 않는 fold 는 실행이 불가능함

List(1,2,3).foldLeft(false) {(a,b) => if(a) a else b==2}   // true

List(1,2,3).foldLeft(false) {(a,b) => if(a) a else b==4}   // false

List(1,2,3).foldRight(false) {(a,b) => if(b) b else a==2}   // true

List(1,2,3).foldRight(false) {(a,b) => if(b) b else a==4}   // false

List(1,2,3).fold(false) {(a,b) => if(a) a else b==2}   // error

 

 

 

 

 

 

 

 

 

 

집합(Set). 한 번 생성되면 내부 값을 바꿀 수 없음 

 

val mySet = Set()

val mySet = Set(1,2,3)

val mySet = Set("a","b",1,1,2,2)   //"a", "b", 1, 2

 

 

 

 

 

 

 

 

 

 

 

Map (Java 의 HashMap, Python 의 dictionary). 이 역시 생성된 후 내부 값 변경이 불가능

 

val myMap = Map()

val myMap = Map(1->"a", 2->"b")

myMap(1)   // "a"

myMap(2)   // "b"

myMap.contains(1)   // true

myMap.contains(3)   // false

for ( pairs <- myMap ) {

  val key = pairs._1

  val value = pairs._2

  println(key+" "+value)

}

결과 : 1 "a", 2 "b"

 

 

 

 

 

collection 간 전환은 아래와 같이 가능함

 

mkString 를 사용하여 collection 을 구분자로 구분된 String 으로 전환

List(1,2,3).mkString("-")   //"1-2-3"

Set(1,2,3).mkString("-")   //"1-2-3"

 

toBuffer 를 사용하여 collection 을 가변의 List 로 전환

List(1,2,3).toBuffer   // Buffer(1,2,3)

Map(1->1, 2->2, 3->3).toBuffer   // Buffer((1,1), (2,2), (3,3))

 

toList 를 사용하여 collection 을 불변의 List 로 전환

Set(1,2,3).toList   // List(1,2,3)

 

toMap 을 사용하여 튜플이 담긴 collection 을 Map 으로 전환

Set((1,1), (2,2), (3,3)).toMap   // Map(1->1, 2->2, 3->3)

 

toSet 을 사용하여 collection 을 Set 으로 전환

List(1,1,2,2).toSet   // Set(1,2)

 

toString 을 사용하여, collection 의 타입과 내용을 String 으로 전환

List(1,2,3).toString   // "List(1,2,3)"

Set(1,2,3).toString   // "Set(1,2,3)"

 

 

JVM 을 사용하는 Scala 와 Java 간 collection 은 기본적으로 서로 호환되지 않지만

asJava, asScala 를 통해 호환되도록 만들 수 있음

 

import collection.JavaConverters._

val scalaList = List(1,2,3)

scalaList: List[Int] = List(1,2,3)

val javaList = scalaList.asJava

javaList: java.util.List[Int] = [1, 2, 3]

val scalaListAgain = javaList.asScala

scalaListAgain: scala.collection.mutable.Buffer[Int] = Buffer(1,2,3)

 

 

 

 

 

 

collection 을 match 표현식에 사용하는 방법 예제

 

val myList = List(1,2,3)

 

myList(0) match {

  case 1 => "A"

  case _ => "B"

}

결과 : "A"

 

myList(0) match {

  case x if x > 0 => "A"

  case _ => "B"

}

결과 : "A"

 

myList match {

  case x if x contains (2) => "A"

  case _ => "B"

}

결과 : "A"

 

myList match {

  case List(1,

 

 

 

 

 

 

 

call by name, call by ref 차이는?

+ Recent posts