Uknow's Lab.
article thumbnail

 
코파기(코틀린 파헤치기) 시리즈 1부는 재밌게 읽고 오셨을까요?
객체지향편 시작합니다.
 
 

객체지향 프로그래밍(OOP, Object Oriented Programming)

객체지향 프로그래밍이란, 프로그램을 단순히 데이터를 처리하는 것으로 보는 것을 넘어, 각 객체(Object) 단위로 나누고, 이들간의 서로 상호작용하는 프로그래밍 패러다임 입니다.

 
 
음... 객체지향을 어떻게 소개해야 하나 고민이 좀 많았습니다.
객체지향에 관한 내용을 설명하는 것 만으로도 책 몇 권은 뚝딱 나올텐데... 다 적을 수 있을까?
 
자바의 객체지향에 관한 책을 읽었을 때,
저자는 진정한 객체지향이 뭔지,
객체지향 프로그래밍을 시작한지 8년이 지나서야 좀 알 것 같다고 적혀있었습니다.
 
흠... 함부로 적었다간 고수분들께 호되게 혼날 것 같아,
객체지향 자체의 내용은 간략히 설명한 후,
객체지향 자체의 내용보다는 코틀린으로 객체지향을 어떻게 하는지에 관한 내용을 다뤄보겠습니다.
 
 

클래스(Class)의 선언

class Person { 
	
}

클래스는 객체를 정의하는 틀 혹은 설계도입니다.
이 설계도(Class)를 가지고 여러 객체를 생성합니다.
 
코틀린의 클래스 선언은 자바와 상당히 유사합니다.
class 키워드와 클래스 이름, 그리고 몸체가 되는 중괄호로 이루어져 있습니다.
 
 

class Person {
	val word = "Korea"
	fun say() { println("$word") }
	
	class PersonInnerClass {
	
	}
}

위와 같이, 클래스 내 함수와 프로퍼티, 내부 클래스 등을 생성할 수 있습니다.
 
 

객체의 생성 (인스턴스화)

class Person {
	var word = "Temp"
	fun say() { println(word) }
}

fun main() {
	val person1 = Person()
	val person2 = Person() // 객체의 생성. 인스턴스화 (객체를 메모리에 할당함)

	// new 키워드를 필요로 하지 않음.
	val person3 = new Person() // 에러발생!
}

 
설계도(Person Class)를 갖고 객첼르 만든 모습입니다.
클래스를 이용해 객체를 만드는 것을 인스턴스(Instance)화라고 합니다.
실체화되어 메모리에 할당된 상태지요.
 
자바와는 다르게 new 키워드를 사용하지 않습니다.
 
 

class Person {
	var word = "Temp"
	fun say() { println(word) }
}

fun main() {
	val person1 = Person()
	val person2 = Person() // 객체의 생성. 인스턴스화 (객체를 메모리에 할당함)

	person1.word = "안녕하세요. 저는 아이유 입니다."
	person1.word = "안녕하세요. 저는 어른이에유 입니다."

	person1.say()
	person2.say()
}


// 출력
>>> 안녕하세요. 저는 아이유 입니다.
안녕하세요. 저는 어른이에유 입니다.

 
객체별로 서로 할당된 메모리가 다르므로, 모든 객체는 서로 다른 객체입니다.
따라서, 서로 다른 데이터를 갖고 있습니다.
 
 

클래스와 생성자 (Constructor)

생성자는 객체가 생성될 때, 자동으로 호출되는 메소드입니다.
생성자라는 이름처럼, 객체를 처음 생성할 때 기본적인 세팅 등을 하는데 쓰이곤 합니다.
 

class ExClass1 constructor(val word: String) {
    fun say() {
        println(word)
    }
}

class ExClass2(val word: String) {
    fun say() {
        println(word)
    }
}

fun main() {
    val exClass1 = ExClass1("Ooo")
    val exClass2 = ExClass2("Oooooo")
}

 
생성자는 위와 같이 소괄호() 안에 프로퍼티를 넣음으로써 쉽게 다룰 수 있습니다.
생성자로 넘겨준 프로퍼티는 클래스 안 어디서든 접근이 가능한 전역변수가 됩니다.
 
 

// 자바
class ExClass {
    int n1, n2, n3;
    
