KotlinでAndroid-No.07(関数・メソッド)

対象バージョン:Android Studio v4.1, Kotlin v1.4

関数・メソッド

今回はKotlinの関数・メソッドについて確認していきます。

この記事では「メソッド」は、クラス内に定義される関数をメソッドと扱います。(混同して使う場合もありますが、ご容赦ください)
クラスについては、まだ記載していないのですが、勉強用アプリのMainActivityクラスを使う事になるので、必然的にメソッドとなります。

前置き

いつものことながら、ここで紹介している内容は、僕自身がKotlinでプログラミングする上で必要と思う事を記載しています。(ほぼ備忘録です)

間違いや、不足部分、正しくない使い方も多々あると思いますので、記載内容に疑問がある場合はKotlinの正式なサイトや、他の方の記事も参考にしてみてください。

宣言

基本的な(?)宣言

(個人的に思う)基本的な宣言方法は下記になります。

fun <関数名>([<引数1> : <型1> [ = デフォルト値], ...]) [ : <戻り値の型>] 
{
     // 処理

     return <戻り値>
}

戻り値が不要な場合には、「[ : <戻り値の型>]」の部分と「return <戻り値>」が不要になります。

簡単な例では下記の様になります。

// 4つまでの整数の和
fun plus(p0: Int, p1: Int, p2 : Int = 0, p3 : Int = 0) : Int
{
    return p0 + p1 + p2 + p3
}

デフォルト値が型の後に来るのが個人的には少し気持ち悪い所ではありますが…^^;

デフォルト値は任意の位置の引数に設定可能です。呼び出し方については、後述します。

さて、勉強用アプリでも、文字列を表示させる部分も下記の様なメソッドにしてやる事で、呼び出し側がスッキリ出来そうです。

// editTextResultに指定の文字列を表示する
fun dispMsg(str: String)
{
    val editTextResult : EditText = findViewById(R.id.editTextResult)
    editTextResult.append(str + System.lineSeparator())
}

(まぁ、毎回findViewById()をするのは、若干非効率ではありますが…^^;)

簡易的な宣言

処理内容が簡単な式の様な場合は下記の様な宣言方法もあります。

fun <関数名>([<引数1> : <型1> [ = デフォルト値], ...]) = <式>
  
もしくは

fun <関数名>([<引数1> : <型1> [ = デフォルト値], ...]) = run { <式> }

前者は宣言の後半に記載した「= <式>」の結果が戻り値になるというものです。後者も書き方は少し異なりますが結果は同じになります。(後者はあまり使わないでしょうけど…)

前述の和を計算する関数は下記の様に記載できます。

// 4つまでの整数の和
fun plus(p0: Int, p1: Int, p2 : Int = 0, p3 : Int = 0) = p0 + p1 + p2 + p3

また、if文なども値を返す事が出来るので、下記の様な関数の宣言の仕方も可能です。

// 最大値の取得
fun max(x: Int, y: Int) = if(x > y) x else y

個人的には、「C/C++のマクロ関数に似てるなぁ」と思いましたが、マクロ関数がコードを展開するのに対して、こちらはあくまで関数・メソッド呼び出しになるので、まったく同じにはならないのですが…^^;

引数について

ふと『Kotlinの関数・メソッドの引数は「値渡し」だろうか「参照(アドレス)渡し」だろうか?』という疑問が浮かびましたが、そもそもKotlinにはそのような区別は無さそうです。

引数の宣言は、

<引数> : <型>

の様になっており、「var」も「val」もないのですが、基本は「val」扱いになります。

「val」と同じという事は値の変更・再割り当てが出来ませんので、下記の様なコードはエラーになります。

個人的に「Intなどと言えどもクラスなので、全て参照渡しだろう」と思ってたので、、「えッ?これ出来ないの」と思いました…^^;

まぁ、しかし「val」と同じという事は、「No.6 (コレクション)」で記載した様に、配列などの要素の変更であれば可能です。当然、呼び出し元での値も変わります。

同様に引数で渡されたクラス(インスタンス)のメンバーの値の変更なども可能です。

ラムダ関数

Kotlinではラムダ関数をサポートしています、というか頻繁に利用されます。

基本的な構造は下記になります。

val <関数名> : (引数型リスト) -> 戻り値型 = { 引数リスト -> 式 }

関数だけども、「val」や「var」で指定する所や、型指定部分が「(引数型リスト) -> 戻り値型」になっている点がポイントでしょうか?

また、ラムダ関数の引数にはデフォルト値は設定できません。

引数1個、戻り値あり

引数1個、戻り値ありのラムダ関数の宣言は以下の様になります。
(例は、ある数値の二乗を計算するラムダ関数です。)

// 省略なし
val lambdaSquare1 : (Int) -> Int = { x : Int -> x * x}
// 引数xの型宣言を省略
val lambdaSquare2 : (Int) -> Int = {x -> x * x}
// it表記を利用し、変数宣言部を省略
val lambdaSquare3 : (Int) -> Int = {it*it} // 「it」表記は引数が一つの時にしか利用できない
// 型・戻り値宣言部を省略
val lambdaSquare4 = {x : Int -> x * x}

引数2個以上、戻り値あり

引数2個以上、戻り値ありのラムダ関数の宣言は以下の様になります。
(例は、ある2つの数値の和を計算するラムダ関数です。)

// 省略なし
val lambdaPlus1 : (Int, Int) -> Int = {x : Int, y : Int -> x + y}
// 引数x, yの型宣言を省略
val lambdaPlus2 : (Int, Int) -> Int = {x, y -> x + y}
// 型・戻り値宣言部を省略
val lambdaPlus3 = {x : Int, y : Int -> x + y}

