1.  람다식   Lambda Expression

🚀  람다식은 람다 표현식, 람다라고도 불림
🚀  람다식을 한 줄로 표현하면 '람다식은 마치 값처럼 다룰 수 있는 익명함수'

      ⚡️  람다식을 값처럼 다룰 수 있다는 말은, 람다식 자체가 함수의 인수가 되거나 반환값이 될 수 있다는 의미

    val sayHello = fun() { println("Hello world!") }
    sayHello()

 

1)  람다식 정의

    ✓  람다를 이용하여, 인수 숫자의 제곱값을 반환

    val squareNum: (Int) -> Int = { number -> number * number }
    println(squareNum(12))
  • squareNum : 람다식을 저장할 변수의 이름을 지정
  • (Int) : 람다식의 인수 자료형을 지정
  • Int : 람다식의 반환 자료형을 지정. 이 경우에는 정수를 넣고 정수를 반환.
  • number : 인수 목록을 나열. number의 자료형은 자료형에서 명시해주었으므로 형추론이 되어 number는 Int가 됨.
  • number * number : 람다식에서 실행할 코드를 지정

 

 📌  자료형은 인수목록에서 명시해주어도 됨. 앞의 코드와 동일하게 작동하는 함수

    val squareNum2 = { number: Int -> number * number }
    println(squareNum2(12))

 

📌  람다식의 인수가 한 개이면 인수를 생략하고 it으로 지칭할 수 있음

    val squareNum3: (Int) -> Int = { it * it }
    println(squareNum3(12))

 


    2)  람다를 표현하는 다양한 방법

 람다는 '값처럼' 사용할 수 있는 익명 함수. 값처럼 사용한다는 것은 함수의 인수로도 넣어줄 수 있다.

    fun invokeLambda(lambda: (Int) -> Boolean): Boolean { //람다식을 인수로 받음.
        return lambda(5)
    }
    // 이 함수는 다음과 같이 람다식을 인수로 넣어 사용할 수 있음.
    val paramLambda: (Int) -> Boolean = { num -> num == 10 }
    println(invokeLambda(paramLambda)) // 람다식의 인수로 넣은 5 != 10 이므로 -> false
    // 변수를 사용하지 않고도 바로 넣어줄 수도 있음.
    // 다음의 람다식들은 모두 똑같이 작동
    invokeLambda({ num -> num == 10 }) // 람다식 바로 넣어주기
    invokeLambda({it == 10}) // 인수가 하나일 때 it으로 변경 가능
    invokeLambda(){ it == 10 } // 만약 함수의 마지막 인수가 람다일 경우 밖으로 뺄 수 있음.
    invokeLambda{ it == 10 } // 그 외 인수가 없을 때 () 생략 가능.

 

3)  SAM(Single Abstract Method) 변환

📍  안드로이드를 개발하다 보면 다음과 같은 코드를 아주 많이 작성하게 됨

    button.setOnClickListener {
        // 버튼이 눌렸을 때 작성할 코드
    }

 

  👾  함수의 인수가 하나이고 람다식인 경우에 ()를 생략하고 {}에 코드를 작성할 수 있음
  👾  setOnClickListener() 함수는 람다식이 아닌 OnClickListener 인터페이스를 인수로 받음

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }


  👾  OnClickListener는 다음과 같이 추상 메서드 하나가 있는 인터페이스

public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }


  ⚡️  setOnClickListener 는 이와 같이 람다식이 아님에도 마치 람다식처럼 취급되고 있음
         ▶️  이것이 가능한 이유가 바로 자바 8에서 소개된 SAM 변환
             

🤓  SAM 변환은 두 가지 조건이 있다

    1. 코틀린 인터페이스가 아닌 자바 인터페이스
    2. 인터페이스 내에는 딱 한 개의 추상 메서드만 존재

    이 조건을 만족하는 경우 익명 인터페이스 객체 생성에 람다식을 사용할 수 있음.
    람다식을 사용하면 코드가 훨씬 간결해지고 가독성이 높아짐.

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  지연 초기화

코틀린은 지연 초기화를 사용하는데 이는 클래스의 코드에 Nullable 처리가 남용되는 것을 방지해 줌

 

1) lateinit