    ExClass(int n1, int n2, int n3) {
        this.n1 = n1;
        this.n2 = n2;
        this.n3 = n3;
    }
}

생성자로 매개변수를 입력받고,
같은 이름의 전역변수에 this 키워드로 접근에 값을 할당하던
기존의 자바를 개선한 느낌을 받았습니다.
 
 

생성자 사용 시 기본 값 지정

class Person(name:String, age:Int = 15) {
	
}


fun main() {
	val p1 = Person("홍길동", 10)
	val p2 = Person("전우치") // age가 15로 자동 초기화됨
}

 
함수의 매개변수에 디폴트값을 지정할 수 있던 것과 비슷하게,
클래스 역시 생성자에서 디폴트 값을 지정하는 것이 가능합니다.
 
그런데, 여기서 한 가지 의문이 듭니다.
자바에서의 생성자는 메소드였습니다.
생성자에서 단순히 this를 통해 전역변수에 값을 저장하는 용도로만 쓰였던 게 아니라,
생성자에서 다른 메소드를 호출하거나 연산을 수행하기도 했습니다.
 
하지만, 위와 같은 생성자는 그저 변수 할당밖에 할 수가 없어요.
 
 

init

class Person(name: String) {

	init {
		println("안녕하세요. 저는 $name 입니다")
	}
}

fun main() {
	val p1 = Person("홍길동")
}

// 출력
>>> 안녕하세요. 저는 홍길동 입니다

 
위 문제점에 대한 좋은 방안은 init 입니다.
init은 객체가 처음 만들어졌을때 실행되는 코드 블럭으로써,
생성자를 () 안에 넣어주면서, init을 통해 자바의 생성자와 같이 연산도 수행할 수 있지요.
 
 

보조 생성자 (Secondary Constructor)와 생성자 오버로딩

좋아! 생성자로 매개변수를 간편하게 넘겨주는 방법을 알았어!
init으로 코드 블럭을 실행하는 방법도 알았어!
근데... 그러면 생성자 오버로딩은 어떻게 하는데?
 
 

class Person{
    constructor(age:Int) {
        println("첫 번째 생성자. 나이 = $age")
    }

    constructor(name:String, age:Int) {
        println("두 번째 생성자. 이름 = $name, 나이 = $age")
    }
}

fun main() {
    val p1 = Person(14)
    val p2 = Person("홍길동", 234)
}

// 출력
>>> 첫 번째 생성자. 나이 = 14
두 번째 생성자. 이름 = 홍길동, 나이 = 234

 
코틀린에서는 클래스명 옆에 소괄호와 매개변수를 넘겨주는 주 생성자 말고도,
클래스 내 constructor를 사용한 보조 생성자(Secondary Constructor)를 지원합니다.
이를 사용해 자바의 생성자처럼 오버로딩이 가능하지요.
 
 

그럼 보조 생성자와 init을 같이 쓸 수도 있나?

class Person{
    init {
        println("init 실행!")
    }
    constructor(age:Int) {
        println("첫 번째 생성자. 나이 = $age")
    }
}

fun main() {
    val p1 = Person(14)
}

// 출력
>>> init 실행!
첫 번째 생성자. 나이 = 14

 
당연히 같이 쓸 수 있습니다.
다만, 둘이 혼용하여 쓸 경우 init이 먼저 실행된 후, 보조 생성자가 가동합니다.
 
 

부록. 코틀린은 main() 함수가 클래스가 아니라 단독으로 있네?

자바는 C++과 다르게, main 함수 까지도 클래스로 이루어져 있습니다.
 

// Java
public class Test() {
	public static void main(String[] args) {
		System.out.println("Hello World");
	}
}

fun main() {
	println("Hello World")
}

 
코틀린 역시 main 함수를 class 안에 넣어 만들 수 있지만,
지금까지 봐왔던 예제를 보면, 굳이 class 안에 넣지 않아도 동작은 되긴 했습니다.
 
 

이는 코틀린 코드가 JVM 위에서 실행될 때, class를 자동으로 생성하기 때문입니다.
결국, 코틀린의 main 함수도 사실은 class가 필요한 것이죠.
다만 생략이 가능하였을 뿐, 생략하면 뒤에서 만들어주고 있었습니다.

profile

Uknow's Lab.

@유노 Uknow

인생은 Byte와 Double 사이 Char다. 아무말이나 해봤습니다.