안드로이드

코틀린을 코틀린답게 사용하는 법 - (1)

싀온 2020. 2. 29. 18:48

지난 25기 솝트 앱잼 팀과 함께 개발한 '얼리버디' 개발을 다시 시작한다.

원래는 서버 개발자였는데, 안드로이드로 갈아 탐 ㅋㅋㅋ (미안 정욱, 예지,,)

 

앱 자체는 거의 다 완성 했지만, 디자인 패턴과 아키텍처를 공부하고 적용해서 개발을 하기로 했다.

일단 첫번째 목표는 3월 4일까지 제목에서 쓴 것 대로 '코틀린을 코틀린답게 사용하는 법' 익히기.

 

커리큘럼은 [커니의 코틀린]의 목차를 따라가는 걸로 ! 

  • 2장 자바와 비교해보는 코틀린
  • 3장 자바와는 다른 코틀린의 특징
  • 4장 코틀린 표준 라이브러리
  • 8장 코틀린 안드로이드 익스텐션

 

이번 포스팅에서는,

자바와 비교해보는 코틀린에 대해서 정리해보도록 하겠다.

 

컬렉션

1. 코틀린에서는 컬렉션 내 자료의 수정 가능 여부에 따라 컬렉션의 종류를 구분한다.

  • 가변 타입(mutable): 컬렉션 내 자료 수정 가능
  • 불변 타입(immutable): 컬렉션 내 자료 수정 불가능

2. 코틀린 표준 라이브러리에서 컬렉션을 쉽게 생성하는 함수를 제공한다.

원하는 컬렉션 타입 + Of() 함수로 생성할 수 있는데, 예시는 아래와 같다.

// 자료를 수정할 수 없는 리스트 생성
val immutableList : List<String> = listOf("hello", "hiroo")

// 컴파일 에러: 자료 수정 X
immutableList.add("hi")

// 자료를 수정할 수 있는 리스트 생성
val mutableList : MutableList<String> = arrayListOf("one", "two")

// 자료 수정 O
mutableList.add("three")

3. 코틀린에서는 컬렉션 내 항목에 get, set 메서드를 사용하지 않고 인덱스로 바로 접근할 수 있다.

그리고 맵은 숫자 인덱스 대신 키 값을 넣어 항목에 접근할 수 있다.

val immutableMap : Map<String, Int> = mapOf(Pair("A", 65), Pair("B", 66))

// code = 65
val code = immutableMap("A")
// 컴파일 에러: 자료 수정 X
immutableMap["C"] = 67

val mutableMap : HashMap<String, Int> = hashMapOf(Pair("A", 65), Pair("B", 66))

// 자료 수정 O
mutableMap["C"] = 67

 

💡맵을 생성하는 함수들은 키와 값을 인자로 받기 위해 Pair 클래스를 사용하는데, 이때 코틀린 표준 라이브러리에서 제공하는 to 함수를 사용하면 Pair 형태의 값을 더 편리하게 생성할 수 있다.

val map = mapOf("A" to 65, "B" to 66)

 

 

클래스 및 인터페이스

클래스와 인터페이스의 선언 및 인스턴스 생성

1. 코틀린에서는 자바에서 클래스의 인스턴스를 생성하기 위해 사용한 new 키워드를 사용하지 않는다. 

 

2. 추상 클래스(abstract class)는 자바와 동일한 방법으로 생성하지만, 추상 클래스의 인스턴스를 생성하는 형태는 매우 다르다.

  • 자바 - 클래스 생성과 동일하게 new 사용
  • 코틀린 - object: [생성자] 형태로 선언
// 추상 클래스 선언
abstract class Foo {
	abstract fun bar()
}

// 추상 클래스의 인스턴스 생성
val foo = object: Foo() {
	override fun bar() {
    	// do something
    }
}

 

3. 인터페이스를 선언하고, 인터페이스의 인스턴스를 생성하는 방법은 추상클래스와 거의 똑같다.

단, 추상 클래스에서는 인스턴스 생성시 생성자를 사용하지만, 생성자가 없는 인터페이스는 인터페이스의 이름만 사용한다.

// 인터페이스 선언
interface Bar {
	fun baz()
}

// 인터페이스의 인스턴스 생성
val bar = object : Bar {
	override fun baz() {
    	// do something
    }
}

 

 

프로퍼티(property)

자료를 저장할 수 있는 필드와 이에 상응하는 getter/setter 메서드 함께 제공

  • 프로퍼티의 선언
    • val: 값을 읽을 수만 잇음
    • var: 값을 읽고 쓰는 게 모두 가능
    • lateinit var: 프로퍼티 선언 시점이나 생성자 호출 시점에 값을 할당할 수 없는 경우 lateinit 키워드 사용. 값은 나중에 할당
val name = "양시연" // 값이 할당된 뒤 변경 불가
var age : Int? = null // null만으로는 타입을 추론할 수 없으므로 타입 선언 필요
age = 24 // 변경 가능
lateinit var address : String

 

 

생성자

1. 코틀린은 init 블록을 사용하여 기본 생성자를 대체한다.

 

2. 코틀린에서는 생성자의 인자를 통해 바로 클래스 내부의 프로퍼티에 값을 할당 할 수 있다.

이 경우 생성자의 인자를 통해 프로퍼티 선언을 대신하므로 추가로 프로퍼티를 선언하지 않아도 된다.

