(kotlin) class and design

박용석·2023년 7월 11일
0
post-custom-banner
class 클래스명 {
    var 변수
    fun 함수() {
        //코드
    }
}

다음은 문자열을 저장할 수 있는 String 클래스의 코드를 함축해서 보여주는 것

class String{
    var length: Int
    fun plus(other: Any){
        // code
    }
    fun compareTo(other: String){
        // code
    }
}

중괄호 {}를 스코프(Scope)라고 하는데, 클래스에서 사용하면 클래스 스코프라고 한다.
몇몇 예외는 존재하지만 대부분의 코드는 클래스 스코프 안에 작성된다.
작성된 클래스를 사용하기 위해서는 생성자라고 불리는 함수가 호출되어야 하는데,
Kotlin은 프라이머리(Primary)와 세컨더리(Secondary) 2개의 생성자를 제공한다.

프라이머리 생성자

class Person 프라이머리 생성자() {
}

프라이머리 생성자(Primary Constructor)는 마치 클래스의 헤더처럼 사용할 수 있으며
constructor 키워드를 사용해서 정의하는데 조건에 따라 생략할 수 있다.
프라이머리 생성자도 결국은 함수이기 때문에 파라미터를 사용할 수 있다.

class Person constructor(value: String){
    // code
}

생성자에 접근 제한자나 다른 옵션이 없다면 constructor 키워드를 생략할 수 있다.

class Person(value: String){
    // code
}

프라이머리 생성자는 마치 헤더처럼 class 키워드와 같은 위치에 작성된다
클래스의 생성자가 호출되면 init 블록의 코드가 실행되고, init 블록에서는 생성자를
통해 넘어온 파라미터에 접근할 수 있다.

class Person(value: String){
    init{
        Log.d("class", "생성자로부터 전달받은 값은 ${value}입니다.")
    }
}

init 초기화 작업이 필요하지 않다면 init 블록을 작성하지 않아도 된다.
대신 파라미터로 전달된 값을 사용하기 위해서는 파라미터 앞에 변수 키워드인 val을
붙여주면 클래스 스코프 전체에서 해당 파라미터를 사용할 수 있다.

class Person(val value: String) {
    fun process() {
        print(value)
    }
}

생성자 파라미터 앞에 var도 사용할 수 있지만, 읽기 전용인 val을 사용하는 것을 권장

세컨더리 생성자
세컨더리 생성자(Secondary Constructor)는 constructor 키워드를 마치 함수처럼 클래스
스코프 안에 직접 작성할 수 있다.
다음과 같이 init 블록을 작성하지 않고 constructor 다음에 괄호를 붙여서 코드를 작성

class Person{
    constructor (value: string) {
	Log.d("class", "생성자로부터 전달받은 값은 ${value}입니다.")
    }
}

세컨더리 생성자는 파라미터의 개수, 또는 파라미터의 타입이 다르다면 여러 개를
중복해서 만들 수 있다.

class Kotlin{
    constructor (value: String){
	Log.d("class", "생성자로부터 전달받은 값은 ${value}입니다.")
    }
    constructor (value: Int){
	Log.d("class", "생성자로부터 전달받은 값은 ${value}입니다.")
    }
    constructor (value1: Int, value2: String){
	Log.d("class", "생성자로부터 전달받은 값은 ${value1}, ${value2}입니다.")
    }
}

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

class Student {  // 생성자를 작성하지 않아도 기본 생성자가 동작
    init {
	// 기본 생성자가 없더라도 초기화가 필요하면 여기에 코드를 작성
    }
}

클래스의 사용
클래스의 이름에 괄호를 붙여서 클래스의 생성자를 호출한다.
클래스명()
constructor 키워드를 호출하지는 않는다.
아무런 파라미터 없이 클래스명에 괄호를 붙여주면 생성자가 호출되면서
init 블록 안의 코드가 자동으로 실행된다.
세컨더리 생성자의 경우 init 블록이 먼저 실행되고, constructor 블록 안의
코드가 그다음 실행된다.
다음과 같이 Kotlin 클래스의 생성자를 호출한 후 생성되는 것을 인스턴스(Instance)라
하는데 생성된 인스턴스는 변수에 담아둘 수 있다.
var kotlin = Kotlin();

클래스와 인스턴스의 관계를 비교할 때 가장 많이 사용되는 예가 붕어빵 틀과 붕어빵이다.
여기서 붕어빵틀이 클래스에 해당되고 계속 만들 수 있는 붕어빵이 인스턴스와 같다.
생성자에 파라미터가 있으면 값을 입력해서 호출해야 한다.

