본문 바로가기

Programming/Kotlin

[Kotlin]코틀린을 이용한 안드로이드 프로그래밍 실습 02

<퀴즈>

탑레벨의 숫자 데이터 10개를 가지는 immutable 리스트 2개를 생성하고,

리스트 2개를 인자로 받아 같은 인덱스의 값을 비교하여 큰 값만 새로운 리스트에 넣어 리턴하는 탑레벨 함수를 만들고 (두개의 리스트의 크기가 다르면 null 리턴) 리턴받은 리스트의 값들을 인덱스와 함께 출력한다. (idx : value)

1. 새로운 테스트 코틀린 파일을 생성한다. 

2. 값 비교는 if 표현식으로 탑레벨의 max 함수를 작성한다. 

3. 리턴 받는 List는 탑레벨의 lateinit 으로 작성한다.  또한 이 변수는 작성한 함수 안에서 접근하지 않고 리턴된 리스트만 받는다.



# 객체지향 프로그래밍

- PIE

  1. Polymorphism(다형성)

    * overloading : 같은 이름의 함수(연산자)를 다른 타입으로 같이 사용 → 파라미터만 다름

    * overriding : 다른 타입의 객체를 같은 인터페이스로 같이 사용 → 메소드 재정의. 파라미터 같음

  2. Inheritance(상속성) : 부모 객체로부터 코드를 상속받아 그대로 재사용

  3. Encapsulation(은닉성) : 최소한으로 필요한것만 노출시키고 나머지는 숨김

    * public, private 제어자 사용



# Class 선언 방법

- 다양한 형태로 클래스 선언 가능

- 멤버변수, 멤버 함수를 선언할 수 있음

- 내부 클래스 가능

- 기본 생성자, 보조 생성자 → 생성자 오버로딩 느낌?

- 인스턴스 생성시 별도의 new 필요 없음

class MyClass1 {}

class MyClass2

class MyClass3 {
val v1 = 10
var v2 = "hello"
}

class MyClass4 constructor(val v1: Int = 10, val v2: String = "none")

// constructor 키워드 생략 가능
class MyClass5(val v1: Int = 10, val v2: String = "none")

// 접근 제한자를 쓸 경우에는 constructor 키워드 생략 불가
class MyClass6 private constructor(var v1: Int = 10, val v2: String = "none")

class MyClass7(pv1: Int = 10, pv2: String = "none") {
// 아래 두 변수는 멤버변수 아님(?)
var v1 = pv1
var v2 = pv2
}

class MyClass8(v1: Int = 10, v2: String = "none") {
var v1: Int
var v2: String

init { // initialization block, 없으면 오류
this.v1 = v1
this.v2 = v2
}
}

class MyClass9 {
var v1: Int
var v2: String

constructor() : this(0) {} // 이것만 있으면 error
constructor(v1: Int, v2: String = "none") {
this.v1 = v1
this.v2 = v2
}
}
@Test
fun T02() {
var class3 = MyClass3()
println("${class3.v1}, ${class3.v2}")
// 출력 : 10, hello

var class4_1 = MyClass4(1,"aaa")
var class4_2 = MyClass4(v2="zzz")
println("${class4_1.v1}, ${class4_1.v2}")
println("${class4_2.v1}, ${class4_2.v2}")
// 출력 : 1, aaa
// 10, zzz

// var class6 = MyClass6() // 생성자가 private 이므로 인스턴스 생성 불가

var class7 = MyClass7()
println("${class7.v1}, ${class7.v2}")
// 출력 : 10, none

var class8 = MyClass8(4, "test")
println("${class8.v1}, ${class8.v2}")
// 출력 : 4, test

var class9_1 = MyClass9()
var class9_2 = MyClass9(111, v2 = "ggggg")
println("${class9_1.v1}, ${class9_1.v2}")
println("${class9_2.v1}, ${class9_2.v2}")
// 출력 : 0, none
// 111, ggggg

var class10 = MyClass10()
class10.print()
// 출력
// 0 : 1, a
// 1 : 0, none
// 2 : 0, a
}



# Class - property

- 멤버 변수는 모두 property

- 자동으로 get변수이름, set변수이름으로 getter, setter 생성

- 재정의 가능 : 검사, 로깅, 제약 등을 위해 재정의

- getter, setter에서는 field(backing field - 후원필드)로 자신 접근

  * 변수 이름으로 접근할 경우 또 다시 getter, setter가 호출되며 무한 반복되므로 스택 오버플로우 발생

- lateinit 사용 가능

- Nullable ? 사용 가능

