본문 바로가기

Kotlin

[Kotlin] 2. 코틀린 기본 - 함수(최상위/확장)

#함수 호출 (인자에 이름 붙여 함수 호출)

  • 함수 호출 시 인자에 이름을 붙여서 호출 할 수 있다
  • java에 정의된 함수일 경우 인자에 이름 붙여 호출은 불가(코틀린은 JDK 6과 호환되는데, 클래스 파일에 함수 파람 정보를 넣는것은 JAVA 8 이후 추가됨)
  • 장점 : 함수 호출 코드를 명확하게 표시 할 수 있다 -> 가독성 UP

 

fun paramNameTest(str1: String, str2: String, str3: String) {...}
    paramNameTest("11", "22", "33") //일반적 함수 호출
    paramNameTest(str1 = "11", str2 = "22", str3 = "33") //인자에 이름 붙여 호출

 

#함수 호출 (디폴트 파람 값 추가)

  • 함수 정의 시 파라미터에 대한 디폴트 값을 정의 할 수 있다 (함수 선언 쪽에)
  • 그럴 경우 함수 호출 시 아래와 같이 파라미터를 생략 후 호출 할 수 있다
  • 장점 : 오버로딩한 함수를 정의할 필요성이 줄어든다
fun paramNameTest(str1: String = "defaultStr1", str2: String= "defaultStr2", str3: String= "defaultStr3") {...}
    paramNameTest("11", "22") //str3 생략
    paramNameTest("11") //str2, str3 생략
    paramNameTest("11", str3="33") //str2만 생략(중간에 낀 인자만 생략하고 싶을 경우 다음 인자에 이름을 붙여 사용 가능)
    paramNameTest()// 모두 생략

 

Java 파일에서 디폴트 파람 기능을 사용 하고자 하면 '@JvmOverloads' 어노테이션을 사용한다

코틀린 컴파일러가 자동으로 맨 마지막 파람부터 하나씩 삭제한 오버로딩 자바 메소드를 사용할 수 있게 해준다

@JvmOverloads
fun paramNameTest( str1: String = "defaultStr1",str2: String = "defaultStr2",str3: String = "defaultSt3") {...}
    //만들어진 오버로딩 함수(JAVA에서 호출 가능)
    public void paramNameTest(String str1, String str2,String str3){}
    public void paramNameTest(String str1, String str2){}
    public void paramNameTest(String str1){}
    public void paramNameTest(){}

 

#최상위 함수 (Top-level Function)

  • kotlin 파일에서는 클래스 밖에 함수를 선언해서 쓸 수가 있으며 static 메소드 처럼 사용 된다
  • Java 파일에선 Uitl 성 함수를  Util 클래스를 생성후 static 함수로 정의해서 사용하곤 했지만 Kotlin에서는 Util 클래스 생성 없이 최상위 함수를 선언해 사용할 수 있다 
  • 같은 package일 경우 그냥 호출해서 사용할 수 있고 다른 package일 경우 해당 패키지 import 후 사용 할 수 있다
  • 컴파일러가 컴파일 시, 최상위 함수를 static 메소드로 포함하는 클래스를 생성해준다(어쨌든 JVM은 클래스 안에 정의된 코드만을 실행 할 수 있기 때문에 / 임의의 클래스 이름은 '파일명+Kt')
  • 장점 : 코드 구조를 유연하게 만들 수 있다
//기존 자바에서 으레 사용하는 Util성 함수
public class Utils {
	public static void utilMethod() {...}
                 ...
}

 

정의 / 사용법

//********* FuntionTest.kt *********

package kotlin

fun utilMethod(){} //코틀린에서의 Util 함수 정의

class TestClass(){
    fun useUtil(){
        utilMethod() //유틸성 함수 사용
    }
}

 

FunctionTest.kt  ->  Java 로 변경 시

public final class FuntionTestKt { // 컴파일러가 생성 해 준 class
   public static final void utilMethod() { //최상위 함수는 static이 함수가 된다
   }
}