클래스의 변수 > 멤버 함수 > ----- 프로퍼티(Property)
클래스의 함수 > 멤버 함수 > ----- 메서드(Method)

클래스 안에 정의된 변수는 프로퍼티라고 하지만 함수 안에 정의된 변수는
프로퍼티가 아닌 변수(지역 변수)라고 한다.

class 클래스명 {
    var 변수A  // 프로퍼티: 함수 밖에 있어야 함.
    fun 함수(){
        var 변수B  // 변수(또는 지역변수): 함수 안에 있어야 함.
    }
}

프로퍼티와 메서드를 사용하기 위해서 다음과 같이 프로퍼티 1개와 메서드 1개를 갖는
클래스를 만든다.

class pig{
    var name: String = "pinky"
    fun printName(){
        Log.d("class", "Pig의 이름은 ${name}입니다.")
    }
}

위에서 정의한 클래스를 생성자로 다음과 같이 인스턴스화해서 변수에 담는다

var pig = Pig()

인스턴스가 담긴 변수명 다음에 도트 연산자 (.)를 붙여서 프로퍼티와 메서드를 사용

pig.name = "Pooh"
pig.printName()

오브젝트
오브젝트(Object)를 사용하면 클래스 생성자로 인스턴스화 하지 않아도 블록 안의
프로퍼티와 메서드를 호출해서 사용할 수 있다. (Java를 알고 있다면 static과 같은 역할)

