리터럴 : 5, 'A', "abc" 처럼 코드에 바로 등장하는 데이터

값 : 불변의 타입을 갖는 저장 단위. val 로 생성

변수 : 가변의 타입을 갖는 저장 단위. var 로 생성

타입 : 우리가 자업하는 데이터의 종류

 

 

값, 변수의 값은 GC 의 대상이 된다.

 

 

val x : Int = 5

val y : String = "abc"

var z : Double : 3.14

var c : Char = '@'

 

 

Byte 부호 있는 정수 1byte 

Short 부호 있는 정수 2byte 

Int 부호 있는 정수 4byte

Long 부호 있는 정수 8byte 

Float 부호 있는 부동소수점 4byte

Double 부호 있는 부동소수점 8byte

 

 

타입을 바꾸려면 toInt, toDouble 등의 메소드를 사용한다.

asInstanceOf 도 사용가능하지만, 이 메소드는 에러를 반환하기 때문에 위험하다.

var a : Double = 3.1

print(a.toInt)

val x : String = "112"
print(x.toDouble)

 

 

두 문자열이 같은지 비교하려면 == 를 사용한다.

물론 JAVA 처럼 equals 메소드도 사용 가능.

 

 

python 처럼 문자열 반복 출력을 위해 * 을 사용할 수 있다.

val x : String = "a"

print(x * 10)

"aaaaaaaaaa"

 

 

문자열 앞에 s 를 붙여 시작하는 것을 문자열 보간이라고 한다.

문자열 내에 외부 값과 변수명을 인식하고 해석한다.

val x : String = "apple"

print( s"This is an $x" )

print( s"This is an ${x}xl size" )

 

 

정규표현식 예제

 

찾기 : matches 명령어를 이용하여 문자열을 정규표현식으로 찾음

예) "a b c d e" matches ".*[c].*"

결과) true

 

모두 바꾸기 : replaceAll 명령어를 이용하여 문자열 내 정규표현식 매칭된 것을 바꿈

예) "a b c d e" replaceAll ("c.d", "z")

결과) a b z e

 

가장 처음 것만 바꾸기 : replaceFirst  명령어를 이용하여 문자열 내 정규표현식 매칭된 첫번째 것을 바꿈

예) "a a a" replaceFirst ("a", "z")

결과) z a a

 

 

 

Any : 모든 타입의 부모 클래스

AnyVal : 모든 값 타입의 부모 클래스

AnyRef : 모든 참조값 타입의 부모 클래스

Null : null 값을 의미하는 모든 AnyRef 타입의 하위 클래스

Unit : 값이 없음을 나타내는 AnyVal 타입의 하위 클래스

Nothing : 모든 타입의 하위 클래스(Unit, Null보다 아래)

 

 

boolean 비교 할 때,

&&, || 는 게을러서 첫 번째 인수로 충분하다면 두 번째 인수는 평가하지 않는다.

&, | 는 항상 두 번째 인수까지 모두 평가한다.

 

 

튜플은 단순히 둘 이상의 (서로 다를 수 있는) 값을 담는 컨테이너이다.

접근할 때는 ._1, ._2 로 접근 가능하다.

tuple 내의 원소들은 모두 val 인 듯.

val tuple = (5, "eyeballs", true)

println(tuple._1) //5

println(tuple._2) //eyeballs

println(tuple._3) //true

 

 

{} 괄호로 묶여있는 다수의 표현식 중, 가장 마지막 표현식해당 블록의 반환값(return 값)이 된다.

val amount = { val x = 5*20 ; x+10}

print(amount) //110

 

 

if (true) {

}

else {

}

는 다음과 같이 축약해서 쓸 수 있음

val max = if(x>y) x else y

 

 

if-else 보다 더 자주 사용된다는 match 표현식은 다음과 같이 사용 가능하다.

먼저 등장한 표현식에 맞는 결과값을 매칭시켜준다.

매치 표현식은 0개 혹은 단 하나의 결과만 매칭하여 반환하기 때문에, break 문이 없다.

x>y match {

  case true => x

  case false => { y }

}

 

매치 표현식에서의 case 문은 위에 중괄호{} 와 마찬가지로,

마지막에 나타나는 표현식만 return 한다.

또한, | 를 사용하여 여러 값들을 하나의 case 문에 같이 묶을 수 있다.

또한, 매치가 안 되는 값을 위해 other 혹은 _ 키워드를 사용할 수 있다.

val status : Int = 400

