[Android/Flutter 교육] 10일차

MSU·2024년 1월 9일

Android-Flutter

목록 보기
10/85
post-thumbnail

코틀린 강의 끝나고 자바의 쓰레드, 스트림 공부할 예정
미니프로젝트 학생정보관리프로그램 만들 예정

라이브러리 배포를 위한 서버
Maven
Gradle
안드로이드는 Gradle을 사용하고 있음
Reflection 라이브러리도 Gradle에서 이용

코틀린에서 Reflection 라이브러리 설치 방법

  1. File > Project Structure 메뉴를 클릭한다
  2. Project Structre 창에서 좌측의 Modules를 클릭하고 중간에서 main을 클릭해준다.
  3. 우측에 있는 Dependencies 탭을 눌러준다.
  4. 상단에 있는 + 를 눌러주고 Library를 클릭해준다
  5. Choose Libraries 창에서 하단에 있는 New Library 버튼을 눌러주고 From Maven을 선택해준다.
  6. 검색 창에서 kotlin-reflect 로 검색해준다
  7. 검색 결과에서 원하는 버전을 선택한다.
  8. OK~ OK~ 를 눌러준다.
  9. Choose Libraries 창에서 내려받은 라이브러를 선택하고 Add Selected 버튼을 눌러준다.
  10. Project Structure 창에서 OK를 눌러 닫아준다.
  11. 좌측의 Project 창에서 build.gradle.kts 파일을 열어준다.
  12. dependencies 부분에 다음과 같이 라이브러리 사용을 설정해준다.
    implementation("org.jetbrains.kotlin:kotlin-reflect")

Reflection - Gradle

  • 클래스의 정보를 파악하고 싶을 때 문서를 통해 정보를 찾는 방법과 Reflection을 통해 클래스를 파악하는 방법이 있다.

클래스 타입 파악

import kotlin.reflect.KClass

fun main(){
    // 클래스 타입
    // 코틀린에서의 클래스
    val a1:KClass<String> = String::class
    println("String의 클래스 이름(코틀린) : $a1")
    // 자바에서의 클래스
    val a2:Class<String> = String::class.java
    println("String의 클래스 이름(자바) : $a2")

    // 변수를 통해 접근할 수 있는 객체의 클래스 타입을 파악한다.
    val str1 = "안녕하세요"
    val a3:KClass<out String> = str1::class
    println("str1을 통해 접근할 수 있는 객체의 클래스 타입(코틀린) : $a3")

    val a4:Class<out String> = str1::class.java
    println("str1을 통해 접근할 수 있는 객체의 클래스 타입(자바) : $a4")
    
    val test1 = TestClass(100,200,300)

    val a5 = test1::class
    val a6 = test1::class.java
    println("test1의 클래스 타입(코틀린) : $a5")
    println("test1의 클래스 타입(자바) : $a6")
}

class TestClass(var number1:Int, var number2:Int, var number3:Int){

    var number4:Int = 0
    var number5:Int = 0

    constructor(a1:Int) : this(100,200,300)

    constructor(a1:Int, a2:Int) : this(100,200,300)

    fun testMethod1(){

    }
    fun testMethod2(a1:Int, a2:Int){

    }

}
String의 클래스 이름(코틀린) : class kotlin.String
String의 클래스 이름(자바) : class java.lang.String

str1을 통해 접근할 수 있는 객체의 클래스 타입(코틀린) : class kotlin.String
str1을 통해 접근할 수 있는 객체의 클래스 타입(자바) : class java.lang.String

test1의 클래스 타입(코틀린) : class TestClass
test1의 클래스 타입(자바) : class TestClass

클래스 정보 분석

fun main(){
    val test1 = TestClass(100,200,300)

    // 클래스 정보 분석
    println("추상 클래스 인가 : ${test1::class.isAbstract}")
    println("Companion 인가 : ${test1::class.isCompanion}")
    println("Data 클래스 인가 : ${test1::class.isData}")
    println("Final 클래스 인가 : ${test1::class.isFinal}")
    println("open 클래스 인가 : ${test1::class.isOpen}")
    println("중첩 클래스 인가 : ${test1::class.isInner}")
    println("Sealed 클래스 인가 : ${test1::class.isSealed}")
}

class TestClass(var number1:Int, var number2:Int, var number3:Int){

    var number4:Int = 0
    var number5:Int = 0

    constructor(a1:Int) : this(100,200,300)

    constructor(a1:Int, a2:Int) : this(100,200,300)

    fun testMethod1(){

    }
    fun testMethod2(a1:Int, a2:Int){

    }

}
추상 클래스 인가 : false
Companion 인가 : false
Data 클래스 인가 : false
Final 클래스 인가 : true
open 클래스 인가 : false
중첩 클래스 인가 : false
Sealed 클래스 인가 : false

생성자 정보 파악

  • 생성자의 정보를 가지고 있는 객체가 리스트에 담겨져서 전달됨
fun main(){
    val test1 = TestClass(100,200,300)

    // 생성자 정보
    // 생성자의 정보를 가지고 있는 객체가 리스트에 담겨져서 전달된다.
    val constructors = test1::class.constructors
    println(constructors)
    
    for(con in constructors){
        println("생성자 : $con")
        
        // 생성자의 매개 변수 목록을 가져온다.
        for(param in con.parameters){
            println("매개변수 순서 : ${param.index}")
            println("매개변수 타입 : ${param.type}")
            println("매개변수 이름 : ${param.name}")
        }
    }
}