public final class TestClass {
   public final void useUtil() {
      FuntionTestKt.utilMethod();
   }
}

 

사용법(Java)

import kotlin.FuntionTestKt;
       
       ...
       
FuntionTestKt.utilMethod();

 

#최상위 프로퍼티(Top-level Property)

  • 프로퍼티도 함수처럼 파일의 최상위 수준에 위치할 수 있으며 static 필드가 된다
  • 장점 : 코드 구조를 유연하게 만들 수 있다

 

정의

//********* PropertyTest.kt *********

package kotlin;

var count =0 //최상위 프로퍼티 var
val countVal =10 //최상위 프로퍼티 val

const val REQUEST_CODE = 100 //최상위 프로퍼티 (상수)

 

PropertyTest.kt  ->  Java 로 변경 시

public final class PropertyTestKt {
   private static int count;
   private static final int countVal = 10;
   public static final int REQUEST_CODE = 100;

   public static final int getCount() {
      return count;
   }
   
   public static final void setCount(int var0) {
      count = var0;
   }
   
   public static final int getCountVal() {
      return countVal;
   }
}

최상위 프로퍼티도 자바코드에 노출 될 때 접근자 메소드를 통해 사용된다

var 일 땐 getter/setter, val일 땐 getter만 생성이 된다

const 변경자를 붙이면 상수 호출하듯이 getter로 접근이 아닌 필드 바로 접근이 가능하다

(val로 최상위 프로퍼티 선언 시, 의미는 상수 처럼 쓰일 수 있지만 getter로 접근해야 함)

 

#확장 함수(Extension Function)

  •  확장 함수는 어떤 클래스의 멤버 메소드를 추가 생성/호출 해서 사용할 수 있는 것 '처럼' 쓰는 함수(클래스를 확장 개념)
  • 실제론 그 클래스 밖에 선언된다
  • 확장할 어떤 클래스 이름을 '수신 객체 타입'이라고 하고 확장 함수가 호출될 대상이 되는 인스턴스 객체를 '수신 객체'라고 부른다
  • 확장 함수 override는 불가하다(클래스 밖에 선언되며, 수신 객체 변수의 정적 타입에 의해 어떤 확장 함수가 호출될지 결정되기 때문) 
  • 확장한 클래스의 멤버 함수와 확장 함수의 모양새가 같다면 멤버 함수가 호출된다(멤버 함수의 우선순위가 높음)
  • 장점 : 문법적 편의 / 외부 라이브러리에 정의된 클래스 같은 경우에도 클래스의 소스코드를 바꿀 필요 없이 확장 가능

 

정의

//********* FunctionTest.kt *********

//클래스 선언
class Student(var name: String, var age: Int) {}

//확장 함수 선언 - firstNameChar
//Student = 수신 객체 타입(receiver type)    this = 수신 객체(receiver object)
fun Student.firstNameChar(): Char = this.name[0] //학생 이름 첫 Char 반환

 

사용법

import kotlin.firstNameChar //확장 함수 import 
//import kotlin.firstNameChar as firstChar //확장 함수 import 시 as 키워드를 사용해서 다른 이름으로 사용 가능

fun print() {
    val student = Student("Tina", 19);
    println(student.firstNameChar()) // T
}

 

확장할 클래스가 직접 작성한 클래스가 아니고 수정할수 없다 하더라도 원하는 메소드를 어떤 클래스에 추가한 것 처럼 사용 할 수 있다

But 확장 함수 안에서, 클래스의 private/protected로 정의된 메소드나 프로퍼티를 사용 할 수는 없다 -> 클래스의 캡슐화는 지켜짐

 

확장함수는 내부적으로 구현시 아래와 같은 모양을 띄게 된다(확장할 클래스를 첫 번째 인자로 받는 static 메소드)

Java에서는 아래와 같이 사용한다.

 

확장함수 ->  Java 로 변경 시

