Kotlin Programming Intermediate - 5

Beautify.log·2022년 1월 25일
0

Kotlin for Android

목록 보기
5/11
post-thumbnail

이번 포스팅은 클래스와 객체에 대해 다룹니다.

클래스와 객체의 자세한 개념은 자바 포스팅(여기를 눌러 이동)을 참고해주세요.

자바에서는 클래스를 이렇게 작성합니다.

class Person {

	// 멤버변수
	private String name;
	private String gender;
	private int age;

	// 생성자
	public person() {}
    
	public person(String name, String gender, int age) {
		this.name = name;
		this.gender = gender;
		this.age = age;
	}

	// getter와 setter...생략
    
	// Override된 toString()...생략

	// 필요에 따라 멤버 메서드도 작성 가능
	public static String sayHello() {
		return "Hello! " + name
	}
}

우선 멤버변수, 생성자가 눈에 띄고 DTO 목적으로 사용한다면 gettersetter가 필요합니다.

필요에 따라 멤버 메서드도 작성할 수 있습니다.


Kotlin에서 클래스 작성하기


자바와 비슷하게 작성하기

class Person {
    var firstName:String
    var lastName:String
    var age:Int

    constructor(firstName:String, lastName:String, age:Int) {
        this.firstName = firstName
        this.lastName = lastName
        this.age = age
    }

    fun getFullName():String {
        return "$firstName $lastName $age"
    }
}

클래스의 기본적인 틀은 자바와 비슷합니다. 다만 생성자를 만들어줄 때 constructor라는 것을 통해 멤버변수를 넣어주고 this를 사용했습니다.

그리고 멤버 메서드로 함수를 만들어주었습니다.

또 다른 방법으로는 constructor를 사용하지 않고 클래스명 옆에 함수의 매개변수처럼 선언해주는 방법입니다.

class Person(var firstName:String, var lastName:String, var age:Int) {
	fun getFullName():String {
		return "$firstName $lastName $age"
	}
}

이처럼 constructor로 생성자를 만들지 않고 클래스명 옆에 멤버변수를 선언해주면 생성자가 자동으로 만들어집니다.

이 클래스를 객체로 만들어서 사용해보겠습니다.

fun main() {
    val person = Person("gildong", "hong", 20)
    println(person.getFullName())
}

class Person(var firstName:String, var lastName:String, var age:Int) {
    fun getFullName():String {
		return "$firstName $lastName $age"
	}
}

자바에서는 객체를 생성할 때 new 연산자로 객체를 생성해주었고, 이를 생성하면서 초기화해줄 수 있었습니다.

코틀린에서는 new연산자 없이 바로 객체를 생성해주고, 선언 초기화를 할 수 있습니다.


객체 생성 및 초기화


init

생성자를 통해 객체가 만들어질 때 호출되는 함수입니다. 이는 기본생성자라고 하며 constructor 즉 보조생성자를 만들어주기 전에 초기화해주는 역할을 합니다.

다시 말해서 constructor 보조 생성자로 생성자를 만들어줄 때는 init으로 기본생성자를 만들어 초기화 해 준 이후에 보조 생성자를 만들 수 있는 것입니다.

예를 들어서

class MyClass {
    var number:Int
    init {              // 내부 초기화, 객체 생성시 자동 호출
        number = 123
        println(number)
    }
}

이렇게 init으로 초기화된 클래스를 메인 함수에서 생성만 해줘도 123이 출력될 것입니다.

클래스의 상속

자바에서의 클래스 상속

class Human {...}

// Human 클래스를 상속받는 Person 클래스
class Person extends Human {...}

자바에서는 클래스 상속간 extends를 사용했습니다. 코틀린에서는 :를 사용하여 클래스를 상속해줍니다.

class Human : Person() {}

이 때 상속해주는 부모객체에는 open이라는 키워드를 붙여야합니다.

그래서

open class Person() {...}

class Human : Person() {...}

이렇게 클래스를 상속해줄 수 있습니다.

접근제어자

자바에서는 다음과 같은 접근 제어자가 있습니다.

접근 제어자사용법
publicpublic 접근제어자가 붙은 변수, 메소드는 어떤 클래스에서라도 접근이 가능하다.
private접근제어자가 private 이 붙은 변수, 메소드는 해당 클래스에서만 접근이 가능하다.
protectedprotected가 붙은 변수, 메소드는 동일 패키지의 클래스 또는 해당 클래스를 상속받은 다른 패키지의 클래스에서만 접근이 가능하다.

코틀린에서의 접근 제어자는 자바에서의 접근 제어자와 동일하고 여기에 internal이 추가되는데 이는 가시성 변경자로 같은 모듈 안에서만 볼 수 있게 해줍니다.

