티스토리 뷰

https://play.kotlinlang.org/

1, 변수와 상수

1
2
3
4
5
6
7
fun main() {
    var a: Int = 10 // var 변수명: 자료형 = 값
    val b: Int = 20 // val 상수명: 자료형 = 값, 상수는 값 변경 안됨
    
    var a = 10 // 자료형을 지정하지 않아도 추론해서 자료형 생략 가능함
    val b = 20
}
cs

- 숫자형
Double - 64비트 부동소수점
Float : 32비트 부동소수점
Long : 64비트 정수
Int : 32비트 정수
Short : 16비트 정수
Byte : 8비트 정수

리터럴 : 자료형을 알 수 있는 표기 형식, 리터럴에 따라 코틀린 컴파일러는 자료형을 추론한다.

1
2
3
4
5
6
fun main() {
    val a = 10         // val a:Int    
    val b = 10L        // val b:Long
    val c = 10.0    // val c: Double
    val d = 10.0f    // val d:Float
}
cs

- 문자형
String : 문자열
Char : 하나의 문자, 숫자가 아닌것이 자바와 다른점

1
2
3
4
5
6
7
fun main() {
   val str = "안녕하세요"    // val str: String    
   val char = '안'            // val char: Char
   val str2 = """오늘은    
    날씨가 좋습니다.
    """                        // 여러줄로 쓸때는 큰 따옴표를 3 사용
}
cs

문자열 비교는 == 를 사용, 자바의 equals() 와 같다.
참고로, 자바의 오브젝트 비교는 == 인데, 코틀린에서 오브젝트 비교는 === 사용한다.
$ : 문자열 리터럴 내부에 변수