public final class FuntionTestKt {
   public static final char firstNameChar(@NotNull Student $this$firstNameChar) {
      Intrinsics.checkNotNullParameter($this$firstNameChar, "$this$firstNameChar");
      return $this$firstNameChar.getName().charAt(0);
   }
}

 

사용법(Java)

Student student = new Student("Tina", 19);
Character firstName = FuntionTestKt.firstNameChar(student); //T

 

확장함수는 오버라이드 되지 않는다! 예제

open class CustomView {
    open fun onClick() = println("View is Clicked")
}

class CustomButton : CustomView() {
    override fun onClick() = println("Button is Clicked")
}

fun CustomView.expandedOnClick()=println("View Expanded Click")
fun CustomButton.expandedOnClick()=println("Button Expanded Click")


fun main() {
    val view: CustomView = CustomView()
    val button: CustomView = CustomButton() //컴파일 시점에, 변수의 타입에 따라 어떤 함수가 호출될 지 결정 된다 (CustomView)

    view.onClick()                  //View is Clicked
    button.onClick()                //Button is Clicked -> Override 성공

    view.expandedOnClick()          //View Expanded Click
    button.expandedOnClick()        //View Expanded Click -> Override 실패/안됨
}

 

 

 

#확장 프로퍼티(Extension Property)

  •  확장 프로퍼티는 어떤 클래스에 프로퍼티를 추가생성/호출 해서 사용할 수 있는 것 '처럼' 사용
  •  But 실제 상태를 저장할 뒷받침하는 필드(Backing Field)가 없을 뿐더러 접근자(getter/setter)도 확장 할 클래스의 밖에 선언 되는 방식으므로 실제로 클래스 객체에 필드를 추가한 것은 아니다
  •  var 선언일 경우 getter/setter 모두 정의
  •  val 선언일 경우 getter만 정의
  • 장점 : 문법적 편의 / 외부 라이브러리에 정의된 클래스 같은 경우에도 클래스의 소스코드를 바꿀 필요 없이 확장 가능

 

정의

//********* FunctionTest.kt *********

//클래스 선언
//name의 char 변경을 위해 StringBuilder 사용
class Student(var name: StringBuilder, var age: Int) {}

//확장 프로퍼티 선언
var Student.firstNameChar: Char
    get() = this.name[0]
    set(firstName: Char) {
        this.name.setCharAt(0, firstName)
    }

 

사용법

fun print() {
    val student = Student(StringBuilder("Tina"), 19)
    println(student.firstNameChar()) // T
}

 

확장 프로퍼티 ->  Java 로 변경 시

public final class FuntionTestKt {
   public static final char getFirstNameChar(@NotNull Student $this$firstNameChar) {
      Intrinsics.checkNotNullParameter($this$firstNameChar, "$this$firstNameChar");
      return $this$firstNameChar.getName().charAt(0);
   }

   public static final void setFirstNameChar(@NotNull Student $this$firstNameChar, char firstName) {
      Intrinsics.checkNotNullParameter($this$firstNameChar, "$this$firstNameChar");
      $this$firstNameChar.getName().setCharAt(0, firstName);
   }
}

 

사용법(Java)

getter/setter 명시적 사용

Student student = new Student(new StringBuilder("Tina"), 19);
Character firstName = FuntionTestKt.getFirstNameChar(student); //T
FuntionTestKt.setFirstNameChar(student,'t'); 
Character changedFirstName = FuntionTestKt.getFirstNameChar(student); //t

*코틀린  -> 자바 변경 방법

Android Studio :  Tools -> Kotlin -> Show Kotlin ByteCode -> Decompile

 

*자바와 코틀린이 100% 호환되는 이유

둘다 컴파일 후 .class 파일 즉 ByteCode로 변환되기 때문

 

 

출처 : Kotlin In Action - 에이콘 출판사

(위 도서를 학습하고 개인 학습용으로 정리한 내용입니다)


 

문제 있을시 알려 주세요.

 

좋은 하루 되세요!