만약 다음과 같은 클래스를 생성하였다면

open class Person {
    val firstName:String = ""
    val lastName:String = ""
    private var age:Int = 0

    protected var protectedVar:Int = 123

    internal var internalVar:Int = 20

    private fun privateFunc() {

    }
    override fun toString(): String {
        return "Person(firstName='$firstName', lastName='$lastName', age=$age)"
    }

}

firstNamelastName은 기본적으로 아무것도 생성해주지 않았기 때문에 public으로 선언되어 있으므로 어떤 클래스에서라도 접근할 수 있습니다.
ageprivate로 선언되어 있으므로 해당 클래스에서만 접근할 수 있습니다. 이를 다른 클래스에서 접근하도록 하기 위해서는 public으로 변경해주면 됩니다.
protectedVarprotected가 선언되어 있기 때문에 상속받은 다른 클래스에서만 접근할 수 있습니다.
internalVarinternal이 선언되어 있으므로 같은 모듈에서만 접근할 수 있습니다.

그렇지만 접근제어자의 의미가 살짝 퇴색되어 가는 경향이 보이는 것 같아 그렇게 큰 의미는 없는 것 같습니다.

물론 접근 제어자를 사용해서는 안된다는 의미는 아닙니다.


gettersetter && by lazy


gettersetter

자바에서 private로 선언된 클래스의 변수를 참조하여 값을 바꾸거나 조회하기 위해서는 gettersetter를 사용해야 했습니다.

코틀린에서는 자바에서 public으로 선언된 값을 조회하거나 수정하는 것처럼 직접 인스턴스로 접근해서 값을 수정해주는 방법을 사용하여 이것이 gettersetter를 대신할 수 있습니다.

그렇지만 직접 gettersetter를 선언하여 사용할 수 있습니다.

예를 들어

class User(_id:Int, _name:String, _age:Int) {
    var id:Int = _id
        get() = field

    var name:String = _name
        get() = field
        set(value){
            field = value
        }

    var age:Int = _age
        get() = field
        set(value) {
            field = value
        }
}

이와 같이 gettersetter를 어떤 값에 대해 직접 선언해줄 수 있는데, 여기에서 field라는 것에 주목할 필요가 있습니다.

필드는 각각의 값을 가리키는 역할을 합니다. 아이디로 예를 들면 _idid라는 변수안에 넣어주고 fieldid를 가리킵니다. 그래서 get()field를 선언해서 값에 대한 getter가 그 값을 가져오도록 해줍니다.

setter는 들어오는 값 value를 가리키는 값 field에 대입하여 그 값을 고칠 수 있도록 해줍니다.

by lazy란?

by lazy는 늦은 초기화라는 것으로 객체를 생성할 때 반드시 초기화할 필요가 없는 것에 대해 붙여줍니다.

예를 들어 다음과 같은 클래스가 있다고 가정해봅시다.

class LazyClass {
    init {
        println("init block")
    }

    fun flow() {
        println("subject notflow")
        println("subject: $subject")
    }
}

이 클래스를 메인 함수에서 객체로 생성하여 실행되는 순서를 확인해보겠습니다.

fun main(args: Array<String>) {
	
    val lazyCls = LazyClass()		// 실행순서 1
	lazyCls.flow()			// 실행순서 3

}

class LazyClass {
    init {
        println("init block")		// 실행순서 2
    }

    fun flow() {
        println("subject notflow")	// 실행순서 4
        println("subject: $subject")	// 실행순서 5
    }
}

자 그러면 LazyClass의 기본 생성자와 멤버 메서드 사이에 by lazy를 사용하여 늦게 초기화할 생성자를 만들어보겠습니다.

fun main(args: Array<String>) {
	
	val lazyCls = LazyClass()		// 실행순서 1
	lazyCls.flow()				// 실행순서 3

}

class LazyClass {
	init {
		println("init block")		// 실행순서 2
	}

	val subject by lazy {
		println("lazy init")		// 실행순서 6
		"subject Value"			// 실행순서 7
	}

	fun flow() {
		println("subject notflow")	// 실행순서 4
		println("subject: $subject")	// 실행순서 5
	}
}

이처럼 by lazy는 초기화를 늦게 하게 됩니다. 그래서 실행순서도 달라지게 되지요.

by lazy를 사용하려고 할 때 유의해야 할 사항입니다.

  1. val 변수에 대해 사용 가능
  2. gettersetter를 지원하지 않음
  3. null을 허용함
  4. 클래스 생성자에서 사용 불가
  5. 재초기화 되지 않음

결과는 이렇게 출력될 것입니다.

profile
tried ? drinkCoffee : keepGoing;

0개의 댓글