1
2
3
4
5
6
fun main() {
   val str = "안녕"    
   println(str + "하세요"// 안녕하세요
   println("$str 하세요")  // 안녕 하세요
   println("${str}하세요"// 안녕하세요
}
cs

2. 배열

1
2
3
4
5
fun main() {
  val numbers: Array<Int> = array(123,45)
  val numbers2: arrayof(12345// 자료형 생략
  numbers[0= 5
}
cs

3. 함수 선언
fun 함수명(인수1: 자료형1, 인수2: 자료형2...): 반환자료형

1
2
3
4
5
6
7
fun main() {
   greet("안녕")
}
 
fun greet(str: String) {
    println(str) // 안녕
}
cs

4. 제어문
- if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
    val a = 10
    val b = 20
    val max = a
    if(a < b) max = b
    
    if(a > b) {
        max = a
    } else {
        max = b
    }
    
    val max = if(a > b) a else b
}
cs

- when
자바의 switch 문과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
    val x = 1
    when(x) {
        1 -> println("x==1"// 값 하나
        23 -> println("x==2 or x==3"// 여러값은 콤마로 구분
        in 4..7 -> println("4부터 7까지"// in 연산자로 범위지정
        !in 8..10 -> println("8부터 10 사이가 아님")
        else -> { // 나머지
            println("x는 1 이나 2가 아님")
        }
    }    
}
cs

 

1
2
3
4
5
6
7
8
fun main() {
    val number = 1
    val numStr = when(number % 2) {
        0 -> "짝"
        else -> "홀"
    }
    println(numStr) // 홀
}
cs

 

1
2
3
4
5
6
7
8
9
fun isEven(num: Int) = when(num % 2) {
    0 -> "짝"
    else -> "홀"
}
 
fun main() {
    val number = 1
    println(isEven(number)) // 홀
}
cs


- for

1
2
3
4
5
6
fun main() {
    val numbers = arrayOf(12345)
    for(num in numbers) {
        println(num) // 각 행에 1 2 3 4 5 
    }    
}
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
    for(i in 1..3) {
        println(i) // 각 행에 1 2 3
    }   
    
    for(i in 0..10 step 2) {
        println(i) // 각 행에 0 2 4 6 8 10
    }
    
    for(i in 10 downTo 0 step 2) {
        println(i) // 각 해에 10 8 6 4 2 0
    }
}
cs

- while

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
    var x = 10
    println(x) // 10
    
    while(x > 0) {
         x--
         println(x) // 각 행에 9 8 7 6 5 4 3 2 1 0
    }
     
    x = 10
    do {
        x--
        println(x)
    } while(x > 0// 각 행에 9 8 7 6 5 4 3 2 1 0
}
cs

5. 클래스
- 선언

1
2
3
4
5
6
class Person {    
}
 
fun main() {
    val person = Person()
}
cs

- 생성자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person(var name:String) {
// 빈 생성자를 가지는 클래스
 
class Person {
    constructor(name: String) {
        println(name)
    }
}
 
class Person(name: String) {
    init { // 인스턴스화 할때 제일 먼저 초기화 되는 init 블록
        println(name)
    }
}
cs

- 프로퍼티

1
2
3
4
5
6
7
8
class Person(var name:String) {
// 빈 생성자를 가지는 클래스
 
fun main() {
    val person = Person("멋쟁이")
    person.name = "키다리" // 쓰기
    println(person.name) // 읽기
}
cs

- 접근 제한자
public : 전체 공개, 생략 가능
private : 현재 파일 내부에서만 사용
internal : 같은 모듈 내에서만 사용
protected : 상속받은 클래스에서 사

1
2
3
4
5
6
class A {
    val a = 1
    private val b = 2
    protected val c = 3
    internal val d = 4
}
cs

- 상속
상속 가능하게 하려면 open 키워드를 클랫 선언 앞에 추가한다.

1
2
3
open class Animal {}
class Dog : Animal() {    
}
cs

만약 상속받을 클래스가 생성자를 가지고 있다면 다음과 같이 상속 받을 수 있다.

1
2
3
open class Animal(val name: String) {}
class Dog(name: String) : Animal(name) {    
}
cs

- 내부 클래스
inner 키워드를 사용하며, 내부 클래스는 외부 클래스에 대한 참조를 가지고 있다.

1
2
3
4
5
6
7
8
9
10
class OuterClass {
    var a = 10
    
    // 내부 클래스
    inner class OuterClass2 {
        fun something() {
            a = 20 // 접근 가능
        }
    }
}
cs

- 추상 클래스
미구현 클래스이며 abstract 키워드를 사용한다.
추상 클래스는 직접 인스턴스화 할 수 없고 다른 클래스가 상속하여 미구현 메서드를 구현해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract class A {
    abstract fun func()
    fun func2() {        
    }
}
 
class B: A() {
    override fun func() {
        println("hello")
    }
}
 
fun main() {
    val a = A() // 에러
    val b = B() // OK
}
cs

6. 인터페이스
미구현 메서드를 포함하여 클래스에서 이를 구현한다.
추상 클래스와 비슷하지만 클래스가 단일 상속만 되는 반면 인터페이스는 다중 구현이 가능하다. 
주로 클래스에 동일한 속성을 부여해 같은 메서드라도 다른 행동을 할 수 있게 하는데 사용한다.
- 선언

1
2
3
interface Runnable {
    fun run()
}
cs

구현이 없는 메서드뿐 아니라 구현된 메서드를 포함할 수 있다. 이는 자바8의 default 메서드와 같다.

1
2
3
4
interface Runnable {
    fun run()
    fun fastRun() = println("빨리 달린다")
}
cs

- 구현

1
2
3
4
5
6
7
8
9
10
interface Runnable {
    fun run()
    fun fastRun() = println("빨리 달린다")
}
 
class Human: Runnable {
    override fun run() {
        println("달린다")
    }
}
cs

- 상속과 인터페이스를 함께 구현
상속은 하나의 클래스만 상속하는 반면, 인터페이스는 콤마로 구분하여 여러 인터페이스를 동시에 구현할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
open class Animal {}
 
interface Runnable {
    fun run()
    fun fastRun() = println("빨리 달린다")
}
 
interface Eatable {
    fun eat()
}
 
class Dog: Animal(), Runnable, Eatable {
    override fun eat() {
        println("먹는다")
    }
    override fun run() {
        println("달린다")
    }
}
 
fun main() {
   val dog = Dog()
   dog.run() // 달린다
   dog.eat() // 먹는다
}
cs

7. null 가능성
기본적으로 객체를 불변으로 보고 null 값을 허용하지 않습니다. null 값을 허용하려면 별도의 연산자가 필요하고 null 을 허용한 자료형을 사용할 때도 별도의 연산자들을 사용하여 안전하게 호출해야 합니다.
- null 허용?
코틀린에서는 기본적으로 null 값을 허용하지 않습니다. 따라서 모든 객체는 생성과 동시에 값을 대입하여 초기화해야 한다.

1
2
3
4
5
6
fun main() {
       val a: String // 에러 : 초기화를 반드시 해야 함
    val b: String = null // 에러 : null 값을 허용하지 않음
    val c: String= null // OK : ? 기호를 붙여 null 허용한다
       println(kotlin)
}
cs

- lateinit 키워드로 늦은 초기화
변수 앞에 lateinit 키워드를 붙여 초기화를 나중에 할 수 있다.
초기화를 잊는다면 잘못된 null 값을 참조하여 앱이 종료될 수 있으니 주의해야 한다.

1
2
3
4
5
fun main() {
       lateinit var a: String // OK
    a = "hello"
       println(a) // hello
}
cs

lateinit 는 다음 조건에서만 사용 가능하다.
-- var 변수만 사용 가능.
-- null 값으로 초기화할 수 없다.
-- 초기화하기 전에는 변수를 사용할 수 없다.
-- Int, Long, Double, Float 에서는 사용할 수 없다.

- lazy 로 늦은 초기화
lateinit 이 var 로 선언한 변수의 늦은 초기화라면
lazy 는 값을 변경할 수 없는 val 을 사용할 수 있다. 
val 선언 뒤에 by lazy 블록에 초기화에 필요한 코드를 작성한다.
마지막 줄에는 초기화할 값을 작성한다.
str 이 처음 호출될때 초기화 블록의 코드가 실행된다. println() 메서드로 두번 호출하면 처음에만 "초기화" 가 출력된다.

 
1
2
3
4
5
6
7
8
fun main() {
    val str:String by lazy {
    println("초기화")
    "hello"
    }
    println(str) // 각 행에 초기화 hello
    println(str) // hello
}
cs
 

lazy 로 늦은 초기화를 하면 앱이 시작될 때 연산을 분산시킬 수 있어 빠른 실행에 도움이 된다.상
lazy 는 val 에서만 사용 가능하다.
조건이 적기 때문에 상대적으로 lateinit 보다 편하게 사용할 수 있다.

- null 값이 아님을 보증 (!!)
변수 뒤에 !! 를 추가하면 null 값이 아님을 보증하게 된다.

1
2
3
4
5
6
fun main() {
    val name:String= "키다리"
    val name2: String = name // 에러
    val name3: String= name // OK
    val name4: String = name!! // OK
}
cs

- 안전한 호출 (?.)
메서드 호출 시 점(.) 연산자 대신 ?. 연산자를 사용하면 null 값이 아닌 경우에만 호출된다.
다음 코드는 str변수의 값이 null 값이 아니라면 대문자로 변경하고, null 값이면 null 을 반환한다.

1
2
3
4
5
6
7
fun main() {
    val str: String= null
    var upperCase = if(null != str) str else null // null
    println(upperCase) // null
    upperCase = str?.toUpperCase() // null
    println(upperCase) // null
}
cs

- 엘비스 연산자 (?:)
안전한 호출 시 null 이 아닌 기본값을 반환하고 싶을때 엘비스 연산자를 함께 사용한다.
마지막 코드에는 이제 null 이 아닌 "초기화하시오' 라는 문자열을 반환한다.

1
2
3
4
5
6
7
fun main() {
    val str:String= null
    var upperCase = if(null != str) str else null
    println(upperCase) // null
    upperCase = str?.toUpperCase() ?: "초기호를하시오"
    println(upperCase) // 초기화를하시오
}
cs
 컬렉션

- 리스트

1
2
3
4
5
6
fun main() {
       val foods: List<String> = listOf("라면""갈비""밥")
    println(foods) // [라면, 갈비, 밥]
    val foods1 = listOf("라면""갈비""밥"// 형추론으로 자료형 생략 가능
    println(foods1) // [라면, 갈비, 밥]
}
cs

요소를 변경하는 리스트를 작성할 때는 mutableListOf() 메서드를 사용한다.

1
2
3
4
5
6
7
8
9
10
fun main() {
    val foods = mutableListOf("라면""갈비""밥")
    foods.add("초밥")
    foods.removeAt(0)
    foods[1= "부대찌개"
    println(foods) // [갈비, 부대찌개, 초밥]
    println(foods[0]) // 갈비
    println(foods[1]) // 부대찌개
    println(foods[2]) // 초밥
}
cs

- 맵
mapOf() : 읽기전용 맵
mutablemapOf() : 수정 가능한 맵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
    val map = mapOf("a" to 1"b" to 2"c" to 3// 읽기전용 맵
    println(map) // {a=1, b=2, c=3}
    val citiesMap = mutableMapOf( // 변경 가능 맵
        "한국" to "서울",
        "일본" to "동경",
        "중국" to "북경")
    println(citiesMap) // {한국=서울, 일본=동경, 중국=북경}
    citiesMap["한국"= "서울특별시" // 요소에 덮어쓰기
    println(citiesMap) // {한국=서울특별시, 일본=동경, 중국=북경}
    citiesMap["미국"= "워싱턴" // 추가    
    println(citiesMap) // {한국=서울특별시, 일본=동경, 중국=북경, 미국=워싱턴}
   
    for((k, v) in map) { // 탐색
        println("$k->$v"// 각 행에 a->1 b->2 c->3
    }
}
cs

- 집합
중복되지 않는 요소들로 구성된 자료구조
setOf() : 읽기전용 집합
mutableSetOf() 수정 가능한 집합

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
    val citySet = setOf("서울""수원""부산"// 읽기전용 집합
    println(citySet) // [서울, 수원, 부산]
    
    val citySet2 = mutableSetOf( // 변경 가능 맵
        "서울""수원""부산")
    println(citySet2) // [서울, 수원, 부산]
    
    citySet2.add("안양")
    println(citySet2) // [서울, 수원, 부산, 안양]
    
    citySet2.remove("수원")
    println(citySet2) // [서울, 부산, 안양]
    
    println(citySet2.size) // 집합의 크기 3
    println(citySet2.contains("서울")) // true
}
cs

9. 람다식
하나의 함수를 표현하는 방법으로 익명 클래스나 익명 함수를 간결하게 표현할 수 있어 유요하다.
람다식은 코드를 간결하게 해주는 장점이 있지만
디버깅이 어렵고 남발하는 경우 오히려 코드 가독성이 떨어져 주의해서 사용해야 한다.

먼저 두 수를 인수로 받아서 더해주는 add() 메서드 이다.

1
2
3
fun add(x: Int, y: Int): Int {
    return x + y
}
cs

이 코드는 다음과 같이 표현할 수 있다. 반환 자료형을 생략하고 블록 과 return 을 생략할 수 있다.

1
fun add(x: Int, y: Int) = x + y
cs

또한 다음과 같이 표현할 수 도 있다. 코틀린의 람다식은 다음과 같이 항상 중괄호로 둘러 싸여 있다.
내용으로는 인수 목록을 나열하고 -> 이후에 본문이 위치한다. 람다식을 변수에 저장할 수 있고 이러한 변수는 일반 함수처럼 사용할 수 있다.

1
2
3
4
fun main() {
    var add = { x: Int, y: Int -> x + y }
    println(add(25)) // 7
}
cs

- SAM 변환
코틀린에서는 추상 메서드 하나를 인수로 사용할 때는 함수를 인수로 전달하면 편하다.
자바로 작성된 메서드가 하나인 인터페이스를 구현할 때는 대신 함수를 작성할 수 있다.
이를 SAM (Single Abstract Method) 변환이라고 한다.

안드로이드에서는 버튼의 클릭 이벤트를 구현할때 onClick() 추상 메서드만을 가지는 View.OnClickListener 인터페이스를구현한다.

안드로이드에서 버튼에 클릭 이벤트 리스너를 구현하는 코드를 일반적인 익명 클래스를 작성하듯 작성한 코드이다. 여기서 View.OnClickListener 인터페이스에는 OnClick() 추상 메서드가 하나 있기 때문에 onClick() 메서드를 오버라이드하고 있다.

1
2
3
4
5
button.setOnClickListener(object: View.OnClickListener {
    override fun onClick(v: View?) {
        // 클리시 처리
    }
})
cs

구현하는 인터페이스에 구현해야 할 메서드가 하나뿐일 때는 이를 람다식으로 변경할 수 있다.
다음 코드는 람다식으로 변경되어 코드가 줄었지만 괄호도 중첩되어 있고 기호도 많고 뭔가 코드는 복잡해 보인다.

1
2
3
button.setOnClickListener({ v: View? ->
    // 클리시 처리
})
cs

메서드 호출 시 맨 뒤에 전달되는 인수가 람다식인 경우에는 람다식을 괄호 밖으로 뺄 수 있다. 
위 코드는 하나의 인수만 있고 람다식이 전달되었기 때문에 마지막 인수라고 볼 수 있다.

1
2
3
button.setOnClickListener() { v: View? ->
    // 클리시 처리
}
cs

람다가 어떤 메서드의 유일한 인수인 경우에는 메서드의 괄호를 생략할 수 있다.

1
2
3
button.setOnClickListener { v: View? ->
    // 클리시 처리
}
cs

컴파일러가 자료형을 추론하는 경우에는 자료형을 생략할 수 있다.

1
2
3
button.setOnClickListener { v ->
    // 클리시 처리
}
cs

만약 클릭 시 처리에 어떤 코드를 작성했는데 v 인수를 사용하지 않는다면 v 라는 이름은 _ 기호로 대치할 수 있다.
인수가 많은 경우에 꼭 사용하는 인수 이외에는 _ 기호로 변경하여 애초에 잘못 사용하는 것을 방지할 수도 있다.
이런한 방식은 다른 함수형 언어에서도 적용되는 함수형 언어의 특징 중 하나이다.

1
2
3
button.setOnClickListener { _ ->
    // 클리시 처리
}
cs

그리고 람다식에서 인수가 하나인 경우에는 이를 아예 생략하고 람다 블록 내에서 인수를 it 으로 접근할 수 있다.
다음 코드에서 it 는 View? 타입의 v 인수를 가리킨다.

1
2
3
button.setOnClickListener {
    it.visibility = View.GONE
}
cs

 

위 7가지 형태는 모두 같은 결과를 나타내지만 마지막 코드가 가장 읽기 쉽다.
중요한 것은  SAM 변환은 자바에서 작성한 인터페이스일 때만 동작한다는 것이다.
코틀린에서는 인터페이스 대신에 함수를 사용하는 것이 좋다.

10. 기타 기능
확장함수 : 원래 있던 클래스에 기능을 추가하는 함수
형변환 : 숫자형 자료형끼리 쉽게 형변환 가능
형 체크 : 변수의 형이 무엇인지 검사하는 기능
고차함수 : 인자로 함수를 전달하는 기능
동반 객체 : 클래스의 인스턴스 생성없이 사용할 수 있는 객체
let() : 블록에 자기 자신을 전달하고 수행된 결과를 반환하는 함수
with() : 인자로 객체를 받고 블록에서 수행된 결과를 반환하는 함수
apply() : 블록에 자기 자신을 전달하고 이 객체를 반환하는 함수
run() : 익명함수처럼 사용하거나, 블록에 자기 자신을 전달하고 수행된 결과를 반환하는 함수

1
2
3
4
5
6
7
8
fun Int.isEven() = this % 2 == 0
 
fun main() {    
    val a = 5
    val b = 6    
    println(a.isEven()) // false
    println(b.isEven()) // true
}
cs


- 형변환

1
2
3
4
5
6
7
fun main() {    
    val a = 10L
    val b = 20
    val c = a.toInt() // Long 을 int 로 형변환
    val d = b.toDouble() // Int 를 Double 로 형변한
    val e = a.toString() // Long 을 String 으로 형변환
}
cs
1
2
val intStr = "10"
val str = Integer.parseInt(intStr) // 문자열을 숫자로 
cs
1
2
3
4
5
6
7
open class Animal
class Dog: Animal()
 
fun main() {    
    val dog = Dog()
    val animal = dog as Animal // dog를 Animal 형으로 변환
}
cs

- 형 체크
is 키워드를 사용하여 형을 체크할 수 있다.

1
2
3
4
5
6
fun main() {    
    val str = "hello"
    if(str is String) {
        println(str.toUpperCase()) // HELLO
    }
}
cs

- 고차 함수
함수의 인수로 함수를 전달하거나 함수를 반환하는 함수
add 함수는 x, y, callback 3개의 인수를 받아 callback 에 x 와 y 의 합을 전달한다.
여기서 callback 은 하나의 숫자를 받고 반환이 없는 함수이다.

1
2
3
4
5
6
7
fun add(x: Int, y: Int, callback: (sum: Int) -> Unit) {
    callback(x + y)
}
 
fun main() {    
    add(53, { println(it) }) // 8   
}
cs

- 동반 객체
자반의 static 과 같은 정적인 메서드를 만들수 있는 키워드가 없고, 대신 동반 객체로 구현한다.

1
2
3
4
5
6
7
8
9
10
11
class Fragment {
    companion object {
        fun newInstance() {
            println("생성됨")
        }
    }
}
 
fun main() {    
    val fragment = Fragment.newInstance() // 생성됨
}
cs

여기서 동반 객체 내부의 메서드는 Fragment 클래스와 아무 관계가 없는 정적인 존재이다.

- let()
블록에 자기 자신을 인수로 전달하고 수행된 결과를 반환한다.
인수로 전달한 객체는 it 으로 참조하고 안전한 호출 연산자 ?. 와 함께 사용하면 null 값이 아닐때만 실행하는 코드를 다음과 같이 나타낼 수 있다.

1
2
3
4
5
6
7
fun main() {
    val str = "10"
    val result = str?.let {
        Integer.parseInt(it)
    }
    println(result) // 10
}
cs

str 이 null 이 아닐때만 정수로 변경해서 출력한다.
- with() 
인수로 객체를 받고 블록에 리시버 객체로 전달한다. 그리고 수행된 결과를 반환한다.
리시버 객체로 전달된 객체는 this로 접근할 수 있고, this 는 생략이 가능하므로 다음과 같이 작성할 수 있다.
안전한 호출이 불가능하여 str 이 null 값이 아닌 경우에만 사용해야 한다.

1
2
3
4
5
6
fun main() {
    val str = "abc"
    with(str) {
        println(uppercase()) // ABC
    }    
}
cs

- apply()
블록에 객체 자신이 리시버 객체로 전달되고 이 객체가 반환된다.
객체의 상태를 반환시키고 그 객체를 다시 반환할 때 주로 사용한다.

1
2
3
4
val result = car?.apply {
    car.setColor(Color.RED)
    car.setPrice(1000)
}
cs

- run()
익명 함수처럼 사용하는 방법과 객체에서 호출하는 방법을 모두 제공한다.
익명 함수처럼 사용할 때는 블록의 결과를 반환한다. 블록안에 선언된 변수는 모두 임시로 사용되는 변수이다.
이렇게 복잡한 계산에 임시변수가 많이 필요할때 유용하다.

1
2
3
4
5
6
7
8
9
fun main() {
    val avg = run {
        val korean = 100
        val english = 80
        val math = 50
        (korean + english + math) / 3.0
    }
    println(avg) // 76.66666666666667
}
cs

객체에서 호출하는 방법은 객체를 블록의 리시버 객체로 전달하고 블록의 결과를 반환한다.
안전한 호출을 사용할 수 있어서 with() 함수보다는 더 유용하다.

1
2
3
str?.run {
    println(uppercase()) // ABC
}
cs

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31