공상하는 개발자

[코틀린] 코틀린 익숙해지기 3탄(데이터클래스,중첩클래스,람다 등) 본문

개발/코틀린

[코틀린] 코틀린 익숙해지기 3탄(데이터클래스,중첩클래스,람다 등)

공상과학소설 2020. 3. 8. 18:29
반응형

데이터 클래스 (data class)

-> 내부에 특별한 로직이 있는 함수가 없이 데이터만을 포함할 수 있는 클래스

 

코틀린에서는 이런 클래스들을 조금 더 편리하게 사용할 수 있도록 데이터 클래스를 제공한다.

데이터 클래스는 data 예약어(키워드)를 이용해서 선언한 클래스이다.

 

※ 데이터 클래스 선언 형식 

data class User(val name : String, val age: Int)

 

★ 일반 클래스와 비교

  • data 키워드 사용
  • 주 생성자를 선언
  • 주 생성자의 매개 변수는 최소 하나 이상이어야 한다.
  • 모든 주 생성자의 매개변수는 var 또는 val로 선언해야 한다.
  • abstract, open, sealed, inner 등의 예약어를 사용할 수 없다.

에러의 경우

//data class user()   //주 생성자의 매개 변수가 없다.

//data class User2(name: String)  //매개변수에 val/var 선언이 안되어 있음.

//data abstract class User3(val name: String, val age: Int)   //abstract 키워드 사용 

//data class User4(val name: String, id: Int)   //두 번째 매개변수에 val/var 선언이 안 되어있음.

 

♣ 데이터 클래스의 함수

  • 데이터 클래스는 클래스 내에 데이터와 관련된 다양한 함수를 제공한다.
  • 따라서, 개발자가 클래스 내에 선언하지 않아도 자동으로 다음과 같은 함수를 제공받는다.
    • equals() / hashCode / toString()
    • copy()

equals()를 이용한 데이터 비교(같은 data class 끼리 비교)

class Product(val name:String,val price:Int)

data class User1(val name: String,val age:Int)

fun main() {
    // 일반 클래스의 equals()함수는 객체를 비교를 한다.
    var prod1 = Product("상품1", 10000)
    var prod2 = Product("상품1", 10000)

    println(prod1.equals(prod2))    //false 출력

    // 데이터 클래스의 equals()함수는 객체를 비교하지 않고 객체의 데이터를 비교한다.
    val user1 = User1("홍길동", 50)
    val user2 = User1("홍길동", 50)

    println(user1.equals(user2))    //true 출력

    val user3 = User1("홍길동",40)
    println(user1.equals(user3))    //false 출력

}

 

equals() 를 이용한 데이터 비교(다른 data class 끼리 비교)

data class Student(val name:String,val age:Int)

data class Employee2(val name: String,val age: Int)

fun main() {
    
    //equals()함수는 서로 다른 데이터 클래스 객체는 데이터를 비교하지 않는다.
    //같은 데이터 클래스 객체일 때만 데이터를 비교한다.
    val student = Student("홍길동",22)
    val employee = Employee2("홍길동",22)
    println(student.equals(employee))   //false 출력
}

 

equals() 를 이용한 데이터 비교(다른 프로퍼티 값을 가진 data class끼리 비교)

data class Professor(val name: String, val subject: String) {
    var email: String = "aaa@aaa.net"
}

fun main() {
    //데이터 클래스의 equals()함수는 주 생성자의 매개변수 값만 비교한다.
    //클래스 내에 있는 프로퍼티는 비교하지 않는다.
    val professor1 = Professor("홍길동", "컴퓨터공학")

    val professor2 = Professor("홍길동", "컴퓨터공학")
    professor2.email = "bbb@bbb.net"

    println(professor1.equals(professor2))  //true 출력!
}

toString() 함수 소개

class Teacher(val name: String, val age: Int)

data class Teacher2(val name: String, val age: Int) {
    val email: String = "test@test.com"
}

fun main() {
    var teacher = Teacher("홍길동", 55)
    println(teacher.toString())

    // 데이터 클래스의 toString()함수는 객체가 가지고 있는 데이터 값을 반환한다.
    // 이 때의 값은 주 생성자의 프로퍼티 값만을 반환한다.
    // 클래스 내부에 있는 프로퍼티 값은 출력하지 않는다.

    // toString() 함수는 일반적으로 디버깅시에 로그 출력용으로 많이 사용한다.
    //객체의 데이터가 정상적으로 대입 됐는지 확인 하는 용도로 사용한다.
    var teacher2 = Teacher2("홍길북", 33)
    println(teacher2.toString())
}

componentN() 함수

-> 데이터 클래스에서 제공하는 componentN() 함수
     데이터 클래스의 프로퍼티에 값을 가져올 때 프로퍼티의 이름을 사용하는 경우가 있고, componentN() 함수를 이용할 수도 있다.

※ componentN() 함수는 component1(), component2().... 식으로 사용하는데,

     주 생성자의 프로퍼티가 2개인 경우에는 component1(), component2() 함수가 자동으로 만들어진다.
     주 생성자의 프로퍼티가 2개인 경우에는 component1(), component2(), component3() 함수가 자동으로 만들어진다.

 

data class Car(val name: String, val speed: Int)

fun main() {
    var car = Car("카1", 200)

    println(car.name)   //카1 출력
    println(car.speed)  //200 출력

    println(car.component1())   //카1 출력
    println(car.component2())   //200 출력
}

