티스토리 뷰

클래스

  • 코틀린의 클래스는 자바와 매우 유사하지만 자바에는 없는 몇몇 기능들을 제공한다.

데이터 클래스

  • 자료를 구성하는 프로퍼티만 선언하면 컴파일러가 equals(), hashCode(), toString() 함수를 자동으로 생성해준다.

  • 자바에서 흔히 생성하였던 VO 클래스를 코틀린에서는 프로퍼티만 선언함으로써 구현할 수 있다.

          // 데이터 클래스 선언
          data class Person(val name: String, val address: String)
    
          private val shin1 = Person("Shin Kwanghee", "Seoul")
          private val shin2 = Person("Shin Kwanghee", "Seoul")
          private val shin3 = Person("Shin", "Seoul")
    
          private val kim1 = Person("Kim Minyoung", "Cheongju")
    
          @Test
          fun personDataTest() {
              println("shin1 == shin2 : ${shin1 == shin2}")
              println("shin hashCode = ${shin1.hashCode()} , ${shin2.hashCode()} , ${shin3.hashCode()}")
              println("kim.toString = ${kim1.toString()}")
          }
    
      ## 결과
      shin1 == shin2 : true
      shin hashCode = 1455041812 , 1455041812 159626362
      kim.toString = Person(name=Kim Minyoung, address=Cheongju)

생성자

  • 자바와 동일하게 필요한 변수에 따라 생성자를 여러개 생성할 수 있다.

    class ClassTest {
    
        private val name: String
        private val color: String
    
        constructor(name: String) {
            this.name = name
            this.color = "#000000"
        }
    
        constructor(name: String, color: String) {
            this.name = name
            this.color = color
        }
    
    }
    • 커스텀뷰를 생성할떄는 @JvmOverloads 어노테이션을 통해 생성자를 모두 작성하지 않아도 된다
    • @JvmOverloads는 생성자의 파라메터가 기본값으로 대체하도록 컴파일러에서 지시한다
    class CustomView : View {
        constructor(context: Context) : this(context, null)
        constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
        constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
            context,
            attrs,
            defStyleAttr
        ) {
            // ...
        }
    
    }
    
    class CustomView2 @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
        // ...
    }

초기화 블록을 가진 주 생성자

  • 생성자는 기본적으로 함수를 표현하는 기능이기 때문에 변수 초기화 이외에도 특정한 작업을 init 블록을 통해 수행할 수 있다.

    class CustomView2 @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
        init {
            // ...
        }
    }
    • Gson을 사용할 때는 모든 프로퍼티에 초기화 디폴트값을 지정해주어야 init 블록을 호출한다
        data class Documents(
    //        @SerializedName("collection")
    //        val collection: String,
    //
    //        @SerializedName("datetime")
    //        val dateTime: String,
    //
    //        @SerializedName("display_sitename")
    //        val displaySiteName: String,
    //
    //        @SerializedName("doc_url")
    //        val docUrl: String,
    //
    //        @SerializedName("width")
    //        val width: Int,
    //
    //        @SerializedName("height")
    //        val height: Int,
    //
    //        @SerializedName("image_url")
    //        val imageUrl: String,
    //
    //        @SerializedName("thumbnail_url")
    //        val thumbnailUrl: String
    
            @SerializedName("collection")
            val collection: String = "",
    
            @SerializedName("datetime")
            val dateTime: String = "",
    
            @SerializedName("display_sitename")
            val displaySiteName: String = "",
    
            @SerializedName("doc_url")
            val docUrl: String = "",
    
            @SerializedName("width")
            val width: Int = 0,
    
            @SerializedName("height")
            val height: Int = 0,
    
            @SerializedName("image_url")
            val imageUrl: String = "",
    
            @SerializedName("thumbnail_url")
            val thumbnailUrl: String = ""
        )  {
            init {
                println("=== Init Block ===")
            }
        }