class TestClass(var number1:Int, var number2:Int, var number3:Int){

    var number4:Int = 0
    var number5:Int = 0

    constructor(a1:Int) : this(100,200,300)

    constructor(a1:Int, a2:Int) : this(100,200,300)

    fun testMethod1(){

    }
    fun testMethod2(a1:Int, a2:Int){

    }

}
[fun `<init>`(kotlin.Int): TestClass, fun `<init>`(kotlin.Int, kotlin.Int): TestClass, fun `<init>`(kotlin.Int, kotlin.Int, kotlin.Int): TestClass]

생성자 : fun `<init>`(kotlin.Int): TestClass
매개변수 순서 : 0
매개변수 타입 : kotlin.Int
매개변수 이름 : a1

생성자 : fun `<init>`(kotlin.Int, kotlin.Int): TestClass
매개변수 순서 : 0
매개변수 타입 : kotlin.Int
매개변수 이름 : a1
매개변수 순서 : 1
매개변수 타입 : kotlin.Int
매개변수 이름 : a2

생성자 : fun `<init>`(kotlin.Int, kotlin.Int, kotlin.Int): TestClass
매개변수 순서 : 0
매개변수 타입 : kotlin.Int
매개변수 이름 : number1
매개변수 순서 : 1
매개변수 타입 : kotlin.Int
매개변수 이름 : number2
매개변수 순서 : 2
매개변수 타입 : kotlin.Int
매개변수 이름 : number3

주생성자 정보 파악

fun main(){
    val test1 = TestClass(100,200,300)
    
    // 주생성자
    val primaryCon = test1::class.primaryConstructor
    // 없으면 null이 반환된다.
    if(primaryCon != null){
        println("주 생성자 : $primaryCon")

        // 주 생성자의 매개변수들
        for(param in primaryCon.parameters){
            println("매개변수 순서 : ${param.index}")
            println("매개변수 타입 : ${param.type}")
            println("매개변수 이름 : ${param.name}")
        }
    }
}

class TestClass(var number1:Int, var number2:Int, var number3:Int){

    var number4:Int = 0
    var number5:Int = 0

    constructor(a1:Int) : this(100,200,300)

    constructor(a1:Int, a2:Int) : this(100,200,300)

    fun testMethod1(){

    }
    fun testMethod2(a1:Int, a2:Int){

    }

}
주 생성자 : fun `<init>`(kotlin.Int, kotlin.Int, kotlin.Int): TestClass

매개변수 순서 : 0
매개변수 타입 : kotlin.Int
매개변수 이름 : number1

매개변수 순서 : 1
매개변수 타입 : kotlin.Int
매개변수 이름 : number2

매개변수 순서 : 2
매개변수 타입 : kotlin.Int
매개변수 이름 : number3

프로퍼티 정보 파악

fun main(){
    val test1 = TestClass(100,200,300)
    
    // 프로퍼티
    val properties = test1::class.declaredMemberProperties
    for(prop in properties){
        println("프로퍼티 이름 : ${prop.name}")
    }
}

class TestClass(var number1:Int, var number2:Int, var number3:Int){

    var number4:Int = 0
    var number5:Int = 0

    constructor(a1:Int) : this(100,200,300)

    constructor(a1:Int, a2:Int) : this(100,200,300)

    fun testMethod1(){

    }
    fun testMethod2(a1:Int, a2:Int){

    }

}
프로퍼티 이름 : number1
프로퍼티 이름 : number2
프로퍼티 이름 : number3
프로퍼티 이름 : number4
프로퍼티 이름 : number5

메서드 정보 파악

fun main(){
    val test1 = TestClass(100,200,300)
    
    // 메서드
    val methods = test1::class.declaredMemberFunctions
    for(met in methods){
        println("메서드 이름 : ${met.name}")
        println("메서드 반환타입 : ${met.returnType}")
        println("메서드의 매개변수들 : ${met.parameters}")
    }
}

class TestClass(var number1:Int, var number2:Int, var number3:Int){

    var number4:Int = 0
    var number5:Int = 0

    constructor(a1:Int) : this(100,200,300)

    constructor(a1:Int, a2:Int) : this(100,200,300)

    fun testMethod1(){

    }
    fun testMethod2(a1:Int, a2:Int){

    }

}
메서드 이름 : testMethod1
메서드 반환타입 : kotlin.Unit
메서드의 매개변수들 : [instance parameter of fun TestClass.testMethod1(): kotlin.Unit]

메서드 이름 : testMethod2
메서드 반환타입 : kotlin.Unit
메서드의 매개변수들 : [instance parameter of fun TestClass.testMethod2(kotlin.Int, kotlin.Int): kotlin.Unit, parameter #1 a1 of fun TestClass.testMethod2(kotlin.Int, kotlin.Int): kotlin.Unit, parameter #2 a2 of fun TestClass.testMethod2(kotlin.Int, kotlin.Int): kotlin.Unit]

연산자 오버로딩

연산자 동작 원리

  • 자바에서 정수와 정수를 더하면 더하기 연산을 수행하여 결과를 전달
  • '구버전 코틀린' 은 모든 값을 객체로 만들어 객체로 관리하기 때문에 코드에 정수를 작성하면 정수로 관리하는 것이 아닌 정수 값을 관리하는 객체를 생성하여 객체를 관리하게 됨
  • 최신 버전 코틀린은 상황에 따라 정수 자체를 사용하거나 객체를 만들거나 함
  • 코틀린에서는 객체와 객체의 연산자를 통해 연산을 하게되면 객체 내의 메서드를 호출하게 됨
    호출된 메서드의 객체를 반환받아 이를 연산의 결과로 사용하게 됨
  • 단항,증감,산술,대입,비교 연산자 등 각자의 연산자에 대응하는 메서드가 호출이 됨
    ex) a + b => a.plus(b)