개발을 하다 보면 클래스 안에서 변수(프로퍼티)만 Nullable로 미리 선언하고 초기화(생성자 호출)를 나중에 해야 할 경우가 있는데, 이럴 경우 lateinit 키워드를 사용할 수 있음

Nullable로 선언하는 일반적인 방법


  👾  일반적인 선언 방식으로 처음에 null 값을 입력해두고, 클래스의 다른 메서드 영역에서 값을 입력함

class Person {
    var name: String? = null
    init {
        name = "Jane"
    }
    fun process() {
        name?.plus(" Messi")
        println("이름의 길이 = ${name?.length}")
        println("이름의 첫글자 = ${name?.substring(0, 1)}")
    }
}

 

  📍 이 방식은 변수에 입력된 값의 메서드나 프로퍼티를 사용할 때 Safe Call(?.)이 남용되어 가독성을 떨어트리는 문제가 있음

 

 lateinit을 사용하는 방법


  👾  lateinit을 사용하면 Safe Call을 쓰지 않을 수 있기 때문에 코드에서 발생할 수 있는 수많은 ?를 방지할 수 있음

class Person2 {
    lateinit var name: String
    init {
        name = "Jane"
    }
    fun process() {
        name.plus(" Messi")
        println("이름의 길이 = ${name.length}")
        println("이름의 첫글자 = ${name.substring(0, 1)}")
    }
}

 

 

lateinit의 특징


  ✓  var로 선언된 클래스의 프로퍼티에서만 사용할 수 있음
  ✓  null 은 허용되지 않음
  ✓  기본 자료형 Int, Long, Double, Float 등은 사용할 수 없음

  ✓  lateinit은 변수를 미리 선언만 해놓은 방식이기 때문에 초기화되지 않은 상태에서 메서드나 프로퍼티를 참조하면 null 예외가 발생해서 앱이 종료. 따라서 변수가 초기화되지 않은 상황이 발생할 수 있다면 Nullable이나 빈 값으로 초기화하는 것이 좋음

 


2)  lazy

👩🏻‍💻  lazy는 읽기 전용 변수인 val을 사용하는 지연 초기화
👩🏻‍💻  lateinit이 입력된 값을 변경할 수 있는 반면, lazy는 입력된 값을 변경할 수 없음
👩🏻‍💻  val로 변수를 먼저 선언한 후 코드의 뒤쪽에 by lazy 키워드를 사용, 그리고 by lazy 다음에 나오는 중괄호 {}에 초기화할 값을 써주면 됨

class Company {
    // by lazy를 사용하면 반환되는 값의 타입을 추론할 수 있기 때문에 변수의 타입을 생략할 수 있음.
    val person: Person by lazy { Person() }
    init {
        // lazy는 선언 시에 초기화 코드를 함께 작성 하기 때문에 초기화 과정이 필요없음.
    }
    fun process() {
        println("person은 이름은 ${person.name}") // 최초 호출하는 시점에 초기화.
    }
}

 

lazy의 특징


  ✓  선언 시에 초기화 코드를 함께 작성하기 때문에, 따로 초기화 할 필요가 없음
  ✓  lazy로 선언된 변수가 최초 호출되는 시점에 by lazy{} 안에 넣은 값으로 초기화
  ✓  코드에서 Company 클래스가 초기화 되더라도 person에 바로 Person()으로 초기화 되지 않고, process() 메서드에서 person.name이 호출되는 순간 초기화

💡  lazy는 주의해서 사용. 지연 초기화는 말 그대로 최초 호출되는 시점에 초기화 작업이 일어나기 때문에 초기화하는데 사용하는 리소스가 너무 크면 (메모리를 많이 쓰거나 코드가 복잡한 경우) 전체 처리 속도에 나쁜 영향을 미칠 수 있음

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.   제네릭

제네릭 Generics은 입력되는 값의 타입을 자유롭게 사용하기 위한 도구

📌 다음은 실제 MutableList의 선언부

public interface MutableList<E> : List<E>, MutableCollection<E>


  ✓  클래스명 옆에 <E>부분에 String 같은 특정 타입이 지정되면 클래스 내부에 선언된 모든 E에 String 타입으로 지정
  ✓  결과적으로 var list: Array<E>가 var list: Array<String>으로 변경이 되는 것

  ⚡️  이렇게 설계된 클래스는 주로 구현하는 용도로 사용하며 컬렉션이나 배열에서 입력되는 값의 타입을 특정하기 위해 다음과 같이 사용