프로퍼티의 사용자 지정 Getter/Setter

  • 기본적으로 프로퍼티에는 내부에 저장된 필드 값을 가져오거나 설정할 수 있도록 Getter/Setter를 내부적으로 구현하고 있다.

  • 사용자 지정 Getter/Setter를 사용하면 프로퍼티의 Getter/Setter의 구현을 원하는대로 변경할 수 있으며, 특정 개체의 값에 따른 다양한 정보를 속성 형태로 제공할 때 유용하다.

  • 사용자 지정 Getter/Setter는 프로퍼티의 선언과 함께 get() 및 set(value)를 사용하여 선언할 수 있다.

     // 형태
     var <propertyName>[: <PropertyType>] [<property_initializer>] 
         [<getter>]
         [<setter>]
    
     // 예시
     data class Person(
         val age: Int
     ) {
         val adult: Boolean
             // 성인 여부를 age 값이 19보다 크거나 같을 경우에만 true로 반환
             get() = age >= 19
    
         var address: String = "a, b, c"
             get() = "주소 = $field"
             set(value) {
                 // 인자로 들어온 문자열의 앞 10 자리만 필드에 저장
                 field = value.substring(0..9)
             }
     }
    
         private val shin = Person(14)
    
         @Test
         fun personWithAgeTest() {
             shin.address = "서울특별시 강서구 화곡"
             println("adult : ${shin.adult} , address : ${shin.address}")
         }
    
     ## 결과
     adult : false , address : 주소 = 서울특별시 강서구 

추상 클래스

  • 자바에서 사용하는 abstract와 같은 개념으로 코틀린 class 앞에 open 키워드를 붙이거나 abstract를 붙여 사용할 수 있다.

    open class Base { 
      open fun f() {}
    }
    
    abstract class Derived : Base() { 
      override abstract fun f()
    }

싱글톤 클래스

  • 싱글톤 : 단 하나의 인스턴스만 생성되도록 제약을 둔 디자인 패턴

  • 코틀린에선 오브젝트(object)를 사용하여 싱글톤을 간편하게 선언 할 수 있다.

     object today {  
       val day = "29"  
       fun getTime() { ... }  
     }  
    
     val dayValue = today.day  
     today.getTime()

enum 클래스

  • enum 클래스에 프로퍼티를 추가할 수 있다.

    enum class Color {  
        RED, BLUE, GREEN  
    }  
    
    enum class Color(val name: String) {  
        RED, BLUE, GREEN  
    }

어노테이션 클래스

  • 어노테이션에 멤버를 가질 수 있다.

  • 자바에서는 멤버수가 하나이면서 멤버이름이 value인 경우에만 바로 값을 대입할 수 있지만, 코틀린은 상관없이 바로 값을 대입할 수 있다.

    annotation class AnnotationExample {  
        val name: String  
    }  
    
    @AnnotationExample("SHIN KWANGHEE")  
    class Example() {  
        ...  
    }
    • 멤버에 기본값을 지정하는 경우 멤버의 기본값을 참조한다.
    annotation class AnnotationExample {  
        val name: String = "SHIN KWANGHEE"  
    }  
    
    @AnnotationExample()  
    class Example() {  
        ...  
    }
    • 코틀린에서 어노테이션을 사용할 수 있는 타입은 자바와 동일

    • 자바 원시 타입에 대응하는 타입(Int, Long 등)

    • 문자열(String)

    • 클래스

    • enum 클래스

    • 멤버가 속한 어노테이션이 아닌 다른 어노테이션 클래스

    • 위에 나열된 타입으로 구성된 배열

    • 코틀린에서는 배열 타입의 멤버가 포함하는 타입에 따라 값을 지정하는 방식이 달라 진다

    • 자바 원시타입에 대응하는 경우 원시타입 전용 배열클래스 사용(IntArray 또는 LongArray 등)

    • 그 외의 타입은 일반배열 사용

annotation class AnnotationExample {  
    val numbers: IntArray,  
    val colors: Array  
}

@AnnotationExample(numbers = intArrayOf(1, 2, 3),  
colors = arrayOf("RED", "BLUE", "GREEN"))  
class Example() {  
    ...  
}
  • 부가 정보를 표시하기 위해 메타 어노테이션을 지정할 수 있다.
@Target(AnnotationTarget.TYPE) @Retention(AnnotationRetention.SOURCE)  
@Repeatable  
@MustBeDocumented  
annotation class Example
  • 코틀린에서 주 생성자에 어노테이션을 사용하는 경우 constructor 키워드 앞에 넣는다.
// 주 생성자 앞에 어노테이션을 추가  
class Example @AnnotationExample constructor(val name: String) {  
...  
}
  • 프로퍼티와 생성자, 함수의 파라메타에도 어노테이션을 지정할 수 있다.