fun main() {

    val obj1 = TestClass1(100,200)
    val obj2 = TestClass1(10,20)

    // 더하기
    val obj3 = obj1 + obj2
    println("obj3.a1 : ${obj3.a1}")
    println("obj3.a2 : ${obj3.a2}")

    // 빼기
    val obj4 = obj1 - obj2
    println("obj4.a1 : ${obj4.a1}")
    println("obj4.a2 : ${obj4.a2}")
}

class TestClass1(var a1:Int, var a2:Int){

    // 더하기 연산자를 사용하면 호출되는 메서드
    // 연산자를 기준으로 좌측의 객체를 통해 메서드가 호출되고
    // 매개변수로 우측에 있는 객체의 주소값이 전달된다.
    operator fun plus(target:TestClass1) : TestClass1{
        val r1 = this.a1 + target.a1
        val r2 = this.a2 + target.a2

        val result = TestClass1(r1, r2)
        return result
    }

    operator fun minus(target:TestClass1) : TestClass1{
        val r1 = this.a1 - target.a1
        val r2 = this.a2 - target.a2

        val result = TestClass1(r1, r2)
        return result
    }
}
obj3.a1 : 110
obj3.a2 : 220
obj4.a1 : 90
obj4.a2 : 180

함수형 프로그래밍

  • 코틀린이 갖고 있는 특징
  • 자바 언어는 완벽한 객체지향 프로그래밍 언어이다.
  • 모든 코드는 클래스를 설계하고 메서드를 만들어주고 클래스를 통해 객체를 생성해서 사용해야한다.
  • 하지만 Kotlin는 함수만 만들어 사용하는 것을 지원한다.
  • Kotlin은 함수 사용을 보다 편리하게 할 수 있도록 다양한 개념들이 제공된다.
fun main() {
    val r1 = testFun1(100,200)
    println("r1 : $r1")

    val r2 = testFun2(100,200)
    println("r2 : $r2")
}

// 매개변수로 들어오는 값을 계산하여 반환하는 함수
fun testFun1(a1:Int, a2:Int) : Int{
    return a1 + a2
}

// 위의 함수는 다음과 같이 작성할 수 있다.
// = 다음에 작성한 수식을 계산하여 반환해준다.
fun testFun2(a1:Int, a2:Int):Int = a1 + a2
r1 : 300
r2 : 300

람다

  • 만약 매개변수로 들어오는 값을 계산하여 반환하는 함수 내부의 코드가 여러 줄이라면...
fun main() {
    val r3 = testFun3(100,200)
    println("r3 : $r3")
}

// 만약 매개변수로 들어오는 값을 계산하여 반환하는 함수 내부의 코드가 여러 줄이라면...
fun testFun3(a1:Int, a2:Int) : Int{
    val r1 = a1 + 100
    val r2 = a2 + 200
    val r3 = r1 + r2
    return r3
}
r3 : 600
  • 매개변수로 들어오는 값을 가지고 계산한다음 반환하는 함수의 코드가 여러줄일 경우 아래와 같이 작성하는 것은 불가능하다.
fun testFun4(a1:Int, a2:Int) : Int = {
    val r1 = a1 + 100
    val r2 = a2 + 200
    val r3 = r1 + r2
    r3
} // 에러
  • 대신 람다라는 것을 사용할 수 있다.
  • 람다 : 전달 받은 값을 가지고 계산을 한 다음 계산의 결과를 반환해주는 식을 람다식이라고 부르기도 하고 람다 함수라고 부르기도 한다.
    코틀린 람다식을 가지고 있는 함수를 람다 함수라고 부른다.
val lambda1 : (Int, Int) -> Int = {a1:Int, a2:Int -> a1 + a2}
           //매개변수 타입 //반환타입 //매개변수의 이름 정의 //반환값
  • (Int, Int) : 매개변수의 타입을 정의
  • -> Int : 반환 타입을 정의
  • {a1:Int, a2:Int : 매개변수의 이름을 정의. 앞서 정의한 매개변수의 타입과 일치해야 함
  • -> a1 + a1} : 반환할 값을 작성한 부분
fun main() {
    val r4 = lambda1(100, 200)
    println("r4 : $r4")
}
r4 : 300
  • 코틀린은 람다함수 대신 람다식만 작성해도 된다.
val lambda2 = {a1:Int, a2:Int -> a1 + a2}

fun main() {
    val r5 = lambda2(100, 200)
    println("r5 : $r5")
}
r5 : 300
  • 타입을 생략할 수 있다.
val lambda3 : (Int, Int) -> Int = {a1, a2 -> a1 + a2}

