객체 지향 프로그밍 (OOP: Object-Oriented Programming) : 프로그램의 구조를 객체 간 상호작용으로서 표현하는 프로그래밍 방식
↔ 절차적 프로그래밍 (Procedural Programming) : 코딩한 순서대로 프로그램이 수행될 수 있도록 작성하는 방법
📫 알아둘 개념
- 추상화 : 특정 클래스를 만들 때 기본 형식을 규정하는 방법
- 인스턴스 : 클래스로부터 생성한 객체
- 상속 : 부모 클래스의 내용을 자식 클래스가 그대로 물려받음
- 다형성 : 하나의 이름으로 다양한 처리를 제공
- 캡슐화 : 내용을 숨기고 필요한 부분만 사용
- 메시지 전송 : 객체 간에 주고받는 메시지
- 연관 : 클래스 간의 관계
코틀린에서 사용하는 언어 | 다른 언어에서 사용하는 언어 |
---|---|
클래스 (Class) | 분류, 범주 |
프로퍼티 (Property) | 속성, 변수, 필드, 데이터 |
메서드 (Method) | 함수, 동작, 행동 |
객체 (Object) | 인스턴스 |
📫 자바와 코틀린 비교
자바 코틀린 메서드 (클래스에 포함된 기능을 나타내는 함수) 메서드 (클래스에 포함된 기능을 나타내는 함수) 필드 (변수) 프로퍼티 (변수 or 필드에 내부적으로 접근 메서드가 포함되어 있기 때문)
이름 | 설명 | 사용 |
---|---|---|
클래스 다이어그램 (Class Diagram) | 클래스의 정의와 관계를 나타내는 다이어그램 | 클래스의 이름, 프로퍼티, 메서드를 손쉽게 파악할 수 있음 |
시퀀스 다이어그램 (Sequence Diagram) | 시간의 개념을 통해 클래스에서 생성된 객체의 실행 흐름을 나타냄 | 실행 순서를 보기에 적합 |
유스 케이스 다이어그램 (Use Case Diagram) | 사용자 관점에서 사용 방법에 대해 설명 | 시스템에 사용자가 어떤 식으로 접근해 사용할 것인지 보여줌 |
상태 머신 다이어그램 (State-Machine Diagram) | 시스템 관점에서 상태가 어떻게 변화하는지 나타냄 | 객체의 상태 변화를 볼 수 있음 |
+
는 public
, -
는 private
을 의미추상화(Abstraction) : 속성과 동작을 정의하는 과정
Bird |
---|
+name: Sting |
+wing: Int = 2 |
+beak: String |
+color: String |
----------------------------------------------------------------------- |
+fly(): Unit |
+sing(vol:Int): Unit |
class Bird {}
class Bird // 중괄호는 생략가능
fun main() {
val coco = Bird() // 객체 생성
coco.color = "blue" // 프로퍼티에 값 할당
println("coco.color : ${coco.color}")
coco.fly()
coco.sing(3)
}
class Bird {
// 프로퍼티 선언
var name: String = "mybird"
var wing: Int = 2
var beak: String = "short"
var color: String = "blue"
// 메서드 (함수) 선언
fun fly() = println("Fly wing : $wing")
fun sing(vol: Int) = println("Sing vol : $vol")
}
출력 :
coco.color : blue
Fly wing : 2
Sing vol : 3
Bird 클래스란 일종의 선언일 뿐 실제 메모리에 존재해 실행되고 있는 것은 아님
📍 클래스로부터 객체를 생성해야만 객체가 물리적인 메모리 영역에서 실행됨 -> 구체화, 인스턴스화
인스턴스 : 메모리에 올라간 객체
특정 클래스로부터 만들어진 객체는 그 클래스의 인스턴스
📍 객체는 더 포괄적인 용어, 특정 클래스가 아닌 실체화된 것
📫 새의 개념 (Brid)으로 부터 인스터화해 객체(coco)를 만들었다.
생성자 : 클래스를 통해 객체가 만들어질 때 기본적으로 호출되는 함수 (=객체를 생성할 때 자동 실행되는 함수)
constructor()
: 외부에서 인자를 받아 초기화할 수 있도록 특별한 함수 정의class 클래스이름 contructor(필요한 매개변수 ...){ // 주 생성자의 위치
constructor (필요한 매개변수 ...) { // 부 생성자의 위치
// 프로퍼티의 초기화
}
constructor (필요한 매개변수 ...) { // 추가 부 생성자
}
}
fun main() {
val coco = Bird("mybird", 2, "short", "blue")
coco.color = "yellow"
println("coco.color : ${coco.color}")
coco.fly()
coco.sing(3)
}
class Bird {
var name: String
var wing: Int
var beak: String
var color: String
constructor(name: String, wing: Int, beak: String, color: String) {
this.name = name
this.wing = wing
this.beak = beak
this.color = color
}
fun fly() = println("Fly wing : $wing")
fun sing(vol: Int) = println("Sing vol : $vol")
}
출력 :
coco.color : yellow
Fly wing : 2
Sing vol : 3
costruct()
의 name
과 프로퍼티의 name
은 서로 다른 변수this
키워드로 클래스 내부에 있는 함수의 프로퍼티를 참조한다.📍 매개변수 앞에 언더스코어(_)
를 사용해 this
키워드를 대신할 수 있다.
constructor(_name: String, _wing: Int, _beak: String, _color: String) {
name = _name
wing = _wing
beak = _beak
color = _color
}
constructor()
함수 형태로 매개변수가 다르게 여러 번 선언할 수 있다.fun main() {
val bird1 = Bird("mybird", 2, "short", "blue")
val bird2 = Bird("mybird2", "long")
}
class Bird {
var name: String
var wing: Int
var beak: String
var color: String
constructor(_name: String, _wing: Int, _beak: String, _color: String) {
name = _name
wing = _wing
beak = _beak
color = _color
}
constructor(_name: String, _beak: String) {
name = _name
wing = 2
beak = _beak
color = "grey"
}
fun fly() = println("Fly wing : $wing")
fun sing(vol: Int) = println("Sing vol : $vol")
}
주 생성자 : 클래스 이름과 함꼐 생성자 정의를 이용한다.
class Bird constructor(_name: String, _wing: Int, _beak: String, _color: String) {
var name = _name
var wing = _wing
var beak = _beak
var color = _color
fun fly() = println("Fly wing : $wing")
fun sing(vol: Int) = println("Sing vol : $vol")
}
constructor()
생략 가능 fun main() {
val coco = Bird("mybird", 2, "short", "blue")
coco.color = "yellow"
println("coco.color : ${coco.color}")
coco.fly()
coco.sing(3)
}
class Bird(var name: String, var wing: Int, var beak: String, var color: String) {
fun fly() = println("Fly wing : $wing")
fun sing(vol: Int) = println("Sing vol : $vol")
}
출력 :
coco.color : yellow
Fly wing : 2
Sing vol : 3
{}
안에 코드를 넣을 수 없다init{...}
초기화 블록을 선언부에 넣어준다.fun main() {
val coco = Bird("mybird", 2, "short", "blue")
coco.color = "yellow"
println("coco.color : ${coco.color}")
coco.fly()
coco.sing(3)
}
class Bird(var name: String, var wing: Int, var beak: String, var color: String) {
init {
println("~초기화 블록 시작~")
println("이름은 $name 부리는 $beak")
sing(5)
println("~초기화 블록 끝~")
}
fun fly() = println("Fly wing : $wing")
fun sing(vol: Int) = println("Sing vol : $vol")
}
출력 :
~초기화 블록 시작~
이름은 mybird 부리는 short
Sing vol : 5
~초기화 블록 끝~
coco.color : yellow
Fly wing : 2
Sing vol : 3
+) 생성자의 매개변수에 기본값 사용할 수 있다.
상속(Inheritance) : 클래스는 자식 클래스를 만들 때 상위 클래스(부모 클래스) 의 속성과 기능을 물려받아 계승
📍 하위 클래스는 상위 클래스의 모든 내용을 다시 만들지 않아도 된다.
Any
클래스의 하위 클래스가 된다. Any
클래스를 상속받게 된다.open
키워드 사용 : 클래스를 상속할 수 있는 상태open
이 없으면 상속할 수 없는 기본 클래스📫 자바에서는 기본적으로 선언하는 클래스가 상속 가능한 클래스
-> 자바에서 상속할 수 없는 클래스로 선언하려면final
키워드 사용
fun main() {
val coco = Bird("mybird", 2, "short", "blue")
val lark = Lark("mylark", 2, "long", "brown")
val parrot = Parrot("myparrot", 2, "short", "multiple", "korean")
coco.color = "yellow"
println("Coco : ${coco.name}, ${coco.wing}, ${coco.beak}, ${coco.color}")
println("Lark : ${lark.name}, ${lark.wing}, ${lark.beak}, ${lark.color}")
println("Parrot : ${parrot.name}, ${parrot.wing}, ${parrot.beak}, ${parrot.color}, ${parrot.language}")
lark.singHitone()
parrot.speak()
lark.fly()
}
open class Bird(var name: String, var wing: Int, var beak: String, var color: String) {
fun fly() = println("Fly wing : $wing")
fun sing(vol: Int) = println("Sing vol : $vol")
}
// 주 생성자를 사용하는 상속
class Lark(name: String, wing: Int, beak: String, color: String) : Bird(name, wing, beak, color) {
fun singHitone() = println("Happy song!")
}
// 부 생성자를 사용하는 상속
class Parrot : Bird {
val language: String
constructor(name: String, wing: Int, beak: String, color: String, language: String) : super(
name,
wing,
beak,
color
) {
this.language = language
}
fun speak() = println("Speak $language")
}
출력 :
Coco : mybird, 2, short, yellow
Lark : mylark, 2, long, brown
Parrot : myparrot, 2, short, multiple, korean
Happy song!
Speak korean
Fly wing : 2
ㅤ:ㅤ
콜론 앞 뒤에 공백이 있으면 상속클래스 선언
, 변수선언
을 구분하기 위한 코딩 관례다형성(polymorphism) : 이름이 동일하지만 매개변수가 서로 다른 형태를 취하거나 실행 결과를 다르게 가질 수 있다.
오버로딩 : 같은 클래스 안에서 같은 이름의 메서드가 매개변수만 달리해서 여러 번 정의될 수 있다.
fun main() {
val calc = Calc()
println(calc.add(3, 2))
println(calc.add(3.2, 1.5))
println(calc.add(3, 3, 2))
println(calc.add("Hello", "World"))
}
class Calc {
fun add(x: Int, y: Int): Int = x + y
fun add(x: Double, y: Double): Double = x + y
fun add(x: Int, y: Int, z: Int): Int = x + y + z
fun add(x: String, y: String): String = x + y
}
오버라이딩 : 하위 클래스에서 새로 만들어지는 메서드가 이름, 매개변수, 반환값이 이전의 메서드와 똑같지만 새로 작성된다.
📍 재정의한다 : 하위의 새로운 메서드는 상위 클래스의 메서드의 내용를 새로 만들어 다른 기능을 하도록 정의한다.
open
, 파생 클래스에서는 override
키워드를 사용한다.final
키워드를 통해 하위 클래스에서 오버라이딩되는 것을 막을 수 있다.fun main() {
val parrot = Parrot("myparrot", 2, "short", "multiple", "korean")
parrot.language = "English"
println("Parrot : ${parrot.name}, ${parrot.wing}, ${parrot.beak}, ${parrot.color}, ${parrot.language}")
parrot.sing(5)
}
open class Bird(var name: String, var wing: Int, var beak: String, var color: String) {
fun fly() = println("Fly wing : $wing")
open fun sing(vol: Int) = println("Sing vol : $vol")
}
class Parrot(
name: String,
wing: Int = 2,
beak: String,
color: String,
var language: String = "natural"
) : Bird(name, wing, beak, color) {
fun speak() = println("Speak $language")
override fun sing(vol: Int) {
println("I'm a parrot! The volume level is $vol")
speak()
}
}
출력 :
Parrot : myparrot, 2, short, multiple, English
I'm a parrot! The volume level is 5
Speak English
super
하위 클래스는 this
키워드로 참조가 가능하다.super | this |
---|---|
super.프로퍼티 이름 | this.프로퍼티 이름 |
super.메서드 이름() | this.메서드 이름() |
super() | this() |
super
로 상위 클래스의 프로퍼티나 메서드, 생성자를 사용할 수 있다.class Parrot(
name: String,
wing: Int = 2,
beak: String,
color: String,
var language: String = "natural"
) : Bird(name, wing, beak, color) {
fun speak() = println("Speak $language")
override fun sing(vol: Int) {
super.sing(vol)
println("I'm a parrot! The volume level is $vol")
speak()
}
}
super.sing(vol)
을 추가하면 Sing vol : 5
가 출력fun main() {
val sean = Devoloper("Jimuk")
}
open class Person {
constructor(firstName: String) {
println("[Person] firstName : $firstName")
}
constructor(firstName: String, age: Int) {
println("[Person] firstName : $firstName, age : $age")
}
}
class Devoloper : Person {
constructor(firstName: String) : this(firstName, 20) {
println("[Devoloper] $firstName")
}
constructor(firstName: String, age: Int) : super(firstName, age) {
println("[Devoloper] $firstName, $age")
}
}
출력 :
[Person] firstName : Jimuk, age : 20
[Devoloper] Jimuk, 20
[Devoloper] Jimuk
응애 나 스무살👶
this
- 현재 클래스를 가리킴, super
- 상위 클래스를 가리킴 을 사용해 다른 생성자를 처리할 수 있다.fun main() {
val p1 = Person("Jimuk", 20)
println()
val p2 = Person("baby")
}
class Person(firstName: String, out: Unit = println("[Primary Constructor] Parameter")) {
val fName = println("[Property] Person fName : $firstName")
init {
println("[Init] Person init block")
}
constructor(firstName: String, age: Int, out: Unit = println("[Second Constructor] Parameter")) : this(firstName) {
println("[Second Constructor] Body : $firstName, $age")
}
}
출력 :
[Second Constructor] Parameter
[Primary Constructor] Parameter
[Property] Person fName : Jimuk
[Init] Person init block
[Second Constructor] Body : Jimuk, 20
[Primary Constructor] Parameter
[Property] Person fName : baby
[Init] Person init block
확실히 책으로 보는 것보다 직접 작성해 보는 게 더 눈에 잘 보인다..~
out
에 println()
을 할당해 인자에 접근할 때 마다 출력이너 클래스(Inner Class) : 특정 클래스 안에 선언된 클래스
super@바깥클래스이름
open class Base {
open val x: Int = 1
open fun f() = println("Base Class f()")
}
class Child : Base() {
override val x: Int = super.x + 1
override fun f() = println("Child Class f()")
inner class Inside {
fun f() = println("Inside Class f()")
fun test() {
f()
Child().f()
super@Child.f()
println("[Inside] super@Child.x : ${super@Child.x}")
}
}
}
fun main() {
val c1 = Child()
c1.Inside().test()
}
출력 :
Inside Class f()
Child Class f()
Base Class f()
[Inside] super@Child.x : 1
인터페이스(Interfacce) : 일종의 구현 약속으로 인터페이스를 참조하는 클래스는 인터페이스가 가지고 있는 내용을 구현해야 하는 가이드를 제시한다.
<>
을 사용하여 접근하려난 클래스나 인터페이스의 이름을 정해준다. super<A>.f()
, super<B>.f()
open
이다.open class A {
open fun f() = println("A Class f()")
fun a() = println("A class a()")
}
interface B {
fun f() = println("B interface f()")
fun b() = println("B interFACE b()")
}
class C : A(), B {
override fun f() = println("C Class f()")
fun test() {
f()
b()
super<A>.f()
super<B>.f()
}
}
fun main() {
val c = C()
c.test()
}
출력 :
C Class f()
B interFACE b()
A Class f()
B interface f()
📫 코틀린은 자바처럼 한 번에 2개 이상의 클래스를 상속받는 다중 상속이 되지 않는다.
가시성(Visibility) : 각 클래스, 메서드, 프로퍼티의 접근 범위
📍 공개할 부분과 숨길 부분을 정해줄 수 있다.
private : 외부에서 접근할 수 없다.
public : 어디서든 접근이 가능하다. (default)
protected : 외부에서 접근할 수 없으나, 하위 상속 요소에서 접근 가능하다.
internal : 같은 정의의 모듈 내부에서는 접근 가능하다.
✏️ 선언되는 위치
[가시성 지시자] <val | var> 전역 변수 이름
[가시성 지시자] fun 함수이름 () {...}
[가시성 지시자] [특정 키워드] class 클래스이름 [가시성 지시자] constructor(매개변수) {
[가시성 지시자] constructor() {...}
[가시성 지시자] 프로퍼티
[가시성 지시자] 메서드
}
public
constructor
키워드는 생략할 수 없다.: 그 클래스 안의 멤버만 접근할 수 있다.
private class PrivateClass {
private var num = 1
private fun privateFunc() {
num += 1 // 접근 O
}
fun access() {
privateFunc() // 접근 O
}
}
class OtherClass {
val opc = PrivateClass() // 불가능 ! private으로 선언해주어야함
fun test() {
val pc = PrivateClass() // 생성 가능
}
}
fun main() {
val pc = PrivateClass() // 생성 가능
pc.num // 접근 불가
pc.pricvateFunc() // 접근 불가
}
fun TopFunction() {
val tpc = PrivateClass() // 객체 생성 가능
}
private
으로 선언되어 있어, 다른 파일에서 접근 불가능PrivateClass
의 객체를 생성할 수 있음 -> 다른 클래스에서 지정하려면 private
으로 선언 해야함num
, 메서드인 privateFunc
은 private
이라 다른 클래스, main()에서 접근 불가능: 클래스, 인터페이스와 같은 요소의 멤버에 지정할 수 있다.
📍 최상위에 선언된 요소에는 지정 불가능
open class Base {
protected var i = 1
protected fun protectedFunc() {
i += 1
} // 같은 요소의 멤버에서 접근 가능
fun access() {
protectedFunc()
} // 접근 가능
protected class Nested // 내부 클래스에는 지시자 허용
} // 최상위 class에는 protected 사용할 수 없음
class Derived : Base() {
fun test(base: Base): Int {
protectedFunc() // 하위 클래스에서 접근 가능 (메서드 접근)
return i // 하위 클래스에서 접근 가능 (프로퍼티 접근)
}
}
fun main() {
val base = Base() // 생성 가능
base.i // 접근 불가
base.protectedFunc() // 접근 불가
base.access() // 접근 가능
}
: 프로젝트 단위의 모듈을 가리킴 -> 모듈이 달라지면 접근할 수 없다.
📫 자바의
package
지시자
- 접근 요소가 package 내부에 있으면 접근 가능
.jar
파일이 달라도 package 이름이 동일하면 접근할 수 있었기 때문에 보안 문제가 있었음📍 코틀린에서는 package를 버리고 프로젝트의 같은 모듈이 아니면 외부에서 접근할 수 없게 함 !!
internal class InternalClass {
internal var i = 1
internal fun icFunc() {
i += 1 // 접근 허용
}
fun access() {
icFunc() // 접근 허용
}
}
class Other {
internal val ic = InternalClass() // 프로퍼티를 만들 때 internal로 통일
fun test() {
ic.i
ic.icFunc()
}
}
fun main() {
val mic = InternalClass() // 생성 가능
mic.i // 접근허용
mic.icFunc() // 접근허용
}
import
해와서 해당 클래스를 사용하면 된다.open class Car protected constructor(_year: Int, _model: String, _power: String, _wheel: String) {
private var year: Int = _year
public var model: String = _model
protected open var power: String = _power
internal var wheel: String = _wheel
protected fun start(key: Boolean) {
if (key) println("Start the Engine!")
}
class Driver(_name: String, _license: String) {
private var name: String = _name
var license: String = _license
internal fun driving() = println("[Driver] Driving() - $name")
}
}
class Tico(_year: Int, _model: String, _power: String, _wheel: String, var name: String, private var key: Boolean) :
Car(_year, _model, _power, _wheel) {
override var power: String = "50hp"
val driver = Driver(name, "first class")
constructor(_name: String, _key: Boolean) : this(2014, "basic", "100hp", "normal", _name, _key) {
name = _name
key = _key
}
fun access(password: String) {
if (password == "gotico") {
println("---[Tico] access()---")
println("super.model = ${super.model}")
println("super.power = ${super.power}")
println("super.wheel = ${super.wheel}")
super.start(key)
println("Driver().license = ${driver.license}")
driver.driving()
} else {
println("You're a burglar")
}
}
}
class Burglar() {
fun steal(anycar: Any) {
if (anycar is Tico) {
println("---[Burglar] steal()---")
println("anycar.name = ${anycar.name}")
println("anycar.wheel = ${anycar.wheel}")
println("anycar.model = ${anycar.model}")
println(anycar.driver.license)
anycar.driver.driving()
anycar.access("dontknow")
} else {
println("Nothing to steal")
}
}
}
fun main() {
val tico = Tico("Jimuk", true)
tico.access("gotico")
val burglar = Burglar()
burglar.steal(tico)
}
출력 :
---[Tico] access()---
super.model = basic
super.power = 100hp
super.wheel = normal
Start the Engine!
Driver().license = first class
[Driver] Driving() - Jimuk
---[Burglar] steal()---
anycar.name = Jimuk
anycar.wheel = normal
anycar.model = basic
first class
[Driver] Driving() - Jimuk
You're a burglar
연관 관계 : 클래스 간의 참조를 유지
의존 관계 : 클래스 간의 참조를 유지하지 않음
집합 관계 : 연관 관계에 있으면서 객체의 생명주기가 서로 유지
구성 관계 : 연관 관계에 있으면서 객체의 생명주기가 서로 유지되지 않음
class Patient(val name: String) {
fun doctorList(d: Doctor) { // 인자로 참조
println("Patient: $name, Doctor: ${d.name}")
}
}
class Doctor(val name: String) {
fun patientList(p: Patient) { // 인자로 참조
println("Doctor: $name, Patient: ${p.name}")
}
}
fun main() {
val doc1 = Doctor("Hong")
val patient1 = Patient("KIM")
doc1.patientList(patient1)
patient1.doctorList(doc1)
}
출력 :
Doctor: Hong, Patient: KIM
Patient: KIM, Doctor: Hong
Doctor
클래스는 Patient
를 매개변수로 받아야 하므로 Patient
객체가 먼저 생성되어 있어야함class Patient(val name: String, var id: Int) {
fun doctorList(d: Doctor) {
println("Patient: $name, Doctor: ${d.name}")
}
}
class Doctor(val name: String, val p: Patient) {
val customerId: Int = p.id
fun patientList() {
println("Doctor: $name, Patient: ${p.name}")
println("Patient Id: $customerId")
}
}
fun main() {
val patient1 = Patient("Jimuk", 1234)
val doc1 = Doctor("Kim", patient1)
doc1.patientList()
}
출력 :
Doctor: Kim, Patient: Jimuk
Patient Id: 1234
class Pond(_name: String, _members: MutableList<Duck>) {
val name: String = _name
val members: MutableList<Duck> = _members
constructor(_name: String) : this(_name, mutableListOf<Duck>())
}
class Duck(val name: String)
fun main() {
// 두 개체는 서로 생명주기에 영향을 주지 않는다.
val pond = Pond("myFavorite")
val duck1 = Duck("Duck1")
val duck2 = Duck("Duck2")
// 연못에 오리를 추가 - 연못에 오리가 집합한다
pond.members.add(duck1)
pond.members.add(duck2)
// 연못에 있는 오리들
for (duck in pond.members) {
println(duck.name)
}
}
출력 :
Duck1
Duck2
class Car(val name: String, val power: String) {
private var engine = Engine(power) // Engine클래스 객체는 Car에 의존적이다
fun startEngine() = engine.start()
fun stopEngine() = engine.stop()
}
class Engine(power: String) {
fun start() = println("Engine has been started.")
fun stop() = println("Engine has been stopped.")
}
fun main() {
val car = Car("tico", "100hp")
car.startEngine()
car.stopEngine()
}
출력 :
Engine has been started.
Engine has been stopped.