전공수업/Kotlin

[Kotlin] 모바일프로그래밍 2-3주차(2)

aaahyunseo 2024. 10. 12. 03:49

Chapter3 - (7) 클래스와 설계

클래스(Class)

: 단순하게 보면 그룹화할 수 있는 변수와 함수의 모음.

class 클래스명{ var 변수 fun 함수(){} } //클래스의 기본 구조

▷클래스를 사용하기 위해서는 생성자가 호출되어야 함.

→ 코틀린은 프라이머리(Primary)세컨더리(Secondary) 2개의 생성자를 제공.

1) 프라이머리 생성자

: 클래스의 헤더처럼 사용 가능.

constructor 키워드를 사용해 정의하는데

조건에 따라 생략 가능.

(생성자에 접근 제한자나 다른 옵션이 없으면 생략 가능)

🪄중괄호({})스코프(Scope)라고 함.

→ 클래스 코드를 감싸는 것은 클래스 스코프.

🪄프로퍼티(Property) 클래스에 정의된 변수 or 멤버 변수

class Person constructor(value: String){ init { //생성자 초기화 } }

▷프라이머리 생성자는 헤더처럼 class 키워드와 같은 위치에 작성.

클래스의 생성자가 호출되면

init 블록의 코드가 실행되고,

init 블록에서는 생성자를 통해 넘어온 파라미터에 접근 가능.

2)세컨더리 생성자

: constructor 키워드를 함수처럼 클래스 스코프 안에 직접 작성.

class Kotlin { constructor (value: Stirng) {} constructor (vlaue: Int) {} constructor (value1: Stirng, value2: Int) {} }

▷세컨더리 생성자는 파라미터 개수, 또는 파라미터 타입이 다르다면

여러 개를 중복해서 만들 수 있음.

3)Default 생성자

: 생성자는 작성하지 않을 경우 파라미터가 없는 프라이머리 생성자가 하나 있는 것과 동일.


오브젝트(object)

: 클래스를 생성자로 인스턴스화 하지 않아도

블록 안 프로퍼티와 메서드를 호출해서 사용할 수 있음.

컴패니언 오브젝트(companion object)

: 일반 클래스에 object 기능을 추가하기 위해 사용.

▷컴패니언 오브젝트의 블록 안에서 변수와 함수를 정의하면

생성자를 통하지 않고 클래스의 멤버들을 사용할 수 있음.

class Practice{ companion object{ //프로퍼티 & 메서드 } fun A(){ //오브젝트가 아닌 일반 함수 } }

오브젝트 스코프 안에 있는 프로퍼티와 메서드들은

클래스명에 도트 연산자(.)를 사용해 바로 사용할 수 있음.

반면, 일반 함수는 생성자 Practice()를 호출한 후 변수에 저장하는 원래 형태로 사용해야 함.


데이터 클래스(data class)

: 간단한 값의 저장 용도로 사용.

data class 클래스명 (val 파라미터1: 타입, var 파라미터2: 타입)

data class UserData(val name: String, var age: Int) //주로 코드 블록(클래스 스코프)을 사용하지 않고 간단하게 작성 var userData = UserData("Michael",21) //일반 class의 생성자 함수를 호출하는 것과 동일

🛠️

toStirng()

: 일반 클래스에서 호출 시에는 인스턴스의 주소 값을 반환하지만,

데이터 클래스에서 호출 시에는 값을 반환함.

(실제 값 모니터링에 용이)


클래스의 상속과 확장

▷클래스의 재사용을 위해 상속을 지원함.

*상속을 사용하면 부모 클래스의 메서드와 프로퍼티를

내 클래스의 일부처럼 사용할 수 있음.

상속은 코드를 재사용하는 측면도 있지만

코드를 체계적으로 관리할 수 있기 때문에 규모가 큰 프로젝트도 효과적으로 설계 가능.

클래스의 상속

▷부모 클래스를 open 키워드로 만들어야 자식 클래스에서 사용 가능함.

→ 자식 클래스는 콜론을 이용해서 상속할 부모 클래스를 지정.

*상속부모의 인스턴스를 자식이 갖는 과정이므로

부모 클래스명 다음에 괄호를 입력해 꼭 부모 생성자를 호출해야 함.

open class 부모클래스{} class 자식클래스: 부모클래스() {}

▽생성자 파라미터가 있는 클래스의 상속일 경우

open class 부모클래스(value: String){} class 자식클래스(value: String): 부모클래스(value) {}

▽세컨더리 생성자가 있는 클래스의 상속일 경우

class 자식클래스: 부모클래스() { constructor(ctx: Context): super(ctx) //super 키워드로 부모클래스에 전달 }

오버라이드(Override)

: 프로퍼티와 메서드의 재정의.

▷동일한 이름의 메서드나 프로퍼티를 사용할 필요가 있을 경우에

override 키워드를 사용해 재정의 가능.

1)메서드 오버라이드

: 상속할 메서드 앞에 open 키워드를 붙이면 오버라이드할 수 있음.

*없으면 오버라이드 불가능

open class BaseClass{ open fun opened() {} fun notOpened() {} } class ChildClass: BaseClass(){ overried fun opend(){} //BaseClass 내에 있는 opened 함수 오버라이드 }

*세컨더리 생성자를 여러 개 중복 사용할 수 있는 것도 오버라이딩임.

2)프로퍼티 오버라이드

open class BaseClass{ open var opened: String = "kotlin" } class ChildClass: BaseClass(){ overried var opened: String = "java" } //메서드와 동일하게 open으로 열려있어야 오버라이드 가능.

3)익스텐션(Extensions)

: 이미 만들어져 있는 클래스에 메서드를 추가할 수 있음.

fun 클래스.확장할메서드() {} //메서드 확장

🪄상속 vs. 익스텐션

상속은 미리 만들어져 있는 클래스를 가져다 쓰는 개념.

익스텐션은 미리 만들어져 있는 클래스에 메서드를 넣는 개념.


설계 도구

: 패키지, 추상화, 인터페이스, 제네릭

*객체지향 프로그래밍은 설계와 구현(혹은 역할과 구현)으로 구분할 수 있음.

이 전까지의 내용들이 구현 중점의 기법들 이었고,

이 후에 나올 패키지, 추상화, 인터페이스, 제네릭은 설계를 위한 도구들임.

패키지(Package)

: 클래스와 소스 파일을 관리하기 위한 디렉터리 구조의 저장 공간.

▷서로 관계가 있는 파일을 동일한 패키지에 만들어두면 관리가 용이함.

package MainDirectory.SubDirectory //디렉터리가 계층 구조로 만들어져 있으면 온점으로 구분해서 각 디렉터리를 모두 나열.

추상화(Abstract)

: 프로그램 설계 단계에서 있음 직한 기능을 유추해 메서드 이름을 나열하는데,

명확한 코드들은 메서드 블록 안에 직접 코드를 작성하고,

그렇지 않은 경우에 구현 단계에서 코드를 작성하도록 메서드의 이름만 작성하는 것.

abstract class Design{ abstract fun draw() //추상화 fun showWindow() {} } //abstract 키워드를 사용해 명시. class Implements: Disign() { fun draw(){ //구현 코드 } }

인터페이스(interface)

: 실행 코드 없이 메서드 이름만 가진 추상 클래스.

🪄추상클래스 vs. 인터페이스

추상클래스는 클래스 안에 실행 코드가 한 줄이라도 있는 경우

인터페이스는 코드 없이 메서드 이름만 나열되어 있는 경우

🛠️내가 헷갈릴까봐 적어두는 것

추상화(abstract) : 클래스를 개념 설계하기 위한 도구

인터페이스(interface) : 외부 모듈에 제공하기 위해 메서드 이름을 나열한 명세서

→추상화의 한 형태

*둘 다 다형성을 구현.

interface 인터페이스명{ var 변수: String //코틀린은 프로퍼티도 인터페이스 내부에 정의 가능. fun 메서드1( ) fun 메서드2( ) }

접근 제한자(Visibility Modifiers)

: 클래스의 멤버에 지정된 접근 제한자에 따라 외부에서 사용 여부가 결정됨.

▷클래스, 인터페이스, 메서드, 프로퍼티는 모두 접근 제한자를 가질 수 있음.

private : 다른 파일에서 접근할 수 없음.

internal : 같은 모듈에 있는 파일만 접근 가능.

*모듈 : 한 번에 같이 컴파일되는 모든 파일.

protected : private와 같으나 상속 관계에서 자식 클래스가 접근 가능.

public : 제한 없이 모든 파일에서 접근 가능.

제네릭(Generics)

: 입력되는 값의 타입을 자유롭게 사용하기 위한 설계 도구.

→타입을 특정해서 안정성을 유지하기 위한 설계 도구임


Chapter3 - (8) null 값에 대한 안정적인 처리: Null Safety

null 포인터 예외 발생 시

ⅰ) 프로그램 다운

ⅱ) 오류를 발생 시켜 컴파일되지 않도록 막아줌 → Null Safety 필요.

null 값 허용하기: ?

▷코틀린에서 지정하는 기본 변수는 모두 null 입력X.

null 값을 입력하기 위해서는 변수 선언 시 타입 뒤에 ?(Nullable, 물음표) 입력.

Nullable

var variable: String? //null 값을 입력할 수 있게됨.

*null 예외를 발생시키고 싶지 않을 경우에는 기본형으로 선언.

(물음표 제외해주면 됨)

함수 파라미터에도 null 허용 설정이 가능.

fun nullParameter(str: String?){ if(str != null){ var length2 = str.length } //파라미터 str에 null이 허용되었기 때문에 함수 내부에서 null 체크를 하기 전에는 str을 사용할 수 없음. }

함수에 리턴 타입에도 null 허용 설정 가능.

fun nullReturn(): String? { return null } //Nullable이 지정되어 있지 않으면 null 값 리턴 안됨.

안전한 호출: ?.

▷Nullable인 변수 다음에 ?.(Safe Call, 물음표와 온점)을 사용하면

해당 변수가 null일 경우 ?. 다음의 메서드나 프로퍼티를 호출하지 않음.

→즉, null 체크를 간결하게 함.

Safe Call

var result = 변수?.프로퍼티 //null일 때, ?. 다음에 나오는 속성이나 명령어를 처리하지 않기 위해 사용

Null 값 대체하기: ?:

?:(Elvis Operator, 물음표와 콜론)을 사용하면

원본변수가 null일 때 넘겨줄 기본값을 설정할 수 있음.

▷safe call이 적용된 프로퍼티 오른쪽에 ?:을 사용하면

null일 경우 ?: 오른쪽의 값이 반환됨.

Elvis Operator

var result = 변수?:0 var result = 변수?.프로퍼티?:0 //null일 때, ?: 다음에 나오는 값을 기본값으로 사용

Chapter3 - (9) 지연 초기화

:Nullable(?) 처리가 남용되는 것을 방지해줌.

lateinit

: 개발을 하다가 클래스 안에서 변수(프로퍼티)만 Nullable로 미리 선언하고,

초기화(생성자 호출)를 나중에 할 경우에 사용하는 키워드.

lateinit var name: String

📍lateinit의 특징

· var로 선언된 클래스의 프로퍼티에만 사용 가능.

· null은 허용되지 않음.

· 기본 자료형 Int, Long, Double, Float 등은 사용할 수 없음.

*lateinit은 변수를 미리 선언만 해 놓은 방식이므로

초기화되지 않은 상태에서 메서드나 프로퍼티를 참조하면

null 예외가 발생해서 앱이 종료될 수 있음.

→변수가 초기화되지 않은 상황이 발생할 수 있다면,

Nullable이나 빈 값으로 초기화하는 것이 좋음.


lazy

: 읽기 전용 변수인 val을 사용하는 지연 초기화.

🪄lateinit vs. lazy

lateinit은 입력된 값을 변경할 수 있음.

lazy는 입력값을 변경할 수 없음.

by lazy 키워드를 변수 뒤에 선언하고, 중괄호에 초기화할 값을 써주면 됨.

val person: Person by lazy { Person() } //이때 person 변수의 타입(Person)은 생략 가능.

📍lazy의 특징

· 선언 시에 초기화 코드를 함께 작성하기 때문에, 따로 초기화할 필요 없음.

· lazy로 선언된 변수가 최초 호출되는 시점에 by lazy{ } 안에 넣은 값으로 초기화됨.


Chapter3 - (10) 스코프 함수

스코프 함수(Scope Functions)

: 코드를 축약해서 표현할 수 있도록 도와주는 함수.

영역 함수

▷스코프 함수에는

run, let, apply, also, with

가 있음.

▷lateinit과 함께 Safe Call 남용을 막아주는 역할을 함.

run과 let

run 스코프 함수 안에서 호출한 대상을 this로 사용할 수 있음.

▷클래스 내부의 함수를 사용하는 것과 동일한 효과이기 때문에 this는 생략하고

메서드나 프로퍼티를 바로 사용할 수 있음.

var list = mutableListOf("Scope", "Function") list.run { val listSize = size //this 생략. println("리스트의 길이 run = $listSize") }

let 함수 영역 안에서 호출한 대상을 it으로 사용할 수 있음.

▷it은 생략할 수 없지만 target 등 다른 이름으로 바꿀 수 있음.

var list = mutableListOf("Scope", "Function") list.let { val listSize = it.size println("리스트의 길이 run = $listSize") }

this와 it으로 구분하기

1) this로 사용되는 스코프 함수

: run, apply, with

var list = mutableListOf("Scope", "Function") list.apply { val IistSize = size PrintIn("리스트의 길이 apply = $listSize") with (list) { val IistSize = size PrintIn("리스트의 길이 with = $listSize") } //with은 스코프 함수이지만 확장 함수가 아님. 그러므로 일반 함수처럼 사용됨.

*호출 대상이 null일 경우에는 with보다는

apply나 run을 사용하는 것이 더 효율적.

2) it으로 사용되는 스코프 함수

: let, also

Var list = m∪tableListOf("Scope", "Function") list.let { target -> // it을 target 등과 같이 다른 이름으로 변경 가능. val IistSize = target.size // target으로 변경했기 때문에 멤버 접근은 target.속성 PrintIn("리스트의 길이 let = SlistSize"} list.also { val IistSize = it.size val IistSize = target.size PrintIn("리스트의 길이 let = SlistSize") PrintIn("리스트의 길이 also = $listSize") }

반환값으로 구분하기

1) 호출 대상인 this 자체를 반환하는 스코프 함수

: apply, also

▷apply와 also를 사용하면 스코프 함수 안에서 코드가 모두 완료된 후

자기 자신을 반환해줌.

2) 마지막 실행 코드를 반환하는 스코프 함수

: let, run, with

▷let, run, with를 사용하면 자기 자신이 아닌 스코프의 마지막 코드를 반환해줌.