fun main() {
    val r6 = lambda3(100, 200)
    println("r6 : $r6")
}
r6 : 300
  • 람다는 -> 다음에 나오는 코드 중에 제일 마지막에 작성한 수식의 결과를 반환한다.
    testFun3과 같이 코드가 여러줄인 것을 람다로 대체할 수 있다.
fun testFun3(a1:Int, a2:Int) : Int{
    val r1 = a1 + 100
    val r2 = a2 + 200
    val r3 = r1 + r2
    return r3
}

val lambda4 = {a1:Int, a2:Int ->
    val r1 = a1 + 100
    val r2 = a2 + 200
    // 제일 마지막에 작성한 값, 변수의 값, 수식의 결과를 최종 결과로 반환해준다.
    a1 + a2
}

fun main() {
    val r7 = lambda4(100,200)
    println("r7 : $r7")
}
r7 : 300

익명함수

  • 함수의 이름이 없는 함수
  • 단 한 번만 호출 가능
  • 함수를 변수에 담아 관리할 때 사용
  • 고차함수와 관련이 깊음
  • 코틀린은 이름을 가지고 있는 함수를 다른 변수에 담는 것이 불가능하다. (가능한 타 언어가 있다고 함)
val testFunction2 = testFunction1 // 에러
  • 이름이 없는 익명함수를 다른 변수에 담는 것은 가능하다.
fun main() {
    // 익명함수 호출
    // 익명함수를 가지고 있는 변수를 통해 호출
   	testFunction3()
}

// 익명함수
val testFunction3 = fun(){
    println("익명함수입니다")
}
익명함수입니다

인라인함수

  • Java 파일로 변경될 때 함수를 호출하는 코드가 함수 내부의 코드로 변경된다.
  • 자주 사용하는 함수를 inline 함수로 만들어주면 Java 코드로 변경될 때 코드의 양이 매우 많이 늘어나는 단점을 가지고 있지만 함수를 호출해 코드의 흐름이 이동하는 작업을 하지 않기 때문에 수행 속도가 좋아진다. (코드 동작이 달라짐)
  • 자주 사용하지 않는 함수를 inline 함수로 정의해 쓰면 된다.
fun main() {
    testFunction1()
    testFunction1()

    testFunction2()
    testFunction2()
}


fun testFunction1(){
    println("-----------------------")
    println("testFunction1")
    println("-----------------------")
}

inline fun testFunction2(){
    println("-----------------------")
    println("testFunction2")
    println("-----------------------")
}
-----------------------
testFunction1
-----------------------
-----------------------
testFunction1
-----------------------
-----------------------
testFunction2
-----------------------
-----------------------
testFunction2
-----------------------
  • testFunction1 은 자바 코드 변환시 함수 그대로 있지만, testFunction2는 자바 코드 변환 시 함수 안의 코드가 풀어져 나온다.

확장함수

  • 기존 클래스에 새로운 함수를 추가하는 개념
  • 코틀린 코드에서는 기존 클래스에 메서드를 추가해 사용하는 것처럼 보이지만 사실 객체의 주소값을 받은 함수가 만들어지고 그 함수를 호출하는 방식으로 동작한다.
// 확장 함수 : 클래스에 메서드를 추가하는 개념

fun main() {
    val str1 = "abcd"
    str1.printString()
}

// 확장 함수
// 클래스 이름.함수이름
fun String.printString(){
    // 확장함수 안에서 this를 통해 객체 자체에 접근할 수 있다.
    println("관리하는 문자열은 : ${this} 입니다")
}
관리하는 문자열은 : abcd 입니다

Infix 함수

  • 함수를 연산자처럼 사용할 수 있는 함수
  • 자료형.함수이름(매개변수)

기본 자료형에 대한 infix 함수

// infix 함수 : 함수의 호출을 연산자 사용하듯이 할 수 있는 함수

fun main() {
    // infix 함수 호출
    val r1 = 100 add2 200
    println("r1 : $r1")

    val r2 = 10 isMultiple 2
    println("r2 : $r2")
}

// 기본 자료형에 대한 infix 함수
// 자료형.함수이름(매개변수)
infix fun Int.add2(a1:Int) : Int {
    // 여기에서 this는 첫 번째 값을 의미한다.
    return this + a1
}

infix fun Int.isMultiple(a1:Int) : Boolean{
    val r1 = this % a1 == 0
    return r1
}
r1 : 300
r2 : true

특정 클래스에 대한 infix 함수

fun main() {
    val t1 = TestClass1(100, 200)
    val t2 = TestClass1(10,20)
    
    val t3 = t1 add2 t2
    
    println(t3.number1)
    println(t3.number2)

}

class TestClass1(var number1:Int, var number2:Int){

    infix fun add2(target:TestClass1) : TestClass1{
        val r1 = this.number1 + target.number1
        val r2 = this.number2 + target.number2
        val obj1 = TestClass1(r1, r2)
        return obj1
    }
}
110
220

고차함수

컴퓨터에서만 돌아가던 자바가 90년대 이후 Web의 등장으로 Web에 진출함

자바의 성공으로 JVM도 업데이트 됨

코틀린은 1990년대 만들어진 JVM을 기초로 함(안정화)

람다의 등장(람다 사용 시 람다를 관리하는 객체가 생성됨)

상속받은 클래스를 만들어 메서드를 오버라이딩하고 객체를 전달하는 과정이
람다식을 만들어 변수로 전달하면 과정이 간단하게 바뀜