val message = status match {

  case 200 => "OK"

  case 400 => "error"

  case 500 => {

    "OK"

    "error"

  }

  case 600 | 700 | 800 =>{

    "umm... what?"

    "error"

  }

  case other =>{

    "its nothing"

  }

  case _ =>{

    "its nothing"

  }

}

println(message)

 

매치 표현식의 case 문에 간단한 if 문을 추가할 수 있다.

만약 값이 null 이 아니라면 true, null 이라면 false 를 반환하는 매치표현식은 다음과 같다.

 

val res :String = "abc"
res match {
  case s if s!=null => println(s"$s is not null")
  case s => println(s"$s is null")
}

 

여기서의 s 는 res를 받는다.

 

다음과 같이 type 에 따라서도 매칭이 가능하다.

val res : Any = "str"
res match {
  case r : String => println("String")
  case r : Double => println("Double")
  case r : Int => println("Int")
}

 

for(x <- 1 to 7) {}   // 결과 : 아무것도 반환되지 않음

for(x <- 1 to 7) { x }  // 결과 : 아무것도 반환되지 않음

for (x <- 1 to 7) { print(x) }  // 결과 : 1234567 이 출력됨

val a = for (x <- 1 to 7) { x }  // 결과 : a = () 즉, a 에 아무것도 들어가지 않음

val a = for (x <- 1 to 7) yield {x}   // 결과 : Vector(1, 2, 3, 4, 5, 6, 7)

val b = for(x <- 1 to 20 if x%3==0) yield x   // 결과 : Vector(3, 6, 9, 12, 15, 18)

 

위 예처럼 yield 가 붙으면, 호출된 모든 표현식의 반환값은 컬렉션으로 반환됨

 

2중 loop

for {

    x <- 1 to 2
    y <- 1 to 3

} {println(s"$x $y")}

결과 : 

1 1

1 2

1 3

2 1

2 2

2 3

 

아래 for 문에서 pow 는 for 문이 돌아갈 때 마다 새로 할당된다.

그 값이 yield 로 인해 객체화되어 컬렉션 내에 들어가게 된다.

val powerOf2 = for(i <- 0 to 8 ; pow = 1 << i) yield pow

 

아래처럼 for 문에 if 문(필터)을 두 번 이상 붙일 수 있으며, and 조건으로 연결됨

for{

    x <- "a,ab,abc,abcd".split(",")

    if x != null

    if x.size > 2

    if x.size < 4

} { println(x) }

결과 : abc 가 출력됨

 

for 문에 조건식은 {} 나 () 둘 다 가능한 듯??

 

 

스칼라에서 함수란, 이름을 갖고 있고, 재사용이 가능한 표현식이다.

def toNum : Int = "244".toInt  // 이럴꺼면 차라리 그냥 val 에 넣어버려도 될 듯
println(toNum)

def toMaxNum (str : String, str2 : String) : Int ={
  val a = str.toInt
  val b = str2.toInt
  if (a>b) a else b
}
println(toMaxNum("12","44"))

 

def toMaxNum (str : String, str2 : String) : Int ={
if (str.length==0 || str2.length==0) return -1
  val a = str.toInt
  val b = str2.toInt
  if (a>b) a else b
}
println(toMaxNum("12",""))

 

위에 예제처럼 매개변수는 () 괄호를 사용하는데,

매개변수가 아예 없는 함수 역시 빈 괄호를 넣어서

함수를 사용할 때 값이나 변수와 헷갈리지 않도록 한다.

 

def sayHi = println("Hi!")
sayHi // 값이랑 헷갈림

def sayHiDef() = println("Hi as Def!")
sayHiDef()

 

함수 매개변수로 리터럴, 값, 변수, 그리고 표현식도 올 수 있다.

def temp(t : Int) : String = s"$t 도씨 입니다."
println(temp(24))
println(temp{1+2+3+4+5+6+7})

 

반환값을 갖지 않는 함수를 프로시저라고 한단다.

반환값이 없기 때문에 대신 Unit 을 반환

기본값을 갖거나, 이름으로 매개변수를 넣을 수 있다.

def greet(prefix:String = "Ms", name:String) = println(s"$prefix $name")

greet("Ms", "Brown") //Mr Brown
greet(name="Brown", prefix="Mr") //Mr Brown
greet(name="Brown") //Ms Brown

 

 

 

매개 변수의 개수를 다이나믹하게 받을 수 있다.

def sum(items : Int*) : Int = {
var total = 0
for(i <-items) total+=i
total
}
println(sum(1,2,3)) // 6
println(sum(2,3,4,5)) //14

