본문으로 바로가기

[kotlin ] 함수 선언 및 사용법

category kotlin 문법 2022. 11. 2. 11:37

목차

     

     

     기본 함수 선언

        ↑  함수 정의시 fun  예약어를 사용해 다음처럼 사용할수 있습니다.

     

    Syntax : fun FunctionName(param1:Type , ..) : returnType {

    /*...... */

    }

    fun : 함수 선언 키워드

    FunctionName : 함수 이름

    param1:Type : 첫번째 파라미터 이름 : 파라미터 타입, 두번째 파라미터 이름 : 파라미터 타입,....

    returnType : 리턴 타입 (Unit 일 경우 생략 가능)

     

     

     함수 사용법

    >> 함수 정의
        fun add (first:Int , second:Int) : Int{
           return first +second
        }
    
    >> 함수 사용
        val a: Int =2
        val b: Int = 5
        val c =add(a,b)
        Log.d(TAG,"a($a) + b($b) => $c")
        
        
      >> 결과 값
        a(2) + b(5) => 7

      : 특별한 설명이 필요없는 정수 인자 2개 및 리턴으로 정수를 반환하는 함수 정의및 사용법입니다. 

     

    class 안의 멤머함수 일경우

    class FuncTestFragment : Fragment() {
        private fun add (first:Int , second:Int) : Int{
           return first +second
        }
        
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
          val a: Int =2
          val b: Int = 5
          val d =this.add(a,b)   // this 를 사용한 클래스 내부 호출
          val e =FuncTestFragment().add(a,b)    // class 객체를 생성후  멤버함수 호출
          Log.d(TAG," d($d) , e($e) ")
        }
                
     }           
      
       > 결과 값   
       d(7) , e(7)

     

     

     함수 인자   (name : Type)

        : 파스칼 문법 표기법의 name:Type 을 사용하고 각 파라미터 구분은 콤마를 사용합니다.

         위 add함수 선언 부분만 다음처럼 표시 가능합니다.

    1> 기존 add 함수
    fun add (first:Int , second:Int) : Int{
        /*....*/
    }
    
     2> trailing comma 추가
    fun add (first:Int , second:Int,) : Int{
        /*....*/
    }
    
    3> trailing comma  && 개행 추가
    fun add (first:Int , 
             second:Int,
             ) : Int{
        /*....*/
    }

       trailing comma 는 git diff 시 유용하고 kotlin 1.4  버전 이상에서 동작합니다.

     

     

     default arguments

      : 인자에 값을 미리 넣어놓고 없을 경우 대비시 사용합니다.

     fun add (first:Int = 1 , second:Int =2 ,) : Int{
           return first +second
     }
     
      val a: Int =2
      val b: Int = 5
      val f =add(a,b)  
      val g =add(a)     // second 파라미터 인자를 default 값 2 사용한 예제  ( 2 + 2 ==> 4)
      val h =add()      // first ,second 파라미터 모두 default 값을 사용한 예제 ( 1 + 2 ==> 3)
      Log.d(TAG," f($f) , g($g) , h($h)")
      
      
      <결과 값>
      f(7) , g(4) , h(3)

       : 초기값은 type 뒤에 = 추가후 대입합니다.  ( ex> add(first:Int = 1 ,...)

         class 안의 override 함수는 초기값을 넣을수 없습니다.

         Android Studio 에서 class  안의  override 함수에 초기값을 넣을 경우 아래처럼 에러가 발생합니다.

    override fun foo    (i :Int  = 3) {  /*.... */  }

     

    open class A {
        open fun foo(i:Int =2){
            Log.d("FuncTestFragment","class A i =$i")
        }
    }
    
    class B : A() {
        override fun foo    (i :Int){
            Log.d("FuncTestFragment","class B :: i =$i")
        }
    }
    
    val clsB : B = B()
    clsB.foo()
    
    
    <결과 값>
    class B :: i =2

      ==> override 함수는 초기값을 가질수 없습니다.  부모안의 open 함수에서 초기값을 넣으면

            override 함수에서도 적용됩니다.

     

      ==> 초기값을 가지는 인자의 마지막 인수가 람다식일 경우 Named argument 나 괄호 외부로 이동할수 있습니다.

    fun add2 (first:Int = 1 , second:Int =2 ,func:() -> Unit) : Int{
        func()
        Log.d("FuncTestFragment","first($first) , second($second)")
        return first +second
    }
        
    fun main()
    {
        --> 가장 기본 형태의 호출 방법
        add2(first=7,second=4 ,func={Log.d("FuncTestFragment","default Arg exp1")})
        
        --> 파라미터의 이름 생략시 순서대로 값이 들어갑니다. (first=5 , second=3)
        add2(5,3) {Log.d("FuncTestFragment","default Arg exp2")}
        
        --> 파라미터의 순서도 이름을 넣으면 바꿀수 있습니다. (Named arguments)
            마지막 인자가 람다식일 경우 () 바깥으로 뺄수도 있습니다. (Outside parentheses)
        add2(second=2,first=3) {Log.d("FuncTestFragment","default Arg exp3")}
        
        --> 파라미터를 생략하면 초기값이 적용됩니다.
        add2(5) {Log.d("FuncTestFragment","default Arg exp4")}   // second 값에 초기값 2 대입
        
        --> 앞의 2개인자 생략하고 마지막 람다함수 인자만 있는 경우 () 안에 넣는 경우
        add2(func ={Log.d("FuncTestFragment","default Arg exp5")})
        
        --> 람다함수 인자를 () 바깥으로 이동한 경우
        add2() {Log.d("FuncTestFragment","default Arg exp6")}
        
        --> () 까지 생략한 경우 
        add2 {Log.d("FuncTestFragment","default Arg exp7")}
    }    
    
    
    < 결과 값>
    D/FuncTestFragment: default Arg exp1
    D/FuncTestFragment: first(7) , second(4)
    D/FuncTestFragment: default Arg exp2
    D/FuncTestFragment: first(5) , second(3)
    D/FuncTestFragment: default Arg exp3
    D/FuncTestFragment: first(3) , second(2)
    D/FuncTestFragment: default Arg exp4
    D/FuncTestFragment: first(5) , second(2)
    D/FuncTestFragment: default Arg exp5
    D/FuncTestFragment: first(1) , second(2)
    D/FuncTestFragment: default Arg exp6
    D/FuncTestFragment: first(1) , second(2)
    D/FuncTestFragment: default Arg exp7
    D/FuncTestFragment: first(1) , second(2)

     

     Named arguments

      : 이름을 적어주고 함수를 호출하면 파라미터 순서에 신경 쓸 필요가 없습니다.

    fun namedArgTestFunc(
        str:String,
        b_first:Boolean = true,
        b_second:Boolean = false,
        b_third:Boolean = false,
        str2:String = "sub string",
    ){
        Log.d(TAG,"str =$str , b_first =$b_first , b_second= $b_second, b_third =$b_third , str2 =$str2")
    }
    
    fun main() 
       --> 모든 인수에 값을 넣고 호출
       namedArgTestFunc("Named Argu 1",true,true, true,"sub");
       
       --> 첫번째 인자만 값 넣고 호출 
       namedArgTestFunc("Named Argu 2");
       
       --> 첫번째 ,두번째 인자만 값 넣고 호출 (나머지는 default 값 적용됩니다.)
       namedArgTestFunc("Named Argu 3",false);
       
       --> Named Arguments 를 사용해 b_first 에는 초기값을 넣고 ,b_second 값만 넣고 호출
       namedArgTestFunc("Named Argu 4", b_second = true);
             
    
    < 결과 값>
    D/FuncTestFragment: str =Named Argu 1 , b_first =true , b_second= true, b_third =true , str2 =sub
    D/FuncTestFragment: str =Named Argu 2 , b_first =true , b_second= false, b_third =false , str2 =sub string
    D/FuncTestFragment: str =Named Argu 3 , b_first =false , b_second= false, b_third =false , str2 =sub string
    D/FuncTestFragment: str =Named Argu 4 , b_first =true , b_second= true, b_third =false , str2 =sub string

      ==> Named Argumnet 사용뒤에  값만 대입할경우 아래처럼 에러가 발생합니다.

    namedArgTestFunc("Named Argu 5", b_second = true ,false);        // error

     

     

     리턴값이 Unit  인 함수

      : Unit 이란 하나의 값을 가지는 타입이란 의미로  리턴을 명시적으로 생략 가능하다.

    fun namedArgTestFunc(
            str:String,
            b_first:Boolean = true,
        ):Unit { 
        /*.... */ 
        return Unit
    }
    
     --> 위 함수서 return Unit 은 return 만 적어도 됩니다.
     fun namedArgTestFunc(
            str:String,
            b_first:Boolean = true,
        ):Unit { 
        /*.... */ 
        return
    }
     --> 리턴 타입및 return 문도 생략 가능합니다.
     fun namedArgTestFunc(
            str:String,
            b_first:Boolean = true,
        ){ 
        /*.... */ 
    }

     

     

     단일표현식 함수

      : 한줄 표현이 가능한 함수에 대해 {} 를 생략하고 = 를 추가한 함수표현

    fun add (first:Int = 1 , second:Int =2 ,) : Int{
       return first +second
    }
        
    /*single-expression functions */
    ==> 위 add() 함수를 single-expressin 함수로 바꾸기
    fun add3 (first:Int = 1 , second:Int =2 ,) : Int  = first +second
    
    ==> 리턴값이 유추가능하므로 생략 가능합니다.
    fun add4 (first:Int , second:Int)   = first +second

     

     가변 인수 (vararg)

      : 인자의 갯수가 정해지지 않은 경우에 사용하고 보통 마지막 인자에 사용합니다.

        마지막 인자가 아닐경우 named argument 를 사용해야 합니다.

     

        Syntax :   vararg  name: Type

          vararg : modifier

          name : Variable name

          Type : Variable type

     

    fun varArgTest(vararg va : String):Unit
    {
        for((i ,item) in va.withIndex()){
            Log.d(TAG,"item[$i]=$item")
        }
    }
    
    // generic function
    fun <T> varArgTest2(vararg va : T):List<T>
    {
        val result =ArrayList<T>()
        for(item in va){
            result.add(item)
        }
        return result
    }
    fun main()
    {
        --> 인수가 한개인 경우
        Log.d(TAG,"func_test4 : Variable argument 1")
        varArgTest("one")
        
        --> 인수가 2개인 경우
        Log.d(TAG,"func_test4 : Variable argument 2")
        varArgTest("one", "two")
    
        --> 인수가 3개인 경우
        Log.d(TAG,"func_test4 : Variable argument 3")
        varArgTest("one","two", "three")
    
        --> 인수타입이 정해지지 안은 경우는 generic을 사용합니다.
        Log.d(TAG,"func_test4 : Variable arguments with generic")
        val list:List<String> = varArgTest2("one","two", "three" ,"four")
        for((i,item) in list.withIndex())
           Log.d(TAG,"List[$i] = $item ")
    }
    
    <결과 값>
    D/FuncTestFragment: func_test4 : Variable argument 1
    D/FuncTestFragment: item[0]=one
    D/FuncTestFragment: func_test4 : Variable argument 2
    D/FuncTestFragment: item[0]=one
    D/FuncTestFragment: item[1]=two
    D/FuncTestFragment: func_test4 : Variable argument 3
    D/FuncTestFragment: item[0]=one
    D/FuncTestFragment: item[1]=two
    D/FuncTestFragment: item[2]=three
    D/FuncTestFragment: func_test4 : Variable arguments with generic
    D/FuncTestFragment: List[0] = one 
    D/FuncTestFragment: List[1] = two 
    D/FuncTestFragment: List[2] = three 
    D/FuncTestFragment: List[3] = four

     ==> 인자로 Array 가 들어오는 경우는 2가지로 분리가 됩니다.

     

    fun <T> varArgTest2(vararg va : T):List<T>
    {
        val result =ArrayList<T>()
        for(item in va){
            result.add(item)
        }
        return result
    }
    
    fun main()
    {
        val list:List<String> = varArgTest2("one","two", "three" ,"four")
        Log.d(TAG,"func_test4 : Variable arguments with Arraylist")
        
        ==> arraylist 를 인수로 넘기면 결과는 인수가 3개만 나옵니다.
        val list2 =varArgTest2(list,"five","six")
        for((i,item) in list2.withIndex())
    	    Log.d(TAG,"list2[$i] = $item ")
    
        ==> arraylist 의 각각의 아이템을 낱게로 추가시 
            toTypedArray() 와 spread 연산자(*)를 사용합니다.
        Log.d(TAG,"func_test4 : Variable arguments with toTypedArray")
        val list3 =varArgTest2(*list.toTypedArray(),"five","six")
        for((i,item) in list3.withIndex())
        	Log.d(TAG,"list3[$i] = $item ")
            
        ==> primitive type 의 경우 spread 연산자만 사용하면 됩니다.
        val a = arrayOf(1,2,3)
        val list4 =varArgTest2(0,*a,4)
        for((i,item) in list4.withIndex())
        	Log.d(TAG,"list4[$i] = $item ")
    
        ==> primitive type array의 경우도 toTypedArray와 함께 사용 해야 합니다.
        val b: IntArray = intArrayOf(2,3,4)    // IntArray is a primitive type array
        val list5 =varArgTest2(0,1,*b.toTypedArray(),5)
        for((i,item) in list5.withIndex())
        	Log.d(TAG,"list5[$i] = $item ")
     }       
     
     <결과값>
    D/FuncTestFragment: func_test4 : Variable arguments with Arraylist
    D/FuncTestFragment: list2[0] = [one, two, three, four] 
    D/FuncTestFragment: list2[1] = five 
    D/FuncTestFragment: list2[2] = six 
    D/FuncTestFragment: func_test4 : Variable arguments with toTypedArray
    D/FuncTestFragment: list3[0] = one 
    D/FuncTestFragment: list3[1] = two 
    D/FuncTestFragment: list3[2] = three 
    D/FuncTestFragment: list3[3] = four 
    D/FuncTestFragment: list3[4] = five 
    D/FuncTestFragment: list3[5] = six 
    D/FuncTestFragment: list4[0] = 0 
    D/FuncTestFragment: list4[1] = 1 
    D/FuncTestFragment: list4[2] = 2 
    D/FuncTestFragment: list4[3] = 3 
    D/FuncTestFragment: list4[4] = 4 
    D/FuncTestFragment: list5[0] = 0 
    D/FuncTestFragment: list5[1] = 1 
    D/FuncTestFragment: list5[2] = 2 
    D/FuncTestFragment: list5[3] = 3 
    D/FuncTestFragment: list5[4] = 4 
    D/FuncTestFragment: list5[5] = 5

     

     infix notation  (중위표기법)

       :  infix 키워드와 함꼐 선언하고 사용시  .(dot) 과 () : parentheses 를 생략 가능합니다.

       <요구사항>

        ▼ 멤버함수 또는 확장 함수 

        한개의 인수를 가질것

        초기값 사용 불가 && 가변인수 사용 불가

    infix fun Int.multiply(mul:Int):Int{
        return this * mul     
    }
    infix fun Int.sum(a:Int):Int {
        return (this + a)
    }
    infix fun Int.substract(a:Int):Int {
        return this - a
    }
    
    fun main()
    {
        >> 중위표기법 미사용 
        val sum1 = 10.sum(5)
        
        >> . 생략 표현
        val subs = 10 substract(3)      // omitting the dot
        
        >> . && () 생략 표현법
        val multi = 3 multiply 10       // omitting the dot and parentheses
        
        >> 연산자 우선순위는 산술연산자보다 낮습니다.
        val div2 = 10 divide 3 + 2      // lower precedence than arithmetic operator.
    }

     

        산술연산자보다 우선순위가 낮습니다.

         Ex> 1 shl 2 +3     <====> 1 shl  (2+3)

                 

     

     함수 사용범위

           지역 함수 (Local functions)

        ==> 지역함수 바깥에 선언된 변수를 지역함수 안에서  사용가능합니다.

               지역함수는 지정된 블럭/함수  밖에서는 사용 불가능 합니다. 

    fun localFuncTest() {
        val outerVal = 55
        Log.d(TAG,"localFuncTest")
    
        fun localFunc(){
            Log.d(TAG,"localFunc outerVal =$outerVal") // outerVal 사용되기전 정의 되어 있어야 함.
        }
        localFunc()   // 선언후 호출해야 함
    }
    
    fun main()
    {
        Log.d(TAG,"func_test6 : Local && Member function")
        localFuncTest()
    }
    
    < 결과 값>
    FuncTestFragment: func_test6 : Local && Member function
    FuncTestFragment: localFuncTest
    FuncTestFragment: localFunc outerVal =55

     

         멤버 함수 (Member functions)

          ==> 클래스(class) 나  오브젝트(object) 안에 정의된 함수

    open class A {
        open fun foo(i:Int = 5){
            Log.d("FuncTestFragment","class A i =$i")
        }
    }
    fun main()
    {
        Log.d(TAG,"func_test6 : Member function")
        A().foo()    //클래스 인스턴스 생성 및 함수호출
    }    
    
    < 결과 값>
     D/FuncTestFragment: func_test6 : Member function
     D/FuncTestFragment: class A i =5

     

     

     제네릭 함수

        ==> 일반 함수 선언에서 이름앞에 <> 를 적어주면 됩니다.

       fun <T> func_name( item:T) :Unit { /*.... */}

        가변인수 항목의   varArgTest2 함수 참조하세요.

    fun <T:Number> generic_add(first:T,second:T):T
    {
        val result:T =  (first.toDouble() + second.toDouble()) as T
        return result
    }

     

     

     Tail recursive 함수

       : tailrec 키워드 사용시 함수 재귀 호출 코드가 while() 루프 코드로 변경됩니다.

         tailrec  사용한다고 모두 while() 루프 코드로 변환되지 않으니 꼭 decompile 해 확인하시기 바랍니다.

        사용방법은 fun 키워드 앞에 tailrec 를 적어주면 됩니다.

    private fun recursive_sum(n:Long, acc:Long):Long
    {
        return if (n <= 0) {
            acc
        } else {
            recursive_sum(n-1, n+acc)
        }
    }
    
    private tailrec fun recursive_sum2(n:Long, acc:Long):Long
    {
        return if (n <= 0) {
            acc
        } else {
            recursive_sum2(n-1, n+acc)
        }
    }
    
    fun main()
    {
        Log.d(TAG,"func_test8 : tail recursive function")
        val a= recursive_sum(300000,1);   >> 갤럭시 S6 에서 스택 오버플러워 발생 
        Log.d(TAG,"a=$a")
    }
    
    <결과 값>
    I/art: Background partial concurrent mark sweep GC freed 218761(18MB) AllocSpace objects, 0(0B) LOS objects, 9% free, 146MB/162MB, paused 270us total 114.292ms
    D/Error: ERR: exClass=java.lang.StackOverflowError
    D/Error: ERR: exMsg=stack size 8MB
    D/Error: ERR: file=FuncTestFragment.kt
    D/Error: ERR: class=com.xxx.rc100_app.FuncTestFragment
    D/Error: ERR: method=recursive_sum line=173
    D/Error: ERR: stack=java.lang.StackOverflowError: stack size 8MB
    
    
    fun main()
    {
        Log.d(TAG,"func_test8 : tail recursive function")
        val a= recursive_sum2(300000,1);     // tailrec modifier 사용시 스택 에러 발생 안함.
        Log.d(TAG,"a=$a")
    }
    < 결과 값>
    D/FuncTestFragment: func_test8 : tail recursive function
    D/FuncTestFragment: a=45000150001

     

    위의 recursive_sum() && recursive_sum2() 함수  Bytecode 변환후 java decompile 후 코드 내용

     private final int recursive_sum(int n, int acc) {
      return n <= 0 ? acc : this.recursive_sum(n - 1, n + acc);
    }
    
    private final int recursive_sum2(int n, int acc) {
      while(n > 0) {     // <----------   while 루프
         int var10000 = n - 1;
         acc += n;
         n = var10000;
      }
    
      return acc;
    }

     

     

    그럼 수고하세요.

     

    반응형