람다식과 고차함수를 같이 사용하면 코드를 쉽게 짤 수 있다

자바 기반보다 코틀린 기반의 안드로이드 개발이 훨씬 빨라짐

매개변수로 함수를 받는 함수

// 고차 함수 : 매개 변수로 함수를 받거나 함수를 반환하는 함수

fun main(){
    // 고차함수에 전달하는 함수는 익명함수를 사용한다.
    val t1 = fun(x1:Int, x2:Int) : Int{
        return x1 + x2
    }
    testFunc1(100,200,t1)

    val t2 = fun(x1:Int, x2:Int) : Int{
        return x1 - x2
    }
    testFunc1(100,200,t2)
    
    testFunc1(100,200,fun(x1:Int, x2:Int):Int{
        return x1 * x2
    })
    
    // 람다식을 받는 것도 가능하다.
    val lambda1 = {x1:Int, x2:Int -> x1 / x2}
    testFunc1(200,100, lambda1)
    
    testFunc1(100,200,{x1:Int, x2:Int -> x1 % x2})
}

// 매개변수로 함수를 받는 함수
// 함수를 받는 매개변수의 타입은 (매개변수 타입) -> 반환값타입 형태로 작성한다.
fun testFunc1(a1:Int, a2:Int, m1:(Int, Int) -> Int){
    val r1 = m1(a1, a2)
    println("testFunc1 r1 : $r1")
}
testFunc1 r1 : 300
testFunc1 r1 : -100
testFunc1 r1 : 20000
testFunc1 r1 : 2
testFunc1 r1 : 100

함수를 반환하는 함수


fun main(){
    // 함수를 반환하는 함수
    val m2 = testFunc2(100)
    val result1 = m2(200,300)
    println(result1)
    
    val m3 = testFunc3(100)
    val result2 = m3(200,300)
    println(result2)
    
}

// 함수를 반환하는 함수
// 함수의 반환타입을 (반환할 함수의 매개변수들의 타입) -> 반환타입 형태로 작성해준다.
fun testFunc2(a1:Int) : (Int, Int) -> Int {

    // 함수 내부에 선언된 변수
    // val a1 = 100

    return fun(x1:Int, x2:Int):Int{
        // 반환되는 함수 안에서 함수를 반환하는 함수의 변수나 매개변수를 사용하는 것이 가능하다.
        return x1 + x2 + a1
    }
}

// 람다식 사용
fun testFunc3(a1:Int) : (Int, Int) -> Int{
    return {x1:Int, x2:Int -> x1 + x2 + a1}
}
600
600
  • 매개변수들 중에 제일 마지막에 있는 것이 함수나 람다를 받는 매개변수일 경우일때만 아래와 같이 변경하여 작성할 수 있다.
fun main(){
    // testFunc1처럼 매개변수들 중에 제일 마지막에 있는 것이 함수나 람다를 받는 매개변수일 경우
    testFunc1(100,200,{x1:Int, x2:Int -> x1 + x2})

    // 제일 마지막 매개변수가 람다나 함수를 받는 매개변수이고 람다를 전달할 경우 다음과 같이 작성할 수 있다.
    testFunc1(100, 200){x1: Int, x2: Int ->
        x1 + x2
    }
}
testFunc1 r1 : 300
testFunc1 r1 : 300

함수나 람다식을 받는 매개변수만 있는 함수

fun main(){
    testFunc4({x1, x2 -> x1 + x2})
    
    // 클래스의 init함수도 동일한 모양으로 고차함수이다.
    testFunc4 {x1, x2 ->
        x1 + x2
    }
}

// 함수나 람다식을 받는 매개변수만 있는 함수
fun testFunc4(m1:(Int, Int) -> Int){
    val r1 = m1(100,200)
    println(r1)
}
300
300

매개변수를 하나만 가지고 있는 함수나 람다를 받는 함수

  • 고차함수로 전달하는 람다식의 매개변수가 1개일 경우 it을 사용하면 된다.
fun main(){
    // testFunc5{x1 ->
    //    println(x1)
    //    100
    // }
    testFunc5{
        println(it)
        100
    }
}

// 매개변수를 하나만 가지고 있는 함수나 람다를 받는 함수
fun testFunc5(m1:(Int)->Int){
    m1(100)
}
100

코틀린 범위 지정 함수

  • let, apply, run, with, also
  • 생성되어 있는 객체의 프로퍼티나 메서드를 사용할 경우
  • 혹은 클래스로부터 객체를 생성할 때 사용한다.
  • 변수의 이름이 길 때 유용하게 사용할 수 있음
  • ide(IntelliJ) 안내문구에 it이 나와있으면 it을 통해 프로퍼티나 메서드에 접근하고, this가 나와있으면 객체에 포함되기 때문에 프로퍼티나 메서드를 그냥 사용할 수 있다.

프로퍼티나 메서드 사용 시

fun main() {

    val t1 = TestClass1(100,200)
    t1.a3 = 300
    t1.a4 = 400
    t1.testMethod1()
}

class TestClass1(var a1:Int, var a2:Int){
    var a3:Int = 0
    var a4:Int = 0

    fun testMethod1(){
        println("a1 : $a1")
        println("a2 : $a2")
        println("a3 : $a3")
        println("a4 : $a4")
    }
}
a1 : 100
a2 : 200
a3 : 300
a4 : 400

let