def sum(items:Int*, plus:Int) // 가변 매개변수가 앞에 오면 error
def sum(plus:Int, items:Int*) // 가변 매개변수는 무조건 맨 뒤에 와야 okay

 

아래 같은 함수 만들기도 가능
def cal(items : Int*)(mul:Int, items2:Int*) : Int = {
var total = 0
for(i <-items) total+=i
total*=mul
for(i <-items2) total-=i
total
}

println(cal(1,2,3)(10,5,6,7,8,9,10))

 

아래처럼 타입 자체를 매개변수로 줄 수도 있다. generic 같은 느낌으로

함수 호출 할 때 타입을 정해서 넣어줌


def identity[TYPE](value : TYPE) = println(value.getClass.getSimpleName)


identity[String]("Hello World!") // String
identity[Double](3.14) // Double

identity[Integer](1) // Integer

 

 

 

 

 

Lambda 함수를 어떻게 사용하는지 아래 간단한 예제로 나타내었다.

먼저 다양한 함수를 만든다.

def log() : String ="---now logging---"
def identity(value : Int) : Int = value
def max(v1 : Int, v2 : Int) : Int = if (v1>v2) v1 else v2

 

위에서 만든 함수들을 아래와 같이 새롭게 값을 정의한다.

새로 작성되는 값의 타입은 "함수의 매개변수타입 => 함수의 반환타입" 이 된다.

logVal 의 경우 ()=>String 이라는 타입을 갖는 값val 이고

identityVal 의 경우 Int=>Int 라는 타입을 갖는 값val 이고

maxVal 의 경우 (Int,Int)=>Int 라는 타입을 갖는 값val 이다.
val logVal : () => String = log
val identityVal : Int => Int = identity
val maxVal : (Int, Int) => Int = max

 

사용 할 때는 함수처럼 사용하면 된다.
println(logVal())
println(identityVal(3))
println(maxVal(3,5))

 

위에 케이스처럼 val 함수를 만들 때 def 에서 가져온 타입을 직접 지정해줘도 되지만,

def 에 이미 타입이 명시되어 있으므로 _ 로 퉁 칠 수 있다.

 

val logVal = log _
val identityVal = identity _
val maxVal = max _

 

사용 방법은 똑같다.

 

위와 같이 함수를 다시 값으로 바꿔서 넣는 방법을 배운 이유는,

함수를 다른 함수의 파라미터로 넣는 방법을 배우기 위해서이다.

def func(f:()=>String) = { f() }

func(log())

 

 

 

이름이 없는 함수인 '함수 리터럴'을 만드는 방법은 아래와 같다.

val doubler= ( x :Int ) => { x*2 }
val beingPolite = ( s :String ) => { s+"이거예용" }

val greeter = ( name : String ) => { s"Hello, $name!" }

 

 

또한, 함수 리터럴에서 매개 변수를 _로 치환할 수 도 있다.

이를테면,

매개변수로 String 하나를 갖고 반환값으로 String 을 갖는 함수는 아래와 같다.

 

val beingPolite :String => String = { _ +" 이거예용." }

 

매개변수로 Int 두 개를 갖고 반환값으로 Int 를 갖는 함수는 아래와 같다.

 

val sum : (Int,Int) => Int = _ + _

 

여기서 _ 는 매개변수의 값을 나타낸다.

_ 는, 함수 바깥에서 _에 들어올 값의 타입이 명시되어있고,

함수 내에서 딱 한 번만 사용될 때 사용 가능하다.

일반 함수처럼 실행하면 된다.

 

println(beingPolite("밥 먹었냐"))
println(sum(55,45))

 

다양한 함수 작성 방법을 비교해보자.

def mymax(a: Int, b: Int) : Int = if (a>b) a else b
val maximize : (Int, Int) => Int = mymax

val maximize : (Int, Int) => Int = mymax(_,_)

val maximize = (a : Int, b : Int) => if(a>b) a else b

 

 

 

함수의 파라미터로 다른 함수를 받는 함수를 고차 함수라고 한다.

아래처럼 매개변수로 "매개변수로 String 하나를 갖고 반환값으로 String 을 갖는 함수" 를 갖는 함수를 만들 수 있다.

 

def safeStringOp(s : String , f : String => String) = {
  if(s==null) s else f(s)
}

val reverser : String => String = _.reverse

println(safeStringOp(null, reverser))
println(safeStringOp("abcde", reverser))

println(safeStringOp("abcde", _.reverse)) // 앞의 "abcde" 를 _ 가 받는다. 함수를 직접 넣어준 케이스

 

예제에서는 reverser 라는 함수가 f 에 들어갔지만,