중첩 클래스

  • 정적 중첩 클래스(static nested class)의 경우 자바는 static 키워드를 붙이지만, 코틀린은 별도의 키워드를 붙이지 않는다.

  • 비 정적 중첩 클래스(non-static nested class)의 경우 코틀린에는 inner 키워드를 추가해야 한다.

class Outer {

// 키워드가 없으면 정적 중첩 클래스  
class StaticNested {  
    ...  
}  

// 비 중첩 클래스  
inner class NonStaticNested() {  
    ...  
}  

// 정적 중첩 클래스 : Outer클래스의 인스턴스 생성 없이 인스턴스를 생성 가능  
val staticInstance = Outer.StaticNested()  

// 비 중첩 클래스 : Outer 클래스의 인스턴스를 생성해야 인스턴스를 생성 가능  
val nonStaticInstance = Outer().NonStaticNested()  

}

한정 클래스

  • enum 클래스의 개념을 확장

  • 종류별로 하나의 인스턴스만 생성되어있는 enum 클래스와 달리 한정 클래스는 여러 개의 인스턴스를 생성할 수 있다.

  • enum 클래스의 특징을 그대로 가지고 있기에, 이를 상속하는 클래스는 한정 클래스로 정의되는 여러 종류 중 하나로 취급된다.

  • 한정 클래스 사용 예시

    • MobileApp : 한정 클래스

    • Android, IOS : 이를 상속하는 클래스

    ```
    sealed class MobileApp(val os: String) {  
      class Android(os: String, val packageName: String) : MobileApp(os)  
      class IOS(os: String, val bundleId: String) : MobileApp(os)  
    }
    ```
  • 한정 클래스는 일반적으로 클래스 내에 중첩하여 선언하지만, 같은 파일내에서 정의를 한다면 클래스 외부에서도 선언할 수 있다.

  • 한정 클래스는 한정 클래스로 정의된 클래스의 종류에 따라 다른 작업을 처리해야 할 때 유용하다.

fun whoami(app: MobileApp) = when (app) {
is MobileApp.Android -> println("${app.os} / ${app.packageName}")
is MobileApp.IOS -> println("${app.os} / ${app.bundleId}")
// 모든 경우를 처리했으므로 else를 쓰지 않아도 됩니다.
}

  • 한정 클래스의 종류를 모두 구현하지 않은 경우 else 절을 추가해야 한다.

  • else 문을 추가하지 않으려면 한정 클래스의 종류를 모두 구현해야 하며, else 문 또는 종류를 모두 구현하지 않은 경우 컴파일 에러가 발생한다.

lateinit을 통한 프로퍼티 지연 초기화

  • 프로퍼티 선언 시점이나 생성자 호출 시점에 값을 할당할 수 없는 경우 'lateinit' 키워드를 사용하여 나중에 할당될 값임을 명시한다

  • 'lateinit' 키워드는 var 프로퍼티에서만 사용 가능

  • 'lateinit' 키워드를 사용한 프로퍼티를 초기화 없이 사용하려 한다면 Uninitialized PropertyAccessException 예외가 발생한다.

    class Person {
       val name : String? = null // val 프로퍼티는 항상 선언과 함께 값을 할당해야 한다.
       lateinit var address : String? // 선언 시점에 값을 할당하지 않아도 컴파일 에러가 발생하지 않는다.
    }

lazy를 통한 프로퍼티 지연 초기화

  • lateinit을 통해 프로퍼티를 선언할때는 var로 선언해야 되기때문에 언제든 값이 변경될 수 있는 단점이 존재함

  • 읽기 전용의 val로 선언한 객체나 프로퍼티를 나중에 초기화 하기 위해 lazy를 사용

     private val viewModel : DetailViewModel by lazy { 
            DetailViewModel(ImageLoadingInterface(this, cacheSize))
        }