引数なし、戻り値なし

引数なし、戻り値なしのラムダ関数の宣言は以下の様になります。
(例は、呼び出されたら「Hello」を表示するラムダ関数です。)

// 省略なし
val lambdaVoid1 : ()->Unit = {dispMsg("Hello")}
// 型・戻り値宣言部を省略
val lambdaVoid2 = {dispMsg("Hello")}

JAVAなどでは戻り値なしの場合「void」を利用しますが、Kotlinでは「Unit」を利用します。

なお、Android Studioでラムダ関数は宣言部分、呼び出し部分問わず、太字で表示されます。(普通の関数と区別するためでしょうか…?)

おまけ

勉強用アプリで、ボタンのsetOnClickListenerもラムダ関数を設定していました。

View.OnClickListenerはView型の引数、戻り値なし(Unit)なので、下記の様なラムダ関数を利用すれば、OnCreate()メソッド内をシンプルにできると思います。

   override fun onCreate(savedInstanceState: Bundle?) {
        …(略)…
        // 「実行」ボタンの設定
        val btnExec: Button = findViewById(R.id.btnExec)
        // OnClickListenerの設定
        btnExec.setOnClickListener(btnExecOnClickListener)

        …(略)…
    }

   // btnExecのOnClickListener処理
    val btnExecOnClickListener : (View) -> Unit = {
      // テスト関数の呼び出し
      TestFunc() 
    }

呼び出し

通常の呼び出し

宣言順に引数を指定して呼び出す事で、関数・メソッドを呼び出し、処理をさせる事が出来ます。

// 整数の和
fun plus(p0: Int, p1: Int) : Int
{
    return p0 + p1
}

// 呼び出し
val x = 10
val y = 20
val sum = plus(x, y)

Android Studioでコードを書くと、各引数を直値で記載するとどの引数と対応しているかが薄いグレーで表示されます。変数を使った場合は引数の対応は表示されません。

引数の明示

関数・メソッドを呼び出す際に、引数を明示する事も出来ます。

val x = 10
val y = 20
val sum = plus(p0 = x, p1 = y)

引数を明示すると、引数の順序を変更する事も可能になります。
(引数の順番を変更した場合は、全ての引数を明示する必要があります)

デフォルト値のある場合

引数にデフォルト値のある関数・メソッドの呼び出しは、デフォルト値の指定がある引数は指定を省略できます。

// 3つ目以降の引数にデフォルト値あり
fun plus4(p0: Int, p1: Int, p2 : Int = 0, p3 : Int = 0) : Int
{
    return p0 + p1 + p2 + p3
}

途中の引数のみにデフォルト値がある場合は、それ以降の引数は明示が必要になります。

// 3番目の引数p2だけデフォルト値がある
fun plus5(p0: Int, p1: Int, p2 : Int = 30, p3 : Int, p4 : Int) : Int
{
    return p0 + p1 + p2 + p3 + p4
}

関数・メソッドの任意の位置にデフォルト値を指定する事が出来ますが、利用シーンを考えると使いにくいかも知れません^^;

関数を引数や戻り値に指定

関数については、関数自体を引数に渡したり、関数の戻り値に関数を指定したりも出来ます。

下記は、四則演算を行う関数を戻り値に指定したり、関数を引数に与えたりする例です。

    // 引数x, yに対してop関数を実行する
    fun calc(x:Int, y:Int, op:(Int, Int)->Int) : Int
    {
        return op(x, y)
    }

    // 四則演算に対応する関数
    fun sum(x: Int, y : Int) = x + y
    fun sub(x: Int, y : Int) = x - y
    fun mul(x: Int, y : Int) = x * y
    fun div(x: Int, y : Int) = x / y
    fun zro(x: Int, y : Int) = 0

    // 演算文字から対応する関数を取得する。
    fun getOp(opChr : Char) : (Int, Int)->Int
    {
        val op : (Int, Int)->Int =
        when(opChr)
        {
            '+' -> this::sum
            '-' -> this::sub
            '*' -> this::mul
            '/' -> this::div
            else -> this::zro
        }

        return op
    }

    /**
     * 処理を実行し、テキストボックスに結果を表示する
     */
    fun TestFunc()
    {
        val x = 5
        val y = 2
        dispMsg("x = " + x + ", y = " + y)
        dispMsg("x + y = " + calc(x, y, getOp('+')))
        dispMsg("x - y = " + calc(x, y, getOp('-')))
        dispMsg("x * y = " + calc(x, y, getOp('*')))
        dispMsg("x / y = " + calc(x, y, getOp('/')))
    }

関数を引数や戻り値に指定する場合は、「(Int, Int)->Int」などの様に、引数と戻り値を指定し、呼び出す際には、引数と一致した引数と戻り値を持つ関数名を名前空間を指定して(「this::sum」などの様に)渡せばよいようです。

拡張関数(Extension Functions

拡張関数については、クラスの項目で記載しようと思いますので、ここでは割愛します。


さて、今回は関数・メソッドについて見てきたわけですが、個人的な感想として、

「書き方の自由度が高すぎ!」

です。

個人でプログラムを書く場合は、自分の好みの書き方で書けば良いと思うのですが、会社やチームで協力してコードを書く際には、ある程度、どう書くべきかの規約を設けた方がいいような気がします。でないと、自分の様な初心者は他人の高度にパーソナライズされたコードは読めないので^^;

次回予告

次回は、クラスについて見て行きたいと思います。


前の記事次の記事
No.06(コレクション)No.08(クラス)