class MyClass12 {
var count = 100
get() {
println("myclass12 count get")
return field
}
set(value: Int) {
println("myclass12 count set")
field = value
}
val rocount = 1
}
@Test
fun T02() {
val mc12 = MyClass12()
println("${mc12.count}")
mc12.count = 111
println("${mc12.count}")
}

- 출력

myclass12 count get

100

myclass12 count set

myclass12 count get

111



# Class - 가시성 제한자

- private : 정의된 곳에서만 사용 가능

- protected : 상속시 서브클래스까지만 사용 가능

- public : 어디서나 사용 가능 (default)

- internal : 같은 모듈에서 사용 가능 



# Class - Extentions(확장함수)

- 기존 클래스에 함수를 추가할 수 있음. (이미 정의된 클래스에 내가 원하는 기능을 추가하는 것)

- 기본형(Int 등)도 클래스이기 때문에 확장 함수 작성 가능. 

- 확장 property도 확장 함수처럼 추가 가능

  * 확장 property는 필드 없음 : 상태 저장불가. getter, setter만 가지고 기존의 데이터를 조작하는 것만 가능.

  * 단순히 확장함수를 좀 짧게 만드는 효과

  * 필드 없음 : backing field 없음. 기본 getter/setter 없음. 직접 구현 필요.

- 외부에서 사용할때 확장함수까지 import 해야 사용 가능

  ex) import Int (X) 

       import Int.addex (O)

- 상속 안됨 : override 불가

- Receiver type: 확장이 정의될 클래스 ex) String, Int

- Receiver object: 그 클래스의 인스턴스 객체 ex) this

- 내부적으로 확장함수는 receiver object를 첫번째 인자로 받는 정적 메소드

fun Int.addex(a: Int) = this + a
println("${1.addex(2)}")

- 자바 디컴파일

public final class T02ClassKt {
public static final int addex(int $receiver, int a) {
return $receiver + a;
}
}



# nested, inner class

- Class 내부의 class

 1. nested (중첩 클래스)

  - 아무 키워드 없이 사용되는 내부 클래스

  - 내부 클래스는 java의 static 중첩 클래스와 동일

  - 외부 클래스 멤버 참조 불가

  - 외부 인스턴스 없이 인스턴스 생성 가능

 2. inner (내부 클래스)

  - inenr 키워드 사용

  - 외부 클래스 멤버 참조 가능

    * this@외부클래스 이름으로 접근

  - 외부 인스턴스 없이 인스턴스 생성 불가능

- 둘의 차이점 : 이너 클래스는 특별한 클래스. 자신을 포함한 클래스(상위)가 인스턴스화 되어야만 자신도 인스턴스화 가능. 상위 멤버들이 존재해야 접근 가능하므로

class Outter {
var v1 = 0;
var s1 = "outter"
var o1 = "outter o1"

fun print() = println("$s1, $v1, $o1")
inner class Inner {
var v1 = 1;
var s1 = "Inner"

fun print() {
this@Outter.print();
println("$s1, $v1, $o1")
}
}
}

var o = Outter().Inner();
o.print()

 - 출력

outter, 0, outter o1

Inner, 1, outter o1



# 연산자 오버로딩, infix

- Operator 가 함수로 매핑됨. 

- 매핑된 멤버 함수앞에 operator 키워드 사용

- 매핑된 함수 그대로도 사용 가능

- operator fun invoke 

  * () 함수호출 연산자로 사용 가능

var a = Account(10)

a() -> invoke

- 멤버 함수앞에 infix 키워드를 사용해서 중위 표현식으로 사용할 수 있음.

class Account(val age: Int) {
operator fun plus(account: Account): Account {
return Account(age + account.age)
}

operator fun minus(account: Account): Account {
return Account(age - account.age)
}

override fun toString(): String {
return "$age years old"
}
}
@Test
fun T02() {
println(Account(10) + Account(20))
println(Account(20) - Account(10))
println(Account(10).plus(Account(20)))
println(Account(23))
}

- 출력

30 years old

10 years old

30 years old

23 years old



# 상속

class 클래스이름 : 상속클래스이름(), 인터페이스1, 인터페이스2 {…}

- 인터페이스는 다중상속이 가능하지만, 클래스는 불가능

- Kotlin에서 정의한 클래스는 기본적으로 final(default)

  * 기본적으로 상속불가능

  * superclass 에 open 키워드를 붙여야 상속가능

  * abstract, interface는 기본적으로 open

- 기본적으로 Any 클래스를 상속

- 부모 클래스에 기본 생성자가 있다면, 자식 클래스에서 위임