class Foo(val a: Int, var b: String)

 

상속 및 인터페이스 구현

코틀린에서는 클래스의 상속과 인터페이스의 구현을 구분하지 않고, 콜론(:) 뒤에 상속한 클래스나 구현한 인터페이스를 표기한다.

클래스를 상속하는 경우 반드시 부모 클래스의 생성자를 호출해야하며, 자바와 동일하게 super 키워드를 사용하여 호출한다.

class MyActivity : AppCompatActivity(), View.OnClickListener {
	
    // AppCompatActivity의 onCreate() 메서드 상속
    override fun onCreate(savedInstanceState: Bundle?){
    	super.onCreate(savedInstanceState)
    }
    
    // View.OnClickListener 인터페이스 구현
    override fun onClick(v: View){
    
    }
}

 

 

정적 필드 및 메서드

코틀린에서는 자바의 정적 필드 및 메서드를 패키지 단위로 선언할 수 있다.

하지만, 패키지 단위 함수는 특정 클래스에 속해있지 않으므로, 클래스 내 private로 선언된 멤버에 접근해야하는 팩토리 메서드(factory method)는 패키지 단위 함수로 구현할 수 없다. 이 경우 동반 객체(companion object)를 사용한다.

 

동반 객체(companion object)란?

코틀린은 클래스 내에 정적 필드나 정적 함수를 둘 수 없는 대신에 클래스 별로 하나씩 클래스의 인스턴스 생성 없이 사용할 수 있는 오브젝트(object)를 정의할 수 있는데, 이를 동반 객체(companion object)라고 한다.

class User private constructor(val name: String, val registerTime: Long) {
	
    companion object {
    	// companion object는 클래스 내부에 존재하므로 private으로 선언된 생성자에 접근 가능
        fun create(name: String) : User {
        	return User(naem, System.currentTimeMillis())
        }
    }
}
          

 

 

💡정적 필드 및 메서드는 왜 필요한가?

클래스 내에 상수를 정의하거나 인스턴스 생성 없이 메서드를 호출하고 싶을 때 사용

 

🙋🏻‍♀️내가 companion object를 쓰는 법

ViewPager로 Fragment들을 붙일 때 가끔 특정 값을 Fragment에 넘겨줘야 할 때가 있다. 

이때, setter나 생성자로 바로 값을 넘겨주면 restore할 때 불안전 하다고 해서 (어디서 봤던건지 기억안남)

이 경우에는 companion object의 newInstance 메서드를 사용해서 Fragment 인스턴스를 생성해주고,

넘겨줘야하는 특정 값을 Bundle로 넘긴다.

companion object {
    fun newInstance(from: String): ClubCategoryFragment {
        val fragment = ClubCategoryFragment()
        val bundle = Bundle()
        bundle.putString("from", from)
        fragment.arguments = bundle
        return fragment
    }
}

// newInstance 호출하는 부분
ClubCategoryFragment.newInstance("write")

 

 

싱글톤

싱글톤은 단 하나의 인스턴스만 생성되도록 제약을 둔 디자인 패턴이다.

코틀린에서는 오브젝트(object)를 사용하여 간편하게 선언할 수 있다.

 

자바에서는 싱글톤 패턴을 만족시키기 위해 인스턴스 변수를 선언하고, 생성자 및 정적 메서드를 추가하는 작업을 넣어야 한다.

public final class Singleton {
	private static Singleton instance = null;
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
    	if(instance == null){
        	instance = new Singleton()
        }
        return instance;
    }
}       	

하지만 코틀린은 위 작업을 단 한줄로 처리할 수 있다.

object Singleton

 

 

중첩 클래스

특정 클래스 간 종속관계가 있는 경우 이를 중첩 클래스(nested class)로 표현할 수 있다.

단, 자바와 달리 중첩 클래스의 종류에 따라 사용하는 문법이 살짝 다르다.

  • 정적 중첩 클래스(static nested class): 별도의 키워드 필요 X. 바깥 클래스의 인스턴스 생성 필요 없이 인스턴스 생성 가능
  • 비 정적 중첩 클래스(non-static nested class): inner 키워드 추가. 바깥 클래스의 인스턴스를 생성해야 인스턴스 생성 가능
class Outer {

	// 키워드가 없으면 정적 중첩 클래스로 간주
    class StaticNested {
    
    }
    
    // inner 키워드를 사용하여 비 정적 중첩 클래스 선언
    inner class NonStaticNested {
    
    }
}

// 정적 중첩 클래스
val staticInstance = Outer.StaticNested()

// 비 정적 중첩 클래스
val nonStaticInstance = Outer().NonStaticNested()

 

💡중첩 클래스를 쓰는 이유

1. 클래스들의 논리적인 그룹을 나타내기 위해. 나는 주로 RecyclerViewAdapter안에 ViewHolder 클래스를 비 정적 중첩 클래스로 만들어준다.

2. 향상된 캡슐화

3. 가독성이 좋아지고, 유지보수가 편해짐

 

 

 

 

3/4(수)까지 커니의 코틀린 2,3,4,8장을 공부하기로 했는데 아직 2장 반도 못했다 ^,^  망한듯. ㅋㅋㅋㅋ

하지만 어제 애들이랑 만나서 서로 퀴즈를 내주면서 공부했는데 아주아주 즐거웠다. 얼리버디 012❤️