var list: MutableList<String> = mutableListOf("abc", "def", "ghi") 
fun testGenerics() {
    // String으로 제네릭을 사용했기 때문에 list 변수에는 문자열만 담을 수 있음.
    var list: MutableList<String> = mutableListOf()
    list.add("abc")
    list.add("def")
    list.add("ghi")
//    list.add(30) // 입력 오류가 발생

    // String 타입의 item 변수로 꺼내서 사용할 수 있음.
    for (item in list) {
        println("리스트에 저장된 값은 ${item}입니다")
    }
}

 


2.  Null

코틀린은 Null 값의 처리에 많은 공을 들인 언어. null은 프로그래밍하면서 항상 이슈의 중심에 있는데 null로 인해 프로그램 전체, 혹은 앱 자체가 멈출 수 있기 때문.

 

 프로그램이 멈출 수 있는 상황 예시

   

      main() 함수 안에 one 변수를 하나 선언하고 타입으로 클래스를 지정.
     그리고 특정 조건이 만족할 때만 선언한 변수에 생성자를 호출해서 저장하는 조건문 if를 만듦.
     그리고 변수를 통해 해당 클래스의 메서드를 호출.

class One {
    fun printMe() {
        print("null safety")
    }
}

fun main() {
    var one: One
    if (1 > 2) {
        one = One()
    }
 //   one.printMe()
}

 

  👾  이 코드에서 조건이 false이기 때문에 one 변수는 아무것도 없는 null 상태가 됨
         ➡️  print 메서드를 호출하면 null 포인터 예외가 발생하면서 프로그램이 다운, 물론 IDE에서 오류를 발생시켜 컴파일되지 않도록 막아줌
  👾  코드양이 많아 지면 이런 상황이 언제든지 발생할 수 있는데, 코틀린은 이런 상황을 방지하기 위해서 안전장치를 마련해둠
         ➡️  그 결과물이 Null Safety


1)  null 값 허용하기 : ?


코틀린에서 지정하는 기본 변수는 모두 null이 입력되지 않음
null 값을 입력하기 위해서는 변수를 선언할 때 타입 뒤에 ? (Nullable, 물음표)를 입력.

var variable : String?

 

 

변수에 null 허용하기

변수의 타입 뒤에 물음표를 붙이지 않으면 null 값을 입력할 수 없음.
null 예외를 발생시키고 싶지 않다면 기본형으로 선언.
fun main() {
    var nullable: String? // 타입 다음에 물음표를 붙여서 null 값을 입력할 수 있음.
    nullable = null

    var notNullable: String
//    notNullable = null // 일반 변수에는 null을 입력할 수 없음.
}

함수 파라미터에 null 허용 설정하기

함수의 파라미터에도 null 허용 여부를 설정할 수 있음.
함수의 파라미터가 null을 허용하려면 해당 파라미터에 대해서 null 체크를 먼저 해야만 사용할 수 있음.
단, 파라미터를 조건문에서 null인지 아닌지를 체크해야만 사용할 수 있음
fun nullParameter(str: String?) {
    // 파라미터 str에 null이 허용되었기 때문에 함수 내부에서 null 체크를 하기 전에는 str을 사용할 수 없음.
    if (str != null) {
        var length = str.length
    }
}

함수의 리턴 타입에 null 허용 설정하기

함수의 리턴 타입에도 물음표를 붙여서 null 허용 여부를 설정할 수 있음.
함수의 리턴 타입에 Nullable이 지정되어 있지 않으면 null 값을 리턴할 수 없음.
fun nullReturn(): String? {
    return null
}

 

2)  안전한 호출 : ?.


변수를 Nullable로 만들기 위해서 물음표를 사용.
?.(Safe Call, 물음표와 점)을 사용하면 null 체크를 간결하게 할 수 있음
  ✓  Nullable인 변수 다음에 ?.을 사용하면 해당 변수가 null일 경우 ?. 다음의 메서드나 프로퍼티를 호출하지 않음

fun testSafeCall(str: String?): Int? {
    // 문자열 길이를 반환하는 length 프로퍼티를 호출했는데 str 변수 자체가 null일 경우는 
    // length 프로퍼티를 호출하지 않고 null을 반환.

    var result: Int? = str?.length
    return result
}