- super 키워드를 통해 부모 멤버 접근 가능

  * 부모가 여러 개일 경우 super@부모이름

- open 된 메소드 overriding 할때 override 키워드 사용

  * Override 된 메소드를 또다시 상속 받을때 overriding 하지 못하게 하려면 final 

- 프로퍼티도 똑같이 overriding 가능

  * open된 프로퍼티에 대해서만 가능

  * open / override 해주어야함



# interface와 abstract class

1. interface

interface 인터페이스이름{…}

 - class 접근제한 default : open

 - 메소드 접근제한 default : open

 - 껍데기만 존재하기 때문에 직접 인스턴스 생성 불가 

 - 서브클래스는 모든 추상함수(abstract)를 반드시 overriding 해야함

 - 일반 메소드 가질 수 있음

 - 추상 클래스와 다른 점은 인터페이스는 state 저장 불가

  * 프로퍼티는 가질 수 있음. 프로퍼티에 값 할당은 불가능하지만, getter/setter로 다른 데이터에서 가능(?)

  * 자식 클래스에서 override 하여 값 할당 가능

2. abstract(추상클래스)

 abstract class 클래스이름{…}

 - 특정 메소드만 추상화시킨것. 인터페이스는 껍데기만 있는 반면, 추상 클래스는 어느정도 클래스 역할을 함

- 추상 메소드에 abstract 지정자 사용

- 직접 인스턴스 생성 불가 

- 서브클래스는 추상함수를 반드시 overriding 해야함

- 일반 메소드 가질 수 있음

- 프로퍼티 가질 수 있음. 값 저장 가능

- 메소드 default final

- 클래스 default open

- 인터페이스와 다른 점은 state 저장 가능, 인터페이스는 모든 멤버가 open 이지만 추상클래스는 메소드는 final이다.

open class Parent(var a: Int) {
fun funB() {
println("ParentA funB()")
}

open fun funA() {
println("Parent funA()")
}
}

interface IParent {
fun funA() { // abstract
println("IParent funIA()")
}
}

data class Example(var age: Int) : Parent(age), IParent {
constructor(name: String, age: Int) : this(age) {
println("$name $age")
}

// 여기서 funB 를 정의하려고 하면 override 키워드를 붙이라는 오류가 난다
// 하지만 부모클래스에서 funB()가 open 되어있지 않기 때문에 오버라이드가 불가능하다
override fun funA() {
println("Child funA()")
super<IParent>.funA()
super<Parent>.funA()

funB()
}
}

interface MyInterface {
val prop: Int // abstract
val propertyWithImplementation: String
get() = "foo" // get override
// getter/setter 오버라이드 하려면 해당 변수 밑에 해야하는걸 방금 알았음
fun foo(){
print(prop)
}
}

// 변수 오버라이딩
// 아래와 같이 재정의 할 경우에만 인터페이스의 프로퍼티에 값 할당 가능
data class Child (override val prop: Int = 23): MyInterface
@Test
fun T02() {
var ex = Example("delay", 1)
println("$ex")
ex.funA()
println("--------------------------")
// 다형성을 보여주는 예
var ex1: Parent = Example(23)

// 오버라이드된 Example(자식)의 funA()가 호출된다.
// 하나의 슈퍼클래스로 여러 자식의 메소드를 호출할 수 있다. (다운캐스팅 이었던가?)
ex1.funA()
println("==========================")

var ch = Child()
println("$ch : ${ch.propertyWithImplementation}")
ch.foo()
}

- 출력

delay 1

Example(age=1)

Child funA()

IParent funIA()

Parent funA()

ParentA funB()

--------------------------

Child funA()

IParent funIA()

Parent funA()

ParentA funB()

==========================

Child(prop=23) : foo

23



# object 키워드

1. 객체 선언

object ObjectName { … }

  - class 대신 object 로 작성 

  - 따로 인스턴스화 시킬 필요 없음 : 최초로 생성되는 시점에 생성되고, 선언과 동시에 인스턴스화됨

  - 생성자 가질 수 없음.

  - 상속받거나 인터페이스 구현 가능

  - 클래스 내부에서도 사용 가능

   * 외부 클래스가 여러 개 인스턴스화 되어도 하나만 생성됨

   * Outer.InnerObject.xxx 로 사용

   * 자바에서 사용할때는 Outer.InnerObject.INSTANCE.xxxx 로 사용

+ 클래스 생성시 (). 로 인스턴스화(new) 했는데

이럴 필요 없이 바로 접근 가능

2. companion(동반객체)

compainon object ObjectName {…}

- 코틀린에서 static method 역할을 하는 내부 객체

