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 차이는?
'Scala' 카테고리의 다른 글
[Scala] 함수 생성 val 와 def 차이 설명 링크 (0) | 2020.08.25 |
---|---|
[Scala] Regex 를 이용하여 패턴 분석하기 설명 링크 (0) | 2020.08.13 |
[Scala] 스칼라 공부 (0) | 2020.07.27 |
[Scala] 타입 연산 (0) | 2020.07.10 |