f 자리에는 "매개변수로 String 하나를 갖고 반환값으로 String 을 갖는 함수"면 어떤 함수든 올 수 있다.

 

다음과 같은 함수도 만들 수 있다.

def combination(a : Int, b : Int, f : (Int, Int)=> Int) = f(a,b)

println(combination(3,5, _*_))
println(combination(3,5, _+_))
println(combination(3,5, (a:Int, b:Int)=>{if (a>b) a else b}))

 

위와 같이 _ 를 사용하려면, 함수 내에서 _ 가 단 한 번만 사용되어야 함

 

 

 

 

컬렉션(List, Map, Set)한 번 생성되면 값 변경을 할 수 없음

값을 추가하거나 제외하려면 다음과 같은 컬렉션을 사용해야 함

값 변경이 불가능한 타입 (불변) 값 변경이 가능한 타입 (가변)
collection.immutable.List collection.mutable.Buffer
혹은
List.newBuilder[Any]
collection.immutable.Set collection.mutable.Set
혹은
Set.newBuilder[Any]
collection.immutable.Map collection.mutable.Map
혹은
Map.newBuilder[Any]

 

 

 

리스트는 아래처럼 사용 가능하다.

 

val nums = List(1,2,3,4,5) // 선언
println(nums(0)) // 1 출력
println(nums(2)) // 3 출력

println(nums(-1)) // error

for (i <- nums){ println(i) } // 리스트 순회

 

nums.foreach((i:Int)=>{println(i)})
nums.foreach(println(_))
val double = nums.map((i:Int)=>(i*2))
val double = nums.map(_ * 2)
val sum = nums.reduce((a:Int,b:Int)=>a+b) //15
val sum = nums.reduce(_+_) //15

nums.head // 1

nums.tail // List(2,3,4,5)

nums.isEmpty // false

List() == Nil // 아무것도 없는 빈 List 는 Nil 이라는 예약어와 동일함

1::2::3::Nil // List(1,2,3)

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

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

List(1,2) ++ Set(2,3) // List(1, 2, 2, 3)

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

List(1) == List(2) // false 내부 값으로 비교

List(1,2,3).drop(1) // 앞에서부터 제외. List(2,3)

List(1,2,3) drop (1) // 앞에서부터 제외 List(2,3)

List(1,2,3) dropRight (1) //뒤에서부터 제외 List(1,2)

List(1,2,3,4,5) filter (_%2==0) // List(2,4)

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

List(1,2,3,4,5) partition (_<3) // (List[Int], List[Int]) = (List(1, 2),List(3, 4, 5)) 조건으로 나누기

List(1,2,3,4) splitAt 2 // (List(1, 2),List(3, 4)) index 로 나누기. 2 이하와 2 초과

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

List(1,2,3,4) slice (1,3) // List(2, 3) index 1이상 index 3미만

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

List("abc","ab","a") sortBy (_.size) // List(a, ab, abc) sortBy 에 들어간 함수의 반환값 기준으로 정렬

List(1,2,3) take (2) // List(1,2) 처음부터 n개 가져오기

List(1,2,3) takeRight (2) // List(2,3) 뒤에서부터 n개 가져오기

List(1,2,3) zip List("a","b","c") // List((1,a), (2,b), (3,c)) 두 리스트를 인덱스가 동일한 값끼리 뭉쳐 튜플 리스트 만들기

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

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

List(1,2,3).sum // 6

List(2,3,4).product // 24

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

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

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

List(1,2,3) exists (_>2) // true 최소 하나의 요소가 조건에 성립하는지 확인

List(1,2,3) forall (_>2) // false 모든 요소가 조건에 성립하는지 확인

val b = Buffer(1,1,2)

b += 3 // 맨 뒤에 추가 Buffer(1,1,2,3)

b -= 1 // 맨 앞에서 하나 제외 Buffer(1,2,3)

 

val l =List.newBuilder[String]

l += "a"

l += ("b","c")

l.result

결과 : List("a","b","c")

 

 

 

Map 은 다음과 같이 사용 가능하다.

 

val  m = Map(1->"1", "2"->2) // 선언

println(m(1)) // "1" 출력

println(m("2")) // 2 출력

println(m(3)) // 에러

 

val n = m - 1 + ("a" -> "a") // 1을 지우고 새로운 요소 추가

n 의 결과 : Map(2 -> 2, a -> a)

 

Map 을 List 로 변경하면 튜플쌍을 갖는 List 가 된다.

val m = Map(1->"1", "2"->2)

val l = m.toList

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

val m = l.toMap

결과 : Map(1 -> 1, 2 -> 2)

 