3)  Null 값 대체하기 : ?:


?:(Elvis Operator, 물음표와 콜론)을 사용하면 원본 변수가 null 일 때 넘겨줄 기본값을 설정할 수 있음

 

다음 코드에서 Safe Call 다음에 호출되는 프로퍼티 뒤에 다시 ?:을 붙이고 0이라는 값을 표시.
이렇게 호출하면 str 변수가 null일 경우 가장 뒤에 표시한 0을 반환.
fun testElvis(str: String?): Int {
    // length 오른쪽에 ?:을 사용하면 null일 경우 ?: 오른쪽의 값이 반환.
    var resultNonNull: Int = str?.length ?: 0
    return resultNonNull
}

 


 

💫  물음표의 위치와 형태에 따라서 Nullable, Safe Call, Elvis Operator가 구분

🤓  Nullable

표기법 : 선언하는 변수의 타입 다음에 ? 표기.
사용 목적 : null을 입력받기 위해 사용.
사용 예 : var nullable: String?
🥸  Safe Call

표기법 : 선언된 변수의 이름 다음에 ?. 표기.
사용 목적 : null 일 때 ?. 다음에 나오는 속성이나 명령어를 처리하지 않기 위해 사용.
사용 예 : var result = 변수?.length
🧐  Elvis Operator

표기법 : 선언된 변수의 이름 다음에 ?: 표기.
사용 목적 : null일 때 ?: 다음에 나오는 값을 기본 값으로 사용.
사용 예 : var result = 변수 ?: 0

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1. 접근 제한자

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

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


1) 접근 제한자의 종류


  🐰  접근 제한자는 서로 다른 파일에게 자신에 대한 접근 권한을 제공하는 것인데 각 변수나 클래스 이름 앞에 아무런 예약어를 붙이지
않았을 때는 기본적으로 public 접근 제한자가 적용

 

각각의 접근 제한자가 공개하는 범위
  • public : 코틀린의 기본 접근 제한자. 어디서나 접근 가능
  • internal : 같은 모듈 내에서 접근 가능. 안드로이드 개발 시에는 한 프로젝트 안에 있으면 같은 모듈
                    만약 한 프로젝트에 여러 모듈을 만든다면 이는 모듈 간 접근이 제한
  • protected : 자식 클래스에서는 접근할 수 있음
  • private : 해당 클래스 내부에서만 접근할 수 있음

2) 접근 제한자의 적용


  🐰  접근 제한자를 붙이면 해당 클래스, 멤버 프로퍼티 또는 메서드에 대한 사용이 제한

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

class Child: Parent() {
    fun callVariables() {
 //       println(privateVal) // 호출이 안됨.
        println(protectedVal) // 상속 관계이므로 접근할 수 있음.
        println(internalVal)  // 동일한 모듈이므로 접근할 수 있음
        println(defaultVal)
    }
}

// 상속 관계가 아닌 외부 클래스에서 Parent 클래스를 생성하고 사용.
// 상속 관계가 아니기 때문에 public과 internal 에만 접근할 수 있음
class Stranger {
    fun callVariables() {
        val parent = Parent()
//        println(parent.privateVal) // 호출이 안됨.
//        println(parent.protectedVal)  // 호출이 안됨.
        println(parent.internalVal)
        println(parent.defaultVal)
    }
}

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  패키지

🚀  패키지는 클래스와 소스 파일을 관리하기 위한 디렉토리 구조의 저장 공간
      ✓ 현재 클래스가 어떤 패키지(디렉토리)에 있는지 표시
      ✓ 디렉토리가 계층 구조로 만들어져 있으면 점(.)으로 구분해서 각 디렉토리를 모두 나열해 줌

package 메인 디렉토리. 서브 디렉토리
class 클래스 {
}


🚀  이 디렉토리 구조라면 윈도우의 파일 탐색에서 보면 메인 디렉토리 아래에 서브 디렉토리가 있고, 서브 디렉토리 안에 실제 코드가 있는 클래스.kt 파일이 있음

🚀  하나의 패키지에 여러 개의 파일을 생성할 수 있기 때문에 '서로 관계가 있는 파일을 동일한 패키지로 만들어 두면 관리가 용이

 