클래스 및 인터페이스

  • 코틀린에서 접근 제한자를 지정하지 않는 경우 public

    ## Java
    public class Example {  
        ...     
    }
    
    ## Kotlin
    class Example {
        ...
    }
    • 클래스와 인터페이스는 본체 없이 선언하는것이 가능
    class MainActivity  
    interface OnUpdateView
  • 자바와는 다르게 클래스의 인스턴스를 생성할때 new 키워드를 사용하지 않는다

      ## Java
      Today today = new Today();  
      Message message = new Message("Hello World");
    
      ## kotlin
      val foo : Today = Today()   // new 키워드 생략  
      val bar : Message = Message("Hello World")  // 인자 하나를 받는 생성자로 인스턴스 생성
  • 추상클래스 선언

      ## Java
      abstract class FileChoose {  
          public abstract void getVideo();  
      }  
    
      // 추상 클래스의 인스턴스 생성  
      // 클래스 생성과 동일하게 new 사용  
      FileChoose choose = new FileChoose() {  
          @Override  
        public void getVideo() {  
    
          }  
      };
    
      ## Koltin 
      abstract class FileChoose {  
          abstract fun getAudio()  
      }  
    
      // 추상 클래스의 인스턴스 생성  
      // object: [생성자] 형태로 선언  
      val choose = object: FileChoose() {  
    
          override fun bar() {  
              // 함수 구현  
        }  
      }
    • 인터페이스 선언

      ## Java
      
      public interface OnDownloadCompleteListener {  
          void onSuccess();  
       void onFailure();  
      }  
      
      // 인터페이스 인스턴스 생성  
      // 클래스 생성과 동일하게 new 사용  
      OnDownloadCompleteListener listener = new OnDownloadCompleteListener() {  
          @Override  
        public void onSuccess() {  
              ...  
          }  
      
          @Override  
        public void onFailure() {  
              ...  
          }  
      };
      
      ## Kotlin
      interface OnDownloadCompleteListener {  
          fun onSuccess()  
          fun onFailure()  
      }  
      
      // 인터페이스의 인스턴스 생성  
      // object: [인터페이스 이름] 형태로 선언  
      val listener : OnDownloadCompleteListener {  
          override fun onSuccess() {  
              ...  
          }  
      
          override fun onFailure() {  
              ...  
          }  
      }

접근 제한자

  • 접근 제한자가 없으면 기본적으로 public으로 간주한다.

  • internal 접근 제한자는 동일한 모듈 내에 있는 클래스들로의 접근을 제한

    • internal 접근 제한자가 제한하는 모듈의 범위
      • IntelliJ IDEA 모듈
      • Maven / Gradle 모듈
      • 하나의 Ant 태스크 내에서 함께 컴파일 되는 파일들
    ## Java
    public class Example {  
        public int a = 1;  
        protected int b = 2;  
        private int c = 3;  
        int d = 4; // 패키지 단위 제한자(별도 표기 없음) 
    }
    
    ## Kotlin
    class Example {  
       var a = 1 // 접근 제한자가 없으면 public  
       protected var b = 2  
       private val c = 3  
       internal var d = 4
    }

생성자

  • 생성자에 인자가 필요한 경우 클래스 선언할 때 추가 가능

  • 코틀린에서는 이를 주 생성자(primary constructor)라고 한다

    ## Java
    public class Example {  
        public Example(String today) {  
            Log.d(TAG, "Today : " + today)  
        }  
    }
    ## Kotlin
    class Example(today : String) {  
        init {  
            Log.d(TAG, "Today : $today")  
        }  
    }
    • 코틀린에서는 생성자의 인자를 바로 클래스 내부의 프로퍼티에 값을 할당할 수 있다.
    class Example(today : String, time : String)
    • 주 생성자 외의 다른 형태의 생성자가 필요한 경우 constructor 키워드를 사용해서 추가 생성자를 선언할 수 있다.
    class Example2(today : String, time : String) {  
      // today 값만 인자로 받는 추가 생성자  
      constructor(today: String) : this(today, "")  
    
      // 두 인자의 값을 모두 0으로 지정하는 추가 생성자  
      constructor() : this("", "")  
    }
    
    • 코틀린에서 추가 생성자를 정의하는 경우 주 생성자를 반드시 호출해야 한다.
    • 추가 생성자에서 인자와 프로퍼티를 함께 선언할 수 없다
    • 생성자의 가시성을 변경하려면 constructor 키워드 앞에 접근 제한자를 추가한다.
    // 주 생성자의 가시성을 internal로 변경하는 경우 constructor 키워드를 표기해야 한다.  
    class Example internal constructor(today : String, time : String) {  
        private constructor(today: String) : this(today, "")  
    
        // 접근제한자를 지정하지 않았으므로 public  
        constructor() : this("", "")  
    }