val m = Map.newBuilder[String, Any]

m += ("1" -> 1)

m += (("2" -> "2"), ("3" -> true))

m.result

결과 : Map(1 -> 1, 2 -> 2, 3 -> true)

 

 

 

 

Option 컬렉션은 단 하나의 요소만 갖고 있으며

단일 값의 존재 또는 부재를 나타낸다.

코딩중에 잠재적으로 null 이 될 수 있는 값을 Option 으로 감싸서

컴파일 타임에 null 처리가 잘 되었는지 확인 가능하다.

 

다음과 같이 Option 의 타입이 Some 이냐 값이 None 이냐에 따라

단일 값의 행보를 알 수 있음

 

scala> def divide(d : Double):Option[Double] = {

     if (d==0) None

     else Option(1.0/d)

     }

divide: (d: Double)Option[Double]

 

scala> divide(2.0)

res17: Option[Double] = Some(0.5)

 

scala> divide(0.0)

res18: Option[Double] = None

 

혹은 아래와 같이 확인 가능

Option(null).isEmpty // true

Option(null).isDefined // false

Option("not null").isEmpty // false

Option("not null").isDefined // true

 

Option 에서 값을 가져오려면 getOrElse 메소드를 사용함

Option(null).getOrElse("I'm null")

결과 : String = I'm null

Option("not null").getOrElse("I'm null")

결과 : String = not null

 

 

 

 

클래스는 다음과 같이 사용 가능하다.

 

class MyClass //선언

val myClass = new MyClass

 

class MyClass {

  val name = "eyeballs"

  override def toString = s"I'm ${name}"

}

val myClass = new MyClass

myClass.toString()

 

class MyClass(name : String) {   // 혹은 class MyClass(val name : String)

  override def toString = s"I'm ${name}"

}

val myClass = new MyClass("eyeballs")

myClass.toString()

 

상속이 가능함

class Parent{

  def parent = "I'm parent"

}

class Child extends Parent {

  def child = "I'm child"

}

val child : Parent = new Child // 부모 클래스 타입으로 반환받을 수 있다.

child.parent // 부모 요소에 접근. "I'm parent" 출력

 

 

class 내에 apply 메소드를 정의해두면,

apply 라는 메소드 이름 없이 단지 클래스 이름만으로 호출이 가능하다.

 

class MyClass{

     def apply(name:String) = s"I'm ${name}"

     }

val myClass = new MyClass

myClass("eyeballs")  // I'm eyeballs

myClass.apply("eyeballs")  // I'm eyeballs

위 아래가 동일하다.

 

 

class 내 요소, 즉 필드(val값, var변수 등)들

class 가 인스턴스화 될 때 초기화되었다.

필드 값 앞에 lazy 가 붙으면 그 필드는

class 가 인스턴스화 될 때 초기화되는 것이 아니라

필드값이 사용되어질 때 인스턴스화 된다.

 

def MyClass{

     lazy val l = {println("lazy!")}

}

 

 

 

scala 에서는 객체(object) 를 singleton Class 라고 정의함

즉, 객체란 단 하나의 인스턴스만을 갖는 Class

객체는 new 로 인스턴스화 하지 않으며, 인스턴스화 없이 객체 이름으로 그대로 접근 가능하다.

객체는 해당 객체에 최초 접근할 때 자동으로 인스턴스화 된다고 한다.

객체는 어떠한 파라미터도 받지 않는다고 한다.

 

object MyObject { val hi = "hi!" }  // 객체 선언

MyObject.hi  // 객체 이름으로 곧바로 접근. "hi!" 출력.

 

객체 내 val 은 전역변수 같은 느낌으로 사용하면 될 것 같고

객체 내 def 는 순수함수 혹은 외부 입출력에 이용하는 함수로 사용하면 될 것 같다.

아래 예제와 같이 오로지 유틸성을 강조한 순수함수처럼 사용하면 될 듯

 

object HtmlUtils {

  def removeMarkup(input: String)={

    input.replaceAll("""</?\w[^>]*>""","").replaceAll("<.*>","")

  }

}

HtmlUtils.removeMarkup("<body><h1>hi!</h1></body>")

결과 : hi!

 

scala 애플리케이션에서 코드 실행의 최초 시작점을 object main 으로 결정한다고 한다.

 

ojbect MyApp {

  def main(args: Arrag[String]){

    println("app just started")

  }

}

 

$scalac MyApp.scala

$scala MyApp

"app just started" 가 출력됨

 

 

 

 

 

 

 

 

 

 

 

 

 

+ Recent posts