2.  추상화

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

 

  ✓ 구현 단계에서는 이 추상화된 클래스를 상속받아서 아직 구현되지 않은 부분을 마저 구현
  ✓ 다음과 같이 추상화된 Aniamal 클래스를 만들고 동물이 사용할 것 같은 기능 중 walk()와 move()를 설계한다고 가정

abstract class Animal {
    fun walk() {
        println("Walking")
    }

    abstract fun move()
}

 

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

class Bird : Animal() {
    override fun move() {
        println("Bird move")
    }
}

 


3.  인터페이스

🚀 인터페이스 interface는 실행코드 없이 메서드 이름만 가진 추상 클래스. 즉, 누군가 설계해 놓은 개념 클래스 중에 실행 코드가 한 줄이라도 있으면 추상 클래스, 코드 없이 메서드 이름만 나열되어 있으면 인터페이스

🚀 인터페이스는 안드로이드에서는 주로 상속 관계의 설계보다는 외부 모듈에서 내가 만든 모듈을 사용할 수 있도록 메서드의 이름을 나열해둔 일종의 명세서로 제공

🚀 인터페이스는 interface 예약어를 사용해서 정의할 수 있고 인터페이스에 정의된 메서드를 오버라이드해서 구현할 수 있음
    ✓  코틀린은 프로퍼티도 인터페이스 내부에 정의할 수 있고, 추상 클래스와 다르게 class 키워드는 사용되지 않음

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

 

 

1) 인터페이스 만들기

 

interface 예약어로 인터페이스를 정의
코틀린은 인터페이스 내부에 프로퍼티도 정의할 수 있음

interface InterfaceKotlin {
    var variable: String
    fun get()
    fun set()
}

 

 

2) 클래스에서 구현하기


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

class KotlinImpl : InterfaceKotlin {
    override var variable: String = "Default"
    override fun get() {

    }
    override fun set() {

    }
}

 

💡  인터페이스를 코틀린의 상속 형태가 아닌 소스 코드에서 직접 구현할 때도 있는데, object 키워드를 사용해서 구현해야 함
       ➡️  안드로이드 프로젝트에서 자주 사용하는 형태

val kotlinImpl = object : InterfaceKotlin {
    override var variable: String = "Default"

    override fun get() {

    }

    override fun set() {

    }
}

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  프로퍼티와 메서드의 재정의 : 오버라이드

👩🏻‍💻  상속받은 부모 클래스의 프로퍼티와 메서드 중에 자식 클래스에서는 다른 용도로 사용해야 하는 경우가 있음
👩🏻‍💻  동일한 이름의 메서드나 프로퍼티를 사용할 필요가 있을 경우에 override 키워드를 사용해서 재정의 할 수 있음
👩🏻‍💻  오버라이드 할 때는 프로퍼티나 메서드도 클래스 처럼 앞에 open을 붙여서 상속할 준비가 되어 있어야 함

 

메서드 오버라이드


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

    open class BaseClass {
        open fun opened() {

        }
        fun notOpened() {

        }
    }

    class ChildClass : BaseClass() {
        override fun opened() {
            super.opened()
        }
    }

 

 

프로퍼티 오버라이드


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

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

 


2.  추상 클래스

🚀  추상 클래스는 그 자체로는 객체를 만들 수 없는 클래스로, 일반적으로 추상 메소드가 포함된 클래스를 말함
        ➡️  추상 메소드란 아직 구현되지 않고 추상적으로만 존재하는 메소드
🚀  추상 클래스와 추상 메소드 앞에는 abstract 키워드를 붙임
🚀  상속받는 자식 클래스에 특정 메소드 구현을 강제하고 싶을 때 사용
🚀  추상 클래스 자체로는 직접 객체를 만들 수 없고 자식 클래스에서 추상 메소드를 구현한 다음, 자식 클래스의 객체를 생성하면 됨.

abstract class Game {

    fun startGame() {
        println("게임을 시작했습니다.")
    }
    // 추상 메소드
    abstract fun printName()
}

class OverWatch: Game() {
    override fun printName() { // 추상 메소드 구현
        println("오버워치입니다.")
    }
}

fun main() {
    val overWatch = OverWatch()
    overWatch.startGame() // 게임을 시작했습니다.
    overWatch.printName() // 오버워치입니다.
}

 

 

 

[ 내용 참고 : IT 학원 강의 ]

+ Recent posts