함수

  • 특별한 타입을 반환하지 않는 함수는 Unit 타입 반환

  • Unit 타입은 반환 표시를 생략할 수 있다.

    class Example {  
      // 아무것도 반환하지 않는 함수  
      fun temp() : Unit {  
            ...  
      }
    
      // 반환 타입 생략
      fun data() {
          ...
      }
    
      // 정수값을 반환  
      private fun getPosition(position : Int) : Int {  
            return position  
      }  
    }

상속 및 인터페이스 구현

  • 콜론(:) 뒤에 상속한 클래스나 구현한 인터페이스를 표기

  • 클래스를 상속하는 경우 반드시 부모 클래스의 생성자를 호출해야 한다.

     class MainActivity : AppCompatActivity(), View.OnClickListener {
         ...
     }
  • 부모 클래스의 생성자가 여러 형태인 경우, 별도의 생성자 선언에서 부모 클래스의 생성자를 호출하도록 구현할 수 있다.

  • 부모 클래스의 생성자는 자바와 동일하게 super 키워드를 사용하여 호출

  • 생성자가 여럿인 경우 this 키워드를 사용하여 자기 사신의 생성자를 호출할 수 있다.

  • 코틀린에서는 상속받거나 구현된 함수 앞에 무조건 override 키워드를 붙이도록 강제한다.

    class CustomView : View {  
      constructor(context: Context) : this(context, null)
    
      constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {  
            // 뷰 초기화  
      }  
    }
  • 코틀린에서는 open 키워드를 붙인 클래스나 함수가 아니라면 상속하거나 함수를 재정의할 수 없다.

     open class BaseActivity : AppCompatActivity() {  
         open val message = "Hello World"  
    
         ...
     }  
    
     class MainActivity : BaseActivity() {  
         override fun onCreate(savedInstanceState: Bundle?) {  
             super.onCreate(savedInstanceState)  
             setContentView(R.layout.activity_main)  
    
             Log.d(TAG, "Message : $message")  
         }
     }

this

  • 해당 키워드를 사용한 클래스 자신을 지칭할 때 사용

  • 키워드를 사용하는 위치에 따라 this가 의미하는 클래스가 달라질 수 있어 this@{클래스 이름} 형태로 표기할 수 있다.

    class MainActivity : BaseActivity(), View.OnClickListener {  
        override fun onCreate(savedInstanceState: Bundle?) {  
            super.onCreate(savedInstanceState)  
            setContentView(R.layout.activity_main)  
    
            button.setOnClickListener(this)  
        }  
    
        override fun onClick(v: View?) {  
            Toast.makeText(this@MainActivity, "Hello World", Toast.LENGTH_SHORT).show()  
        }  
    }

정적 필드 및 메서드

  • 자바의 정적 필드와 메소드를 static 으로 선언할 수 있지만, 코틀린에서는 이를 지원하지 않는다.

  • 일반적인 경우에는 클래스 내에 선언한 정적 필드 및 메서드를 패키지 단위로 선언할 수 있다.

    const val name = "신광희"  
    fun getName() {}  
    
    class Example {  
        fun data() { }  
    }
    • 패키지 단위로 선언한 값이나 함수는 패키지에 종속되므로 import문에서도 {패키지명}.{값 또는 이름}을 사용한다.
    import com.example.name 
    import com.example.getName
    
    ... 
    • 패키지 단위의 함수는 특정 클래스에 속해있지 않으므로, 클래스 내 private로 선언된 멤버에 접근해야 하는 팩토리 메서드(factory method)는 패키지 단위 함수로 구현 할 수 없다.
    • 동반 객체(companion object)를 통해 클래스 내의모든 멤버에 접근할 수 있으면서 인스턴스 생성을 하지 않을 수 있다.
    • 동반 객체 : 클래스 별로 하나의 클래스의 인스턴스 생성 없이 사용할 수 있는 오브젝트를 정의할 수 있는데 이를 동반 객체라고 한다.
    class MainActivity : BaseActivity(), View.OnClickListener {  
        ...
    
        companion object {  
            fun loadMessage(message : String) : String {  
                ...  
            }  
        }  
    }
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함