object Pig{
    var name: String = "pinky"
    fun printName() {
        Log.d("class", "Pig의 이름은 ${name}이다"
    }
}

object 코드 블록 안의 프로퍼티와 메서드는 클래스명에 도트 연산자를 붙여서 생성자
없이 직접 호출할 수 있다.

Pig.name = "Mikey"
Pig.printName()

주의할 점은 클래스명을 그대로 사용하기 때문에 호출하는 클래스명의 첫 글자가 대문자
object는 클래스와 다르게 앱 전체에 1개만 생성된다.

컴패니언 오브젝트(companion object)
일반 클래스에 object 기능을 추가하기 위해서 사용
위에서 작성한 Pig 코드를 다음과 같이 companion object 블록으로 감싸주면
생성 과정 없이 오브젝트처럼 사용할 수 있다.

class Pig{
    companion object{
        var name: String = "None"
        fun printName(){
            Log.d("class", "Pig의 이름은 ${name}이다.")
        }
    }
    fun walk(){
        Log.d("class", "Pig가 걸어간다.")
    }
}

위 Pig는 클래스로 선언했기 때문에 일반 함수인 walk()는 생성자인 Pig()를 호출한
다음 변수에 저장한 후에 사용할 수 있다.

// companion object 안의 코드 사용하기
Pig.name = "Linda"
Pig.printName() 

// companion object 밖의 코드 사용하기
val cutePig = Pig()
cutePig.walk() 

데이터 클래스
Kotlin은 간단한 값의 저장 용도로 데이터 클래스(data class)를 제공한다
data class 클래스명(val 파라미터1: 타입, var 파라미터2: 타입)

데이터 클래스를 정의할 때 class 앞에 data 키워드를 사용해야 하고, 생성자 파라미터
앞에 입력하는 var(또는 val) 키워드는 생략할 수 없다.
생성하는 코드는 일반 클래스와 동일하게 작성한다.

// 정의 - 주로 코드 블록(클래스 스코프)을 사용하지 않고 간단하게 작성한다
data class UserData(val name: String, var age: Int)
// 생성 - 일반 class의 생성자 함수를 호출하는 것과 동일하다
var userData = UserData("michael", 21)
// name은 val로 선언되었기 때문에 변경 불가능하다
userData.name = "Sundy" // (x)
userData.age = 18 // (o)

toString() 메서드와 copy() 메서드
일반 클래스에서 toString() 메서드를 호출하면 인스턴스의 주소 값을 반환하지만,
데이터 클래스는 값을 반환하기 때문에 실제 값을 모니터링할 때 좋다

Log.d("DataClass", "DataUser는 ${dataUser.toString()}")
// DataUser는 DataUser(name=Michael, age=21)

또 copy() 메서드로 간단하게 값을 복사할 수 있다.

var newData = dataUser.copy()

일반 클래스처럼 사용하기
일반 클래스와 동일하게 생성자를 호출하면 init 블록이 동작하고 메서드도 사용할 수 있다

data class UserData(var name: String, var age: Int){
    init{
        Log.d("UserData", "initialized")
    }
    fun prcess(){
        // 클래스와 동일하게 메서드 사용이 가능
    }
} // 클래스가 생성되면 "initialized"가 출력됨

이처럼 클래스와 사용법이 동일하지만 주로 네트워크를 통해 데이터를 주고받거나, 혹은
로컬 앱의 데이터베이스에서 데이터를 다루기 위한 용도로 사용하는 것이 데이터 클래스

클래스의 상속과 확장
Kotlin은 클래스의 재사용을 위해 상속을 지원한다.
상속은 클래스를 생성한 후 도트 연산자(.)를 통해 메서드와 프로퍼티를 사용하는 것처럼
클래스의 자원을 사용하는 또 다른 방법이다.
상속을 사용하면 부모 클래스의 메서드와 프로퍼티를 마치 내 클래스의 일부처럼 사용

상속을 사용하는 이유
안드로이드에는 Activity라는 클래스가 미리 만들어져 있으며, 이 Activity 클래스 내부에는
글자를 쓰는 기능, 그림을 그리는 기능, 화면에 새로운 창을 보여주는 기능이 미리 정의
상속이 있기에 이러한 기능을 직접 구현하지 않고 Activity 클래스를 상속받아 약간의
코드만 추가하면 앱에 필요한 기능을 추가할 수 있다.

class Activity {
 fun drawText()
 fun draw()
 fun showWindow()
 // ...
}
class MainActivity: Activity() {
    fun onCreate(){
        draw("새 그림")  // 미리 만들어진 기능(draw)을 호출만으로 사용
    }
}

상속은 코드를 재사용하는 측면도 있지만 코드를 체계적으로 관리할 수 있기 때문에
규모가 큰 프로젝트도 효과적으로 설계할 수 있다.

클래스의 상속
상속 대상이 되는 부모 클래스는 open 키워드로 만들어야만 자식 클래스에서 사용가능
만약 open 키워드로 열려 있지 않으면 상속할 수 없다.
상속을 받을 자식 클래스에서는 콜론(:)을 이용해서 상속할 부모 클래스를 지정한다.
상속은 부모의 인스턴스를 자식이 갖는 과정이기 때문에 부모 클래스명 다음에 괄호를
입력해서 꼭 부모의 생성자를 호출해야 한다.

open class 상속될 부모 클래스 {
    // 코드
}
class 자식 클래스: 부모 클래스() {
    // 코드 
}

생성자 파라미터가 있는 클래스의 상속
상속될 부모 클래스의 생성자에 파라미터가 있다면 자식 클래스의 생성자를 통해 값을
전달할 수 있습니다.

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

부모클래스에 세컨더리 생성자가 있다면, 역시 자식 클래스의 세컨더리 생성자에서 super
키워드로 부모 클래스에 전달할 수 있다.

다음은 안드로이드의 View 클래스를 상속받는 예제이다. 자식 클래스에 세컨더리 생성자만
있을 경우 상속되는 클래스 이름 다음에 괄호가 생략됨

class CustomView: View{ // 부모 클래스명 다음 괄호를 생략했다.
    constructor(ctx: Context): super(ctx)
    constructor(ctx: Context, attrs: AttributeSet): super(ctx, attrs)
}

부모 클래스의 프로퍼티와 메서드 사용하기
부모 클래스에서 정의된 프로퍼티와 메서드를 내 것처럼 사용할 수 있다.

open class Parent{
    var hello: String = "안녕하세요"
    fun sayHello(){
        Log.d("inheritance", "${hello}")
    }
}
class Child: Parent(){
    fun myHello() {
        hello = "Hello!"
        sayHello()
    }
}

위 코드에서 Child에는 hello라는 프로퍼티와 sayHello라는 메서드가 없지만 myHello()
메서드를 실행하면 로그에 "Hello!"가 출력

프로퍼티와 메서드의 재정의: 오버라이드
상속받은 부모 클래스의 프로퍼티와 메서드 중에 자식 클래스에서는 다른 용도로
사용해야 하는 경우가 있다.
앞의 예제에서 Parent 클래스의 메서드를 sayHello로, Child 클래스의 메서드를 myHello
라 했는데 이런 경우가 오버라이드(Override)가 필요한 대표적인 경우이다.
오버라이드로 Child클래스의 메서드도 sayhello라고 하는 것이 의미상 더 적합하다
이처럼 동일한 이름의 메서드나 프로퍼티를 사용할 필요가 있을 경우에 override키워드를
사용해서 재정의할 수 있다.
오버라이드할 때는 프로퍼티나 메서드도 클래스처럼 앞에 open을 붙여서 상속할 준비가
되어 있어야 한다.

메서드 오버라이드
상속할 메서드 앞에 open 키워드를 붙이면 오버라이드할 수 있지만, open 키워드가 없는
메서드는 오버라이드 할 수 없다.

open class BaseClass {
    open fun opened(){
    }
    fun notOpened(){
    }
}
class childClass: BaseClass() {
    override fun opened(){
    }
    override fun notOpened(){ 
        // notOpened 메서드는 open 키워드가 없으므로 잘못된 사용이다.
    }
}

클래스의 세컨더리 생성자를 여러 개 중복해서 사용할 수 있는 것도 오버라이딩이
가능하기 때문이다.

프로퍼티 오버라이드
메서드 오버라이드처럼 프로퍼티 역시 open으로 열려 있어야만 오버라이드할 수 있다.

open class BaseClass2 {
    open var opened: String = "I am"
}
class childClass2: BaseClass2() {
    override var opened: String = "You are"
}

익스텐션
Kotlin은 클래스, 메서드, 프로퍼티에 대해 익스텐션(Extension)을 지원한다.
이미 만들어져 있는 클래스에 다음과 같은 형태로 메서드를 추가할 수 있다.

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

상속이 미리 만들어져 있는 클래스를 가져다 쓰는 개념이라면 익스텐션은 미리
만들어져 있는 클래스에 메서드를 넣는 개념이다.
자신이 만든 클래스에 사용하기보다는 누군가 작성해둔, 이미 컴파일되어 있는 클래스에
메서드를 추가하기 위한 용도로 사용하는 것이 좋다.
익스텐션을 사용한다고 해서 실제 클래스의 코드가 변경되는 것은 아니며 단지 실행 시에
도트 연산자로 호출해서 사용할 수 있도록 해준다.
특별한 경우를 제외하고는 거의 메서드 확장 용도로 사용된다.

다음 예제는 기본 클래스인 String에 plus 메서드를 확장하는 전체 코드이다. test 메서드
안에 선언한 original에 문자열을 입력했기 때문에 original은 String의 익스텐션 메서드인
plus를 호출해서 사용할 수 있다.

package net.flow9.thisiskotlin.basicsyntax

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        testStringExtension()
    }

    // String 익스텐션 테스트 하기
    fun testStringExtension() {
        var original = "Hello"
        var added = " Guys~"
        // plus 함수를 사용해서 문자열을 더할 수 있다.
        Log.d("Extension", " added를 더한값은 ${original.plus(added)}입니다")
    }
}
fun String.plus(word: String): String {
    return this + word
}