fun main() {
    // 이미 생성되어 있는 객체에 코틀린 범위 지정함수를 사용한다.

    // let
    val t2 = TestClass1(100, 200)
    // 첫 번째 매개변수로 객체의 주소값이 들어온다.
    // it을 통해 객체의 프로퍼티나 메서드를 사용하면 된다.
    t2.let{
        it.a3 = 300
        it.a4 = 400
        it.testMethod1()
    }
}
a1 : 100
a2 : 200
a3 : 300
a4 : 400

apply

fun main() {
    // 이미 생성되어 있는 객체에 코틀린 범위 지정함수를 사용한다.
    
    // apply
    val t3 = TestClass1(100,200)
    // apply에 작성한 코드는 apply를 호출한 객체의 메서드로 포함된다.
    // this나 아무것도 통하지 않고 프로퍼티나 메서드를 사용하는 것이 가능하다.
    t3.apply{
        a3 = 300
        a4 = 400
        testMethod1()
    }
}
a1 : 100
a2 : 200
a3 : 300
a4 : 400

run

fun main() {
    // 이미 생성되어 있는 객체에 코틀린 범위 지정함수를 사용한다.
    
    // run
    val t4 = TestClass1(100, 200)
    t4.run{
        a3 = 300
        a4 = 400
        testMethod1()
    }
}
a1 : 100
a2 : 200
a3 : 300
a4 : 400

also

fun main() {
    // 이미 생성되어 있는 객체에 코틀린 범위 지정함수를 사용한다.
    
    // also
    val t5 = TestClass1(100, 200)
    t5.also{
        it.a3 = 300
        it.a4 = 400
        it.testMethod1()
    }
}
a1 : 100
a2 : 200
a3 : 300
a4 : 400

with

  • with만 유일하게 매개변수가 2개인 고차함수임
  • 객체 주소를 받고 , 동작 함수를 람다로 받음
fun main() {
    // 이미 생성되어 있는 객체에 코틀린 범위 지정함수를 사용한다.
    
    // with
    val t6 = TestClass1(100, 200)
    with(t6){
        a3 = 300
        a4 = 400
        testMethod1()
    }
}
a1 : 100
a2 : 200
a3 : 300
a4 : 400

객체 생성 시

let

  • 객체에 접근할 때 it 변수를 사용한다.
  • 코드 블럭 마지막에 작성된 값, 객체의 주소값, 수식의 값을 반환해준다.
  • t7 변수에는 TestClass1(100, 200) 를 통해 생성된 객체의 주소값이 아니라 let 이 반환하는 것이 담긴다.
fun main() {
    val t7 = TestClass1(100,100).let{
        it.a3 = 300
        it.a4 = 400
        it // let 마지막에 it을 작성하여 객체의 주소값이 반환될 수 있도록 해줘야 한다.
    }
}
  • 마지막에 it이 아닌 100을 넣었다면 변수 t7에는 객체의 주소값이 아닌 정수 100이 담긴다.

apply

  • 생성된 객체의 주소값이 apply 쪽으로 전달되어 this(혹은 생략)을 통해 객체의 프로퍼티나 메서드에 접근할 수 있다.
  • 람다식 내부의 코드가 수행이 끝나면 생성된 객체의 주소값이 변수에 담긴다.
  • 이에 람다식 내부에 객체의 주소값을 반환하기 위한 코드를 작성하지 않는다.
fun main() {
    val t8 = TestClass1(100, 200).apply{
        a3 = 300
        a4 = 400
    }
    println(t8)
    t8.testMethod1()
}

run

  • 객체를 생성한 후에 run 람다식 내부로 객체의 주소값이 전달되기 때문에 this(혹은 생략)을 통해 프로퍼티나 메서드에 접근할 수 있다.
  • 람다식의 수행이 끝나면 제일 마지막에 작성한 값이 반환되어 변수에 담긴다.
  • 이에 생성된 객체의 주소값이 변수(t9)에 담기게 하기 위해서는 람다식 마지막에 this를 명시하여 객체의 주소값을 반환하게 해야 한다.
fun main() {
    val t9 = TestClass1(100, 200).run{
        a3 = 300
        a4 = 400
        this
    }
    println(t9)
    t9.testMethod1()
}

also

  • 생성된 객체의 주소값이 람다식의 it 변수에 담기게 된다.
  • 따라서 it을 통해 프로퍼티나 메서드에 접근할 수 있다.
  • also 람다식이 끝나면 변수(t10)에 객체의 주소값이 담기게 된다.
  • 마지막에 객체의 주소값을 반환하는 코드는 작성하지 않는다.
fun main() {
    val t10 = TestClass1(100, 200).also{
        it.a3 = 300
        it.a4 = 400
    }
    println(t10)
    t10.testMethod1()
}

with

  • 람다식 코드가 생성된 객체의 일부분 처럼 동작하기 때문에 this(생략가능)을 통해 프로퍼티나 메서드에 접근한다.
  • 람다식 마지막에 객체의 주소값을 반환하는 코드를 작성해야지만 변수(t11)에 객체의 주소값이 담긴다.
fun main() {
    val t11 = with(TestClass1(100,200)){
        a3 = 300
        a4 = 400
        this
    }
    println(t11)
    t11.testMethod1()
}

