출처: https://www.boostcourse.org/mo234/lecture/154292?isDesc=false
데이터 전달을 위한 객체를 DTO (Data Transfer Object)라고 부른다. 자바에서는 POJO (Plain Old Java Object)라고 부르기도 한다.
자바로 DTO를 표현하면, 데이터 필드와 게터/세터, 데이터 표현 및 비교 메서드를 모두 작성해줘야 한다. 하지만, 코틀린으로 DTO를 표현하면 프로퍼티만 신경써서 작성하면 되고, 나머지는 내부적으로 자동 생성해주기 때문에 매우 편리하다!
cf) 데이터 클래스에도 간단한 로직을 포함하고 싶으면, 부 생성자나 init 블록을 사용하면 된다.
data class Customer(var name: String, var email: String) {
var job: String = "Unknown"
constructor(name: String, email: String, _job: String): this(name, email) {
job = _job
}
init {
// 간단한 로직은 여기에
}
}
package chap03.section3
data class Customer(var name: String, var email: String)
fun main() {
val cus1 = Customer("Sean", "sean@mail.com")
val cus2 = Customer("Sean", "sean@mail.com")
println(cus1 == cus2) // true (값을 비교)
println(cus1 === cus2) // false (참조 주소를 비교)
println(cus1.equals(cus2)) // true (값을 비교)
println("${cus1.hashCode()}, ${cus2.hashCode()}") // equal
val cus3 = cus1.copy(name = "Alice")
println(cus1.toString())
println(cus3.toString())
}
true
false
true
-1521025108, -1521025108
Customer(name=Sean, email=sean@mail.com)
Customer(name=Alice, email=sean@mail.com)
데이터 클래스의 객체가 갖고 있는 프로퍼티를 개별 변수들로 분해하는 것
val (name, email) = cus1
println("name = $name, email = $email")
// 특정 프로퍼티를 가져올 필요가 없을 때
//val (_, email) = cus1
val name2 = cus1.component1()
val email2 = cus1.component2()
println("name = $name2, email = $email2")
val cus1 = Customer("Sean", "sean@mail.com")
val cus2 = Customer("Sean", "sean@mail.com")
val bob = Customer("Bob", "bob@mail.com")
val erica = Customer("Erica", "erica@mail.com")
val customers = listOf(cus1, cus2, bob, erica)
for((name, email) in customers){
println("name = $name, email = $email")
}
data class Customer(var name: String, var email: String)
fun myFunc(): Customer {
return Customer("Mickey", "mic@mail.com")
}
fun main() {
// 함수로부터 객체가 반환되는 경우
val (myName, myEmail) = myFunc()
println("$myName $myEmail")
// 람다식에서 사용하는 경우
val myLambda = {
// destructuring
(nameLa, emailLa): Customer ->
println(nameLa)
println(emailLa)
}
val cus1 = Customer("Sean", "sean@mail.com")
myLambda(cus1) // 인자로 객체를 전달
}
종류 | 역할 |
---|---|
정적 클래스 (static class) | static 키워드를 가지며, 외부 클래스를 인스턴스화 하지 않고 바로 사용 가능한 클래스 (주로 빌더 클래스에 이용) |
멤버 클래스 (member class) | 인스턴스 클래스로도 불리며 외부 클래스의 필드나 메서드와 연동하는 내부 클래스 |
지역 클래스 (local class) | 초기화 블록이나 메서드 내의 블록에서만 유효한 클래스 |
익명 클래스 (anonymous class) | 이름이 없고, 주로 일회용 객체를 인스턴스화 하면서 오버라이드 메서드를 구현하는 내부 클래스. 가독성이 떨어지는 단점이 있다. |
자바 | 코틀린 |
---|---|
정적 클래스 | 중첩 클래스: 객체 생성 없이 사용 가능 |
멤버 클래스 | 이너 클래스: 필드나 메서드와 연동하는 내부 클래스로 inner 키워드가 필요하다. |
지역 클래스 | 지역 클래스: 클래스의 선언이 블록 내에 있다면 지역 클래스이다. |
익명 클래스 | 익명 객체: 이름이 없고, 주로 일회용 객체를 사용하기 위해 object 키워드로 선언된다. |
코틀린에서 중첩 클래스는 기본적으로 static 클래스처럼 다뤄진다.
package chap03.section3
class Outer {
val ov = 5
// Nested -> Outer (X)
class Nested {
val nv = 10
fun greeting() = "[Nested] Hello! $nv" // 외부의 ov는 접근 불가
}
// Outer -> Nested (O)
fun outside(){
// 중첩 클래스의 메서드 및 프로퍼티 접근
val msg = Nested().greeting()
println("[Outer] $msg, ${Nested().nv}")
}
}
fun main() {
// static처럼 Outer 객체 생성 없이, Nested 객체 생성하여 사용 가능함.
val output = Outer.Nested().greeting()
println(output)
val outer = Outer()
outer.outside()
//Outer.outside() // error
}
class Outer {
val ov = 5
// Nested -> Outer (X)
class Nested {
val nv = 10
fun greeting() = "[Nested] Hello! $nv" // 외부의 ov는 접근 불가
fun accessOuter(){ // 컴페니언 객체에 접근 가능
println(country)
getSomething()
}
}
// Outer -> Nested (O)
fun outside(){
// 중첩 클래스의 메서드 및 프로퍼티 접근
val msg = Nested().greeting()
println("[Outer] $msg, ${Nested().nv}")
}
companion object { // 컴페니언 객체는 static처럼 접근 가능
const val country = "Korea"
fun getSomething() = println("Get something...")
}
}
inner 키워드로 선언된 이너 클래스는 바깥 클래스의 멤버에 접근 가능하다. (private 멤버 포함)
package chap03.section3
class Smartphone(val model: String){
private val cpu = "Exynos"
inner class ExternalStorage(val size: Int){
// 외부 클래스의 프로퍼티에 접근
fun getInfo() = "${model}: Installed on $cpu with ${size}GB"
}
}
fun main() {
// 이너 클래스의 객체 생성
val mySdcard = Smartphone("S7").ExternalStorage(32)
println(mySdcard.getInfo())
}
package chap03.section3
class Smartphone(val model: String){
private val cpu = "Exynos"
// 이너 클래스
inner class ExternalStorage(val size: Int){
// 외부 클래스의 프로퍼티 접근 가능
fun getInfo() = "${model}: Installed on $cpu with ${size}GB"
}
fun powerOn(): String {
// 지역 클래스
class LED(val color: String){
// 외부 클래스의 프로퍼티 접근 가능
fun blink(): String = "Blinking $color on $model"
}
val powerStatus = LED("Red")
return powerStatus.blink()
}
}
fun main() {
// 이너 클래스의 객체 생성
val mySdcard = Smartphone("S7").ExternalStorage(32)
println(mySdcard.getInfo())
val myPhone = Smartphone("Note9")
println(myPhone.powerOn()) // 지역 클래스의 사용
}
package chap03.section3
interface Switcher {
fun on(): String
}
class Smartphone(val model: String){
private val cpu = "Exynos"
// 이너 클래스
inner class ExternalStorage(val size: Int){
// 외부 클래스의 프로퍼티 접근 가능
fun getInfo() = "${model}: Installed on $cpu with ${size}GB"
}
fun powerOn(): String {
// 지역 클래스
class LED(val color: String){
// 외부 클래스의 프로퍼티 접근 가능
fun blink(): String = "Blinking $color on $model"
}
val powerStatus = LED("Red")
// 익명 객체 생성하여 인터페이스의 추상 메서드 구현
// 하위 클래스를 따로 만들 필요 없음.
val powerSwitch = object: Switcher {
override fun on(): String {
return powerStatus.blink()
}
}
return powerSwitch.on()
}
}
fun main() {
// 이너 클래스의 객체 생성
val mySdcard = Smartphone("S7").ExternalStorage(32)
println(mySdcard.getInfo())
// 지역 클래스와 익명 객체의 사용
val myPhone = Smartphone("Note9")
println(myPhone.powerOn())
}
package chap03.section3
// 상태를 정의하고 관리하는 데 사용되는 실드 클래스
sealed class Result{
open class Success(val message: String): Result()
class Error(val code: Int, val message: String): Result()
}
class Status: Result() // 실드 클래스의 상속은 같은 파일 내에서만 가능
class Inside: Result.Success("status") // open 되어있는 내부 클래스 상속
// 상태를 검사하기 위한 함수
fun eval(result: Result): String = when(result){
is Status -> "in progress"
is Result.Success -> result.message
is Result.Error -> result.message
// Result 클래스의 모든 상태를 처리하므로 else 필요 없음. (실드 클래스의 장점)
}
fun main() {
val result = Result.Success("good")
println(eval(result))
val result2 = Result.Error(10, "No disk")
println(eval(result2))
}
열거형 클래스는
// 각 상수는 Direction 클래스의 객체로 취급됨.
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
enum class DayOfWeek(val num: Int) {
MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4),
FRIDAY(5), SATURDAY(6), SUNDAY(7)
}
val day = DayOfWeek.SATURDAY
when(day.num) {
1, 2, 3, 4, 5 -> println("Weekday")
6, 7 -> println("Weekend")
}
package chap03.section3
enum class Color(val r: Int, val g: Int, val b: Int){
RED(255, 0, 0), ORANGE(255, 165, 0),
YELLOW(255, 255, 0), GREEN(0, 255, 0),
BLUE(0, 0, 255), INDIGO(75, 0, 130),
VIOLET(238, 130, 238); // 세미콜론 필수
fun rgb() = (r * 256 + g) * 256 + b // 메서드 포함
}
fun main() {
println(Color.BLUE.rgb())
}
package chap03.section3
interface Score {
fun getScore(): Int
}
// 인터페이스를 구현하는 열거형 클래스
enum class MemberType(var prio: String): Score {
NORMAL("Third"){
override fun getScore(): Int = 100
},
SILVER("Second"){
override fun getScore(): Int = 500
},
GOLD("First"){
override fun getScore(): Int = 1500
}
}
fun main() {
println(MemberType.NORMAL.getScore())
println(MemberType.GOLD)
println(MemberType.valueOf("SILVER"))
println(MemberType.SILVER.prio)
for(grade in MemberType.values()){
println("name = ${grade.name}, priority = ${grade.prio}")
}
}
어노테이션 (annotation) 이란?
어노테이션의 속성
package chap03.section4
class Point(var x: Int = 0, var y: Int = 10){
operator fun plus(p: Point): Point {
return Point(x + p.x, y + p.y)
}
operator fun dec() = Point(--x, --y)
}
fun main() {
val p1 = Point(3, -8)
val p2 = Point(2, 9)
var point = p1 + p2 // 연산자 오버로딩 (객체의 프로퍼티끼리 더함)
println("point = (${point.x}, ${point.y})")
--point // 연산자 오버로딩
println("point = (${point.x}, ${point.y})")
}