근데 이렇게 사용하면 그냥 사용하는 것보다 직관적이지 못하고 비효율적이다.

그렇다면 언제 쓰는 것이 효율적일까??

 

데이터 분해 선언

// 데이터 분해선언(Destructuring declarations)

data class Flower(val name: String, val price: Int)

fun main() {
    var flower = Flower(price = 2000,name = "장미")

    val (name,price) = flower   //데이터 분해 선언
    
    println("name: $name, price: $price")   //name: 장미, price: 2000 출력....
}

copy() 함수 소개

데이터 클래스에서 제공하는 copy() 함수 : 이름 그대로 객체를 복사해서 다른 개체를 만들어주는 함수

객체의 일부분의 데이터만 변경해서 다른 객체를 만들 때 유용하게 사용할 수 있다.

data class Product2(val name: String, val price: Int)

fun main() {
    var prod = Product2(price = 10000, name = "상품1")
    println(prod.toString())    //Product2(name=상품1, price=10000) 출력

    var prod2 = prod.copy(name = "상품2")
    println(prod2.toString())   //Product2(name=상품2, price=10000) 출력

}

중첩 클래스

-> 클래스 안에 클래스

 

쓰는 이유 : class 안의 private로 선언된 프로퍼티나 함수에 접근해야 할 때 사용한다.

 

중첩 클래스

class Outer {

    var num: Int = 100

    fun outerfun() {
        println("outerfun.....")
    }

    class Nested {
        fun greeting() = println("안녕하세요!")
        // Nested 클래스에서 Outer 클래스에 있는 멤버에 기본적으로 접근이 불가능하다.(private 이 아닌데도 불가)
//      fun nestedfun() = println(num) // 에러 발생
    }
}

 

inner class 구현

class Outer2 {

    private var num: Int = 100
    fun outerfun() = println("Outerfun의 outerfun() 이다... ...")

    inner class Nested {
        val name: String = "홍길동"
        fun myFunc() {
            println("Nested의 myFunc()... ...")
            num = 200
            outerfun()
        }
    }

    fun createNested():Nested{
        return Nested()
    }
}

fun main() {
    val obj:Outer2.Nested = Outer2().Nested()
    val obj2:Outer2.Nested = Outer2().createNested()
    println(obj.name)   //홍길동 출력...
    println(obj2.name)  //홍길동 출력...
    obj.myFunc()    //Nested의 myFunc()... ...
                    //Outerfun의 outerfun() 이다... ...
}

 

내부 클래스에서 바깥 클래스 변수, 함수 사용하기

class Outer3(private var aa: Int) {

    fun getInfo() {
        println(this.aa)
    }

    inner class Inner(private val bb: Int) {
        fun getInfo() {
            // 내부 클래스는 기본적으로 바깥 클래스를 가리키는 참조변수를 가지고 있다.
            // this@외부클래스명
            this@Outer3.getInfo()
            println(this.bb + this@Outer3.aa)
        }
    }
}

fun main() {
    val outer3: Outer3 = Outer3(100)
    val inner: Outer3.Inner = outer3.Inner(50)
    inner.getInfo()
}

람다 표현식

-> 함수  프로그래밍 언어에서 코드의 간결함을 주목적으로 한다. ( 함수 축약형)

 

※ 일반 함수 형식

   fun 함수 이름(매개변수) { 실행 코드들 }

   일반 함수의 축약형 : 람다식

   { 매개변수 -> 실행코드들 }

 

★ 람다 함수의 규칙

- 람다 함수는 항상 {} 감싸서 표현해야 한다.

- {} 안에 -> 연산자가 있으며, -> 왼쪽에는 매개변수, 오른쪽에는 실행코드들이 위치한다. 

- 매개변수 타입을 선언해야 한다. 추론할 수 있을 때는 생략할 수도 있다. 

- 함수의 반환 값은 실행코드들에서 마지막에 표현한다.

 

→ 람다식은 익명 함수여서 호출하여 이용할 수가 없다.

따라서, 주로 변수에 대입을 해서 사용을 하는데, 변수를 사용하지 않고 람다 함수를 호출하려면 다음과 같이 

{ println("Hello")}() // 람다함수를 만들자마자 호출하는 예
run { println("Hello") } // 람다함수를 만들자마자 호출하는 예

// 매개변수가 없는 람다 함수
val sum = { 100 + 100 } // -> 생략할 수 있다.

// 함수의 실행코드가 여러 줄일 때
val sum = { x: Int, y: Int ->
    println("sum() 호출 ...")
    x + y	// 리턴 표현식
}

 

● 함수 타입 : 매개변수의 형식과, 리턴 타입의 형식, 몇 개의 매개변수를 갖는지.... 명시하는 것

fun myFunc(x: Int, y: Int):Boolean{
	return x > y
}

val lamdaFunc: (Int) -> Int = { : Int -> x * 2 }

 

☞ it은 매개변수가 하나일 때 별도의 매개변수를 선언하지 않고 사용할 수 있는 키워드

val lamdaFunc: (Int) -> Int = { x -> x + 10 } 

//식을 it을 이용해서 표현하면 다음과 같다.

val lamdaFunc: (Int) -> Int = { it + 10 }

 

❗️it은 타입이 정의되어 있는 곳에서만 사용할 수 있다.

val lamdaFunc = { it * 10 }	// 에러 발생

 

반응형
Comments