こんにちは!キャスレーコンサルティングの鈴木です!

私は、クライアント先でKotlinを使用したサービス開発を行っています。
今回は、現場で初めてKotlinを使用した際に、
便利だと思った点や簡単な気付きを技術ブログとして纏めていきます。

Kotlinとは?

KotlinはJetBrainsが開発したプログラミング言語です。
コンパイルされたコードはJavaVMで動作するため、
これまでのJavaの多くを流用できるという特徴を持っています。
また、Javaに比べてコードの記述量が少なく済むといわれています。
実際にIntelliJのConvert機能を使用してコード比較してみましょう!
(参考:https://www.jetbrains.com/ja-jp/idea/

JavaをKotlinにConvertする

早速ですがIntelliJのConvert機能を使用してJavaをKotlinに変換してみましょう。
以下のサンプルクラスを使用して読み替えてみます。

Javaサンプルクラス

public class Demo {
    private Long id;
    private String name;

    public Demo(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Convertは右クリック→Convert Java File to Kotlin Fileで実施できます。

Convertされた後のKotlinクラス

class Demo(val id: Long, var name: String) 

Kotlinではクラス定義する際に引数部分にコンストラクタを定義することができます。
これをプライマリコンストラクタといいます。

コンストラクタとは、クラスからオブジェクトを作成した際に、
自動的に実行されるメソッドのことで、メンバ変数の初期化などの主に行います。

コンストラクタの定義でvarとvalが記載されていますが、以下のように意味を覚えると良いです。
valは読込みだけ。Getterのみ
varは書き換えができる。GetterとSetter

また、クラス内にもコンストラクタを定義することができます。

class Demo {
    constructor(id: Long, name: String)
}

こちらはセカンダリコンストラクタといいます。
コンストラクタの複数指定ができるなどの使い方ができます。

データクラス(data class)を使用する

data classは、何かしらのデータをクラスに保存してプログラム中で使用することがある場合に便利です。
実際にコードを見ていただくとわかりますが、classの前にdataとつけるだけですね。
先ほどのサンプルクラスをdata classで記述すると以下のようになります。

data class Demo(val id: Long, var name: String)

実際にdata classを使用してみると以下のようになります。

val data = Demo(1L, "名前")
// data.id = 2L //読み取り専用のためエラー
data.name = "変更"

コード量も短くなりますし、使い方も簡単ですね!
valは読み取り専用のためIntelliJでエラー表示されていることもわかります。
また、公式ドキュメントを読んでみると
equalsやhashCode、toString、copyなどが自動的に作成されてるため、
オーバーライドする必要がなさそうですね。
(参考:https://kotlinlang.org/docs/reference/data-classes.html

アノテーションの指定方法

Kotlinでは、プロパティやプライマリコンストラクタのパラメータを修飾する際、
アノテーションの生成を正確に伝えるために、以下のように指定する必要があります。
修飾しない場合はコード上はエラーにはなりませんが、正しく処理されずバグが発生する危険性があります。

data class Demo(
    val id: Long,
    @field:Max(value = 100)
    var name: String
)

field以外にもgetやsetなども用意されているため、状況に応じて使用することが必要です。

data classではなく、classで定義したフィールドに
アノテーションを付与する記載方法だとfield指定は不要のため、以下のようになります。

class Demo {
    private val id: Long
    @Max(value = 100)
    var name: String

    constructor(id: Long, name: String) {
        this.id = id
        this.name = name
    }
}

セカンダリコンストラクタを用意しなければならないため、
若干コード量が増えますが、Javaに近いような形で記述することができますね。

総復習

総復習としてSpring Boot + KotlinでCSVをダウンロードできるようにします。
プロジェクトの新規作成にはSpring initializrを使用することをおすすめします。
(参考:https://start.spring.io/

csvを簡単に出力するために今回はjacksonのライブラリを使用します。
依存関係にjackson-dataformat-csvを追加して実装しています。
(参考:https://github.com/FasterXML/jackson-dataformats-text/tree/master/csv

Javaサンプルクラス

@Controller
public class CsvController {
    @GetMapping("csv")
    public ResponseEntity downloadCsv() throws JsonProcessingException {
        CsvMapper mapper = new CsvMapper();
        CsvSchema schema = mapper.schemaFor(Demo.class).withHeader();
        List<Demo> csv = new ArrayList<Demo>();
        csv.add(new Demo(1L, "aaa"));
        csv.add(new Demo(2L, "bbb"));
        HttpHeaders headers = new HttpHeaders();
        headers.setContentDispositionFormData("fileName", "demo.csv");
        return new ResponseEntity(mapper.writer(schema).writeValueAsString(csv).getBytes(Charset.forName("SJIS")), headers, HttpStatus.OK);
    }
}
@JsonPropertyOrder({"ID", "名前"})
public class Demo {
    @JsonProperty("ID")
    private Long id;
    @JsonProperty("名前")
    private String name;

    public Demo() {}

    public Demo(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

Kotlinサンプルクラス

@Controller
class CsvController {
    @GetMapping("csv")
    @Throws(JsonProcessingException::class)
    fun downloadCsv(): ResponseEntity<ByteArray> {
        val mapper = CsvMapper()
        val schema: CsvSchema = mapper.schemaFor(Demo::class.java).withHeader()
        val csv: MutableList<Demo> = ArrayList<Demo>()
        csv.add(Demo(1L, "aaa"))
        csv.add(Demo(2L, "bbb"))
        val headers = HttpHeaders()
        headers.setContentDispositionFormData("fileName", "demo.csv")
        return ResponseEntity(mapper.writer(schema).writeValueAsString(csv).toByteArray(Charset.forName("SJIS")), headers, HttpStatus.OK)
    }
}

@JsonPropertyOrder("ID", "名前")
data class Demo(
    @field:JsonProperty("ID")
    val id: Long,
    @field:JsonProperty("名前")
    var name: String
)

実際に起動してhttp://localhost:8080/csvをリクエストしてみてください。
実際にCSVがダウンロードできるはずです!

出力結果

ID,名前
1,aaa
2,bbb

field修飾を忘れた場合

以前にCSV出力でヘッダを出力する際、data classのコンストラクタでfield修飾をせずに
@JsonPropertyを指定した場合に、上手く出力できないことがありました。

@JsonPropertyOrder("ID", "名前")
data class Demo(
    @JsonProperty("ID")
    val id: Long,
    @field:JsonProperty("名前")
    var name: String
)
出力結果

名前,id
aaa,1
bbb,2

アノテーションが有効ではないため、ヘッダが崩れてしまっています。
data classを使用する場合は、fieldの修飾を忘れないようにしましょう。

最後に

KotlinはJavaを使用してプログラミングしたことがある方にとっては簡単な言語かと思います。
私はこれからも様々な言語にチャレンジしていきたいと思います!

最後までお読みいただきありがとうございました!

鈴木啓介
CSVIT事業部 鈴木啓介
ラーメン、お酒、フットサル大好き!