배열

  • 연속된 기억공간을 확보하여 값들을 저장하고 관리하는 자료 구조
  • 배열은 다수의 기억장소를 하나로 묶어서 관리하고 배열의 주소값을 가지고 있는 변수를 통해 배열에 접근하여 배열이 관리하는 기억장소를 사용할 수 있다.
  • 배열의 기억장소 개수는 변경이 불가능하다.

배열 생성

fun main() {
    // 배열 생성
    // 배열이 관리하는 값들을 지정해준다.
    // 지정한 값 만큼의 기억장소가 생성되고 그 기억장소들을 관리하는 배열이 생성된다.
    // 배열이 관리하는 기억장소의 개수는 변경될 수 없다.
    val array1 = arrayOf(10,20,30,40,50)
    println("array1: $array1")
}
array1: [Ljava.lang.Integer;@14d9a52c
  • 배열이 관리하는 값을 모아 문자열로 만들어 반환 받을 수 있다.
    이를 통해 객체가 관리하는 값들을 출력해 볼 수 있다.
fun main() {
    val array1 = arrayOf(10,20,30,40,50)
    println("array1: ${array1.contentToString()}")
}
array1: [10, 20, 30, 40, 50]
  • 배열은 다양한 타입의 값을 담을 수 있다.
fun main() {
    val array2 = arrayOf(100, 11.11, "문자열", true)
    println("array2 : $array2")
    println("array2 : ${array2.contentToString()}")
}
array2 : [Ljava.lang.Object;@21dfaea8
array2 : [100, 11.11, 문자열, true]
  • 특정 타입의 값만 담는 배열을 생성한다.
  • ~~ArrayOf로 생성 못하는 타입의 경우에는 제네릭을 사용
fun main() {
    val array3 = intArrayOf(10, 20, 30, 40)
    // val array3 = intArrayOf(10,20,30,"40") // 에러
    val array4 = doubleArrayOf(11.11, 22.22, 33.33, 44.44, 55.55)
    // ~~ArrayOf로 생성 못하는 타입의 경우에는 제네릭을 사용
    val array5 = arrayOf<String>("문자열1", "문자열2", "문자열3")

    println("array3 : ${array3.contentToString()}")
    println("array4 : ${array4.contentToString()}")
    println("array5 : ${array5.contentToString()}")
}
array3 : [10, 20, 30, 40]
array4 : [11.11, 22.22, 33.33, 44.44, 55.55]
array5 : [문자열1, 문자열2, 문자열3]
  • 람다식을 통한 배열 생성
fun main() {
    // 람다식을 통한 배열 생성
    // 람다식 내부의 코드를 수행하여 얻어진 결과를 배열에 담아준다.
    // Array(기억장소의 개수)
    // 지정된 숫자 만큼 기억장소가 만들어지고 람다식의 제일 마지막에 작성한 값을 담아준다.

    // 주어진 람다식을 지정한 개수만큼 반복한다. 첫 번째 기억장소에 담을 값을 결정하기 위해
    // 람다식을 수행하고 람다식의 제일 마지막에 작성한 값이 첫 번째 기억 장소에 담기게 된다.
    // 이런식으로 모든 기억장소의 수 만큼 람다식이 수행되고 람다식이 전달하는 값은 각각의 기억장소에 담기게 된다.
    val array6 = Array(5){
        0 // 0이 5개 담긴 배열 생성
    }
    println("array6 : ${array6.contentToString()}")
}
array6 : [0, 0, 0, 0, 0]
  • 람다식에 있는 it에는 0부터 1씩 증가되는 값이 들어온다.
fun main() {
    // 람다식에 있는 it에는 0부터 1씩 증가되는 값이 들어온다.
    val array7 = Array(5){
        it
    }
    println("array7 : ${array7.contentToString()}")
}
array7 : [0, 1, 2, 3, 4]
  • 3부터 3의 배수 10개를 가지고 있는 배열을 생성할 경우
fun main() {
    // 3부터 3의 배수 10개를 가지고 있는 배열을 생성한다.
    val array8 = Array(10){
        (it + 1) * 3
    }
    println("array8 : ${array8.contentToString()}")
}
array8 : [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
  • 다차원 배열 생성
  • 배열 안에 배열들이 들어가 있는 것
fun main() {
    // 다차원 배열 생성
    // 배열 안에 배열들이 들어가 있는 것
    val array9 = arrayOf(
        arrayOf(10, 20, 30),
        arrayOf(40, 50, 60),
        arrayOf(70, 80, 90)
    )
    println("array9 : ${array9.contentToString()}")
    // 다차원 배열일 때는 contentDeepToString메서드를 사용하면 됨
    // 배열 안에 있는 배열까지 모두 문자열로 변환해줌
    println("array9 : ${array9.contentDeepToString()}")
}
array9 : [[Ljava.lang.Integer;@89a5d042, [Ljava.lang.Integer;@5385da85, [Ljava.lang.Integer;@57b91da8]
array9 : [[10, 20, 30], [40, 50, 60], [70, 80, 90]]

배열 접근

  • 배열이 관리하는 기억 장소는 0부터 1씩 증가되는 순서값을 사용한다.
  • 배열 접근
fun main() {
	val array1 = arrayOf(10,20,30,40,50)

    println("array1[0] : ${array1[0]}")
    println("array1[1] : ${array1[1]}")
    
    // [ ] 연산자는 get 메서드를 호출하기 때문에 [ ]를 사용한 것과 동일하다.
    println("array1.get(0) : ${array1.get(0)}")
    println("array1.get(1) : ${array1.get(1)}")
}
array1[0] : 10
array1[1] : 20
array1.get(0) : 10
array1.get(1) : 20
  • 다차원 배열 접근
fun main() {
    val array9 = arrayOf(
        arrayOf(10, 20, 30),
        arrayOf(40, 50, 60),
        arrayOf(70, 80, 90)
    )

    // 다차원 배열 접근
    println("array9[0][0] : ${array9[0][0]}")
    println("array9[1][1] : ${array9[1][1]}")
}
array9[0][0] : 10
array9[1][1] : 50

배열 수정

  • 배열의 n번째 값 수정
fun main() {
	val array1 = arrayOf(10,20,30,40,50)
    
    println("array1 : ${array1.contentToString()}")

    // 0번째 기억 장소 새로운 값을 저장한다.
    array1[0] = 100
    println("0번째에 100 설정 : ${array1.contentToString()}")
    
    // [ ] = 값의 문법을 사용하면 set을 호출한다.
    array1.set(1, 200)
    println("1번째에 200 설정 : ${array1.contentToString()}")
    
    // 배열에 추가한다.
    // 원본 배열에 1000이 추가된 새로운 배열을 생성하여 반환한다.
    // 배열은 절대 관리하는 기억장소의 개수를 변경시킬 수 없다.
    val array10 = array1.plus(1000)
    println("1000 추가(원본 유지) : ${array1.contentToString()}")
    println("1000 추가(새롭게 생성된 배열) : ${array10.contentToString()}")
}
array1 : [10, 20, 30, 40, 50]
0번째에 100 설정 : [100, 20, 30, 40, 50]
1번째에 200 설정 : [100, 200, 30, 40, 50]
  • 배열 추가
  • 배열은 절대 관리하는 기억장소의 개수를 변경시킬 수 없다.
  • 원본 배열에 1000이 추가된 새로운 배열을 생성하여 반환해야 한다.
fun main() {
	val array1 = arrayOf(10,20,30,40,50)
    
    // 배열에 추가한다.
    // 원본 배열에 1000이 추가된 새로운 배열을 생성하여 반환한다.
    // 배열은 절대 관리하는 기억장소의 개수를 변경시킬 수 없다.
    val array10 = array1.plus(1000)
    println("1000 추가(원본 유지) : ${array1.contentToString()}")
    println("1000 추가(새롭게 생성된 배열) : ${array10.contentToString()}")
}
1000 추가(원본 유지) : [100, 200, 30, 40, 50]
1000 추가(새롭게 생성된 배열) : [100, 200, 30, 40, 50, 1000]
  • 배열의 일부분을 가져온다.
  • 지정된 순서값 범위에 해당하는 값들을 가지고 있는 새로운 배열을 생성한다.
  • 순서값 1 ~ 3 까지
fun main() {
	val array1 = arrayOf(10,20,30,40,50)
    
    // 일부분을 가져온다.
    // 지정된 순서값 범위에 해당하는 값들을 가지고 있는 새로운 배열을 생성한다.
    // 순서값 1 ~ 3 까지
    val array11 = array1.sliceArray(1..3)
    println("array11 : ${array11.contentToString()}")
}
array11 : [20, 30, 40]
  • for문과 같이 사용
fun main() {
	val array1 = arrayOf(10,20,30,40,50)
    
    // for문과 같이 사용
     for(a1 in array1){
         println(a1)
     }
}
10
20
30
40
50

배열이 제공하는 메서드

fun main() {
	val array1 = arrayOf(10,20,30,40,50)
    
    // 배열이 제공하는 메서드들
    println("첫 번째 값 : ${array1.first()}")
    println("마지막 값 : ${array1.last()}")
    println("30의 위치(존재하는 값) : ${array1.indexOf(30)}")
    println("10000의 위치(없는 값) : ${array1.indexOf(10000)}")
    println("평균 : ${array1.average()}")
    println("총합 : ${array1.sum()}")
    println("관리하는 기억장소의 개수 : ${array1.count()}")
    println("관리하는 기억장소의 개수 : ${array1.size}") // 메서드가아니라 프로퍼티임
    println("30을 포함하는가 : ${array1.contains(30)}")
    println("10000을 포함하는가 : ${array1.contains(10000)}")
}
첫 번째 값 : 100
마지막 값 : 50
30의 위치(존재하는 값) : 2
10000의 위치(없는 값) : -1
평균 : 84.0
총합 : 420
관리하는 기억장소의 개수 : 5
관리하는 기억장소의 개수 : 5
30을 포함하는가 : true
10000을 포함하는가 : false

배열 정렬

fun main() {

    val array12 = arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    // 배열이 관리하는 값의 순서를 랜덤하게 섞어 준다.
    array12.shuffle()
    println(array12.contentToString())

    // 정렬(오름 차순)
    // 정렬된 배열을 새롭게 생성해서 반환한다.
    val array13 = array12.sortedArray()
    println(array13.contentToString())

    // 정렬(내림 차순)
    val array14 = array12.sortedArrayDescending()
    println(array14.contentToString())
    
}
[2, 1, 7, 5, 4, 3, 8, 10, 6, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]



※ 출처 : 멋쟁이사자 앱스쿨 2기, 소프트캠퍼스 
profile
안드로이드공부

0개의 댓글