- Outer 객체의 생성자를 private 으로 하고 팩토리 함수를 만들때 유용

  * Outer 의 private 에 접근할 수 있는 static 객체

- 코틀린에서는 Outer.InnerObjectMethod 로 바로 사용 가능

- 자바에서는 Outer.Companion.InnerObjectMethod 로 사용

  * @JvmStatic 어노테이션을 사용하여 코틀린처럼 사용 가능

- 생성자 private -> 객체 생성 불가

  * 자바 : public static 멤버 함수를 만들어 생성자 접근

  * 코틀린 : companion 객체를 이용, static 처럼 접근할 수 있기 때문에 생성자를 접근하여 FactoryMethod로 이용 

3. 객체표현식

object : superclass() { … }

- 익명객체를 생성하는 방법

- 객체선언과 비슷하나 이름이 없음. 

- 클로저 생성

  * 외부에 있는 변수 사용 가능. 뿐만 아니라 호출된 환경(?)을 가져올 수 있음 ex) 안드로이드의 경우, 익명클래스 내부에서 그 바깥에 있는 변수들에 접근할 수 있는 것

- 싱글톤 아님 (매번 인스턴스 생성)

- 리스너 객체 생성에 이용 가능

data class Person3 private constructor(val name: String, val age: Int, var litener: TListener? = null)
{
fun doFire() = litener?.onEvent()

companion object {
fun makePerson(name: String, age: Int): Person3
{
var tmp = age
return Person3(name, age, object: TListener {
override fun onEvent() {
println("evt2 : $tmp")
}
})
}
}
}



# data 키워드, component+n

data class name

- 기존 java 에서 Lombok 외부 라이브러리 사용한 기능 기본으로 제공

- 변수 선언 순서대로 component 번호 부여

- 자동으로 생성되는 메소드

 * constructor

 * getter/setter

 * hashcode/equals

 * toString

 * componentN() functions

 * copy() function

data class User(val id: Long, val name: String)
@Test
fun T02() {
var u = User(0, "name")
println("$u")
println("${u.id}, ${u.name}")
println("${u.component1()}, ${u.component2()}")

val (id, name) = u
println("id : $id name : $name")
}

- 출력

User(id=0, name=name)

0, name

0, name

id : 0 name : name



# 위임 (Delegate)

class Derived(baseImpl: Base) : Base by baseImpl

- 코틀린은 기본적으로 class의 상속을 제한 

 * final class 가 기본값 

 * 상속을 하려면 open class 해야함

- 특정 객체의 기능을 재사용하기 위해 상속 이외에 위임을 할 수 있음

- 특정 인터페이스의 기능을 다른 객체에 위임함.

- 그냥 클래스 안에서 다른 객체 선언해서 갖다쓰는거라고 이해함.

interface Vehicle {
fun go(): String
}
class CarImpl(val where: String): Vehicle {
override fun go() = "is going to $where"
}
class AirplaneImpl(val where: String): Vehicle {
override fun go() = "is flying to $where"
}
class CarOrAirplane(val model: String, impl: Vehicle): Vehicle by impl {
fun tellMeYourTrip() {
println("$model ${go()}")
}
}
@Test
fun T02() {
val myAirbus330 = CarOrAirplane("Lamborghini", CarImpl("Seoul"))
val myBoeing337 = CarOrAirplane("Boeing 337", AirplaneImpl("Seoul"))

myAirbus330.tellMeYourTrip()
myBoeing337.tellMeYourTrip()
}

- 출력

Lamborghini is going to Seoul

Boeing 337 is flying to Seoul



# enum

enum class ClassName 

- enumType 의 상수 객체 항목들.

- 값을 가질 수 있고, 객체로 선언 될 수 있다. 

- when 표현식을 사용할때 모든 열거형 사용했다면 else 없어도 사용 가능

- enumType.value.ordinal 로 순서

- enumType.value.name 으로 이름 사용

- 멤버 함수 가능

 * 멤버 함수는 항목들이 다 나온 후에 나와야 함. 

 * 마지막 항목에는 세미콜론 필요. 

(따로 공부필요한 부분.... 예제 놓쳤음)



# sealed

sealed class ClassName

- 자신의 sub class 의 종류를 제한

  * Sealed class 는 하위 클래스들은 sealed class 와 같은 파일에 선언되어야함

- when 표현식을 사용할때 모든 경우를 사용했다면 else 없어도 사용 가능

- sealed 는 추상 클래스 : 추상멤버 가능

- 비공개 생성자 불가능 (private 생성자인가?)



# 출처 : 모베란 백지훈 대표이사님