/** [로그캣 출력 내용]
added를 더한 값은 Hello Guys~입니다.
*/

추상화
프로그래밍을 하기 전 개념 설계를 하는 단계에서는 클래스의 이름과 클래스 안에 있음
직한 기능을 유추해서 메서드 이름으로 먼저 나열한다.
이 때 명확한 코드는 설계 단계에서 메서드 블록 안에 직접 코드를 작성하는데,
그렇지 않은 경우에는 구현 단계에서 코드를 작성하도록 메서드의 이름만 작성한다.
이것을 추상화(Abstract)라고 하며 abstract 키워드를 사용해서 명시한다.
구현 단계에서는 추상화된 클래스를 상속받아서 아직 구현되지 않은 부분을 마저 구현

abstract class Design {
    abstract fun drawText()
    abstract fun draw()
    fun showWindow() {
        // code
    }
}

class Implements: Design(){
    fun drawText() {
        // 구현 코드
    }
    fun draw() {
        // 구현 코드
    }
}

다음과 같이 추상화된 Animal 클래스를 만들고 동물이 사용할 것 같은 기능 중에 walk와
move를 설계한다고 가정해보자

abstract class Animal {
    fun walk(){
        Log.d("abstract", "걷습니다.")
    }
    abstract fun move()
}

walk는 명확하게 걸어가는 행위이지만 move는 어떤 동물이냐에 따라 달라질 수 있다.
( 새는 날아가고, 고래는 수영을 한다.)
이렇게 앞으로 상속받을 자식 클래스의 특징에 따라 코드가 결정될 가능성이 있다면
해당 기능도 모두 abstract 키워드로 추상화한다.
그리고 실제 구현 클래스는 이 추상 클래스를 상속받아서 아직 구현되지 않은 추상화되어
있는 기능을 모두 구현해준다.
추상 클래스는 독립적으로 인스턴스화 할 수 없기 때문에 구현 단계가 고려되지 않는다면
잘못된 설계가 될 수 있다.

class Bird: Animal(){
    override fun move(){
        Log.d("abstract", "날아서 이동합니다.")
    }
}

인터페이스
인터페이스(Interface)는 추상화와 비교하면 가장 명확하게 이해할 수 있는데 실행 코드
없이 메서드 이름만 가진 추상 클래스라고 생각해도 무방할 것 같다.
즉, 누군가 설계해 놓은 개념 클래스 중에 실행 코드가 할 줄이라도 있으면 추상화,
코드 없이 메서드 이름만 나열되어 있으면 인터페이스이다.
인터페이스는 상속 관계의 설계보다는 외부 모듈에서 내가 만든 모듈을 사용할 수 있도록
메서드의 이름을 나열해둔 일종의 명세서로 제공된다.
인터페이스는 interface 예약어를 사용해서 정의할 수 있고 인터페이스에 정의된 메서드를
오버라이드해서 구현할 수 있다.
Kotlin은 프로퍼티도 인터페이스 내부에 정의할 수 있는데, 대부분의 객체지향 언어에서는
지원하지 않는다.
추상 클래스와 다르게 class 키워드는 사용되지 않는다.

interface 인터페이스명 {
    var 변수: String
    fun 메서드1()
    fun 메서드2()
}

인터페이스 만들기
interface 예약어로 인터페이스를 정의한다.
Kotlin은 인터페이스 내부에 프로퍼티도 정의할 수 있다.
메서드는 코드 블록 없이 이름만 작성해 놓는다.
인터페이스의 프로퍼티와 메서드 앞에는 abstract 키워드가 생략된 형태이다

interface InterFaceKotlin{
    var variable: String // var 앞에 abstract 키워드가 생략되어 있다.
    fun get()
    fun set()
}

클래스에서 구현하기
인터페이스를 클래스에서 구현할 때는 상속과는 다르게 생성자를 호출하지 않고
인터페이스 이름만 지정해주면 된다.

class KotlinImpl: InterfaceKotlin {
    override var variable: String = "init value"
    override fun get() {
        // code
    }
    override fun set() {
        // code
    }
}

인터페이스를 클래스의 상속 형태가 아닌 소스 코드에서 직접 구현할 때도 있는데,
object 키워드를 사용해서 구현해야 한다.
실제로 안드로이드 프로젝트를 시작하면 자주 사용하는 형태이다.

var kotlinImpl = object: InterfaceKotlin {
    override var variable: String = "init value"
    override fun get() {
        // code
    }
    override fun set() {
        // code
    } 
}

인터페이스는 외부의 다른 모듈을 위한 의사소통 방식을 정의하는 것이다.
혼자 개발하거나 소수의 인원이 하나의 모듈 단위를 개발할 때는 인터페이스를
사용하지 않는 것이 좋다.
인터페이스를 남용하면 코드의 가독성과 구현 효율성이 떨어지기 때문이다.
안드로이드가 제공하는 인터페이스를 자주 사용하는 이유는 안드로이드가 보았을 때
개발자가 만드는 모듈이 외부 모듈이기 때문이다.

접근 제한자
Kotlin에서 정의되는 클래스, 인터페이스, 메서드, 프로퍼티는 모두 접근 제한자
(Visibility Modifiers)를 가질 수 있다.
함수형 언어라는 특성 때문에 코틀린은 기존 객체지향에서 접근 제한자의 기준으로
삼았던 패키지 대신에 모듈 개념이 도입되었다.
internal 접근 제한자로 모듈 간에 접근을 제한할 수 있다.

접근 제한자의 종류
접근 제한자는 서로 다른 파일에게 자신에 대한 접근 권한을 제공하는 것인데 각 변수나
클래스 이름 앞에 아무런 예약어를 붙이지 않았을 대는 기본적으로 public 접근 제한자가
적용된다.
private - 다른 파일에서 접근할 수 없다.
internal - 같은 모듈에 있는 파일만 접근할 수 있다.
protected - private와 같으나 상속 관계에서 자식 클래스가 접근할 수 있다.
public - 제한 없이 모든 파일에서 접근할 수 있다.

Kotlin에서 모듈이란 한 번에 같이 컴파일되는 모든 파일을 말한다.
안드로이드를 예로 든다면 하나의 앱이 하나의 모듈이 될 수 있다.
또한 라이브러리도 하나의 모듈이다.

접근 제한자의 적용
접근 제한자를 붙이면 해당 클래스, 멤버 프로퍼티 또는 메서드에 대한 사용이 제한된다.
다음 코드를 통해서 접근 제한자가 어떻게 작용하는지 알아보자

open class Parent {
    private val privateVal = 1
    protected open val protectedVal = 2
    internal val internalVal = 3
    val defaultVal = 4
}

자식 클래스에서 부모 클래스를 상속받고 테스트한다.

class Child: Parent() {
    fun callVariables() {
        // privateVal은 호출이 안 된다.   (1)
        Log.d("Modifier", "protected 변수의 값은 ${protectedVal}"). // (2)
        Log.d("Modifier", "internal 변수의 값은 ${internalVal}"). // (3)
        Log.d("Modifier", "기본 제한자 변수 defaultVal의 값은 ${defaultVal}"). // (4)
    }
}

privateVal은 private 멤버이기 때문에 접근할 수 없다
protected 멤버 protectedVal은 상속 관계이므로 접근할 수 있다.
internal 멤버 internalVal은 동일한 모듈이므로 접근할 수 있다.
접근 제한자가 없는 멤버 defaultVal에는 public이 적용되어 접근할 수 있다.

상속 관계가 아닌 외부 클래스에서 Parent 클래스를 생성하고 사용해본다.
상속 관계가 아니기 때문에 public과 internal에만 접근할 수 있다.

class Stranger {
    fun callVariables() {
        val parent = Parent()
        Log.d("Modifier", "internal 변수의 값은 ${parent.internalVal}입니다.")
        Log.d("Modifier", "public 변수의 값은 ${parent.defaultVal}입니다.")
    }
}

제네릭
제네릭(Generic)은 입력되는 값의 타입을 자유롭게 사용하기 위한 설계 도구
다음은 자주 사용되는 MutableList 클래스의 원본 코드를 이해하기 쉽게 변형한 코드

public interface MutableList<E> {
    var list: Array<E>
}

클래스명 옆에 라고 되어 있는 부분에 String과 같은 특정 타입이 지정되면 클래스
내부에 선언된 모든 E에 String이 타입으로 지정된다.
결과적으로 var list:Array가 var list:ARray으로 변경되는 것이다.
이렇게 설계된 클래스를 우리는 주로 구현하는 용도로 사용하며 컬렉션이나 배열에서
입력되는 값의 타입을 특정하기 위해 다음과 같이 사용된다.

var list: MutableList<Generic> = mutableListOf("Mon", "Tue", "Wed")

fun testGenerics() {
    // String을 제네릭으로 사용했기 때문에 list 변수에는 문자열만 담을 수 있습니다.
    var list: MutableList<String> = mutableListOf()
    list.add("Mon")
    list.add("Tue")
    list.add("Wed")
    // list.add(35) <- 입력 오류 발생
    // String 타입의 item 변수로 꺼내서 사용할 수 있다.
    for (item in list){
        Log.d("Generic", "list에 입력된 값은 ${item}입니다.")
    }
}

클래스 - 변수와 함수의 모음으로, 연관성 있는 코드를 그룹화하고 이름을 매긴 것
constructor - 클래스를 사용하기 위해서 호출하는 일종의 함수
init - 기본 생성자를 호출하면 실행되는 코드 블록
프로퍼티 - 클래스에 정의된 변수를 프로퍼티 또는 멤버 변수라고 한다.
메서드 - 클래스에 정의된 함수를 메서드 또는 멤버 함수라고 한다.
컴패니언 오브젝트 - 컴패니언 오브젝트의 블록 안에서 변수와 함수를 정의하면
생성자를 통하지 않고 클래스의 멤버들을 사용할 수 있다.
상속 - 코드를 재사용하기 위한 설계 도구이다. 상속 관계에서 자식 클래스는 부모
클래스의 멤버들을 자신의 것처럼 사용할 수 있다.
추상화 - 클래스를 개념 설계하기 위한 도구
인터페이스 - 외부 모듈에 제공하기 위해 메서드 이름을 나열한 명세서이다.
패키지 - 연관성 있는 클래스들을 분류하기 위한 디렉터리 구조이다.
접근 제한자 - 클래스의 멤버에 지정된 접근 제한자에 따라 외부에서 사용 여부가 결정됨
제네릭 - 타입을 특정해서 안정성을 유지하기 위한 설계 도구이다.

profile
슬기로운 개발 활동
post-custom-banner

0개의 댓글