自己紹介
初めまして、NewsPicksエンジニアの米澤翔です。
2022年の6月に入社し、そこから初めてKotlinを触り始めました。
私は昔軽くJavaを触ったことがあったり、C#をメインにコードを書いていたりしたのですが...
Kotlinはまさに「ちょうど良いパートナー」でした。
今回はそんなKotlinの魅力について、特にWebエンジニアの視点から語らせてください。
Kotlinの魅力
「使い手の心理的安全性が高く保たれ、書いたり読んだりする負担が小さい」
「それでいてJavaの便利なライブラリやフレームワークを利用できる」
これにつきると思っています。
null安全がデフォルトとなっている
一番でかいメリット。
例えばJavaで「安全な」コードを書こうとしたら、こんなコードになるのではないでしょうか?
public void save(FreeTrialCouponCode freeTrialCouponCode) { if (freeTrialCouponCode == null) { throw new IllegalArgumentException("freeTrialCouponCode should not be null"); } freeTrialCouponCodeDao.save(freeTrialCouponCode); }
ですが、Kotlinはデフォルトでnullを許容していないので、心配性なコードを書く必要はありません。
fun save(val freeTrialCouponCode: FreeTrialCouponCode) { freeTrialCouponCodeDao.save(freeTrialCouponCode); }
「もしかしたらこの変数、nullなのかも?誤って参照して、null pointerで落ちたらどうしよう」
そんな心配がなくなりますね。
もちろんkotlinでもnullableな変数も使えます。
ですが「null安全がデフォルトの文法におけるnullableな変数」は物理的にnullハンドリングを強制させられるので、null pointer起因のバグをかなり見逃しにくくなります。
Javaの資産が活かせる
KotlinはJVM系の言語です。なのでSpringBootなどのJava製フレームワークの恩恵を受けることができます。
「うちもSpringBoot採用したんだけど、.javaファイルなんだよなぁ...」
ご安心ください。.java
と .kt
という、拡張子が異なるソースが共存して同じJVM上で動きます。

初めて見ると非常に奇妙な光景ですが、ちゃんと動きます。
昔に作られたJavaの資産を活かしつつ、新たな実装箇所や既存機能の一部をKotlinで作る・リプレースすることが可能です。
もちろん、KotlinからJavaのコードを実行できますし、なんなら JavaからKotlinのコードを呼ぶことも可能です。
配列操作が簡単で直感的
弊社エンジニア武藤が執筆した過去の記事と内容が重複しますが、配列操作が簡単に書けます。
例えばJavaで配列の操作を書く場合、こんなコードになるでしょうか。
List<Integer> doubledEvenNumbers = numbers.stream().filter(n -> n % 2 == 0).map(n -> n * 2).collect(Collectors.toList());
Java使いの皆様には悪いのですが、ちょっと野暮ったいように感じます。 (stream
とか collect
とか、いる?)
一方、Kotlinではこう書けます。
val doubledEvenNumbers = numbers.filter { it % 2 == 0 }.map { it * 2 }
以前私はC#を使っていたのですが、LINQ
を彷彿とさせるイケてる文法ですし、明らかに記述量が減っています。
そしてKotlin はイテレータを it
で書けるので、Java では
map(n -> n * 2)
と書いていたところをKotlinでは
map { it * 2 }
と、より短く書けます。地味に嬉しい。
文末のセミコロン不要
Javaはこう
freeTrialCouponCodeDao.save(freeTrialCouponCode);
Kotlinはこう
freeTrialCouponCodeDao.save(freeTrialCouponCode)
ちょっとシンプルになって気持ちがいい(体感)
コンストラクタインジェクションが簡単に書ける
テスタビリティを上げるために、Dependency Injectionを使う方も多いのではないでしょうか?
例えばSpringBootなどではコンストラクタインジェクションが推奨されています。
Javaで書くとこんな感じでしょうか。
@RestController public class ImageController { private final BookImageService bookImageService; @Autowired public ImageController(BookImageService bookImageService) { this.bookImageService = bookImageService } }
一方Kotlinでは、フィールドの宣言とコンストラクタが同時に書けちゃいます。
@RestController class ImageController( private val bookImageService: BookImageService ) { }
楽!
テストフレームワーク「kotest」がイケてる
直感的にして簡潔に記述できるテストフレームワーク「kotest」の存在もありがたいです。
テストコードは例えばこんな感じ
package com.newspicks.util import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import java.time.format.DateTimeFormatter import java.time.format.DateTimeParseException class DateTest : FunSpec({ context("StringのtoZonedDateTimeへの変換テスト") { test("正常にパースできる場合、意図したzonedDateTimeの値となる") { val zonedDateTime = "2023-12-03T10:15:30+01:00".toZonedDateTime(DateTimeFormatter.ISO_OFFSET_DATE_TIME) zonedDateTime.year shouldBe 2023 ... } test("文字列が不正でparseできない場合、例外が発生する") { val invalidTimeString = "何かしら不正な値" val exception = shouldThrow<DateTimeParseException> { invalidTimeString.toZonedDateTime(DateTimeFormatter.ISO_OFFSET_DATE_TIME) } exception.message shouldBe "..." } } })
ネスト形式で記述できるのでテストのスコープがわかりやすいですし、日本語で説明を書いても違和感ないですね。
型を後ろに書く
アラビア語圏の方、すみません。
コードを読むとき、私たちは「左から右」に読みますよね?
試しに↓を読んでみましょう。
public void verifyName(NameVerification model) { ... NameVerificationStatus status = (user.isNameVerified()) ? NameVerificationStatus.UPDATE : NameVerificationStatus.NEW; saveVerificationStatusHistory(user.getId(), NameVerificationStatus.CONFIRMED, null); ... } public Optional<StreamFile> getCertificate(Integer userId) { return certificateRepository.get(userId); }
左から読んでる途中でこう思いませんでしたか?
「型名よりも変数名・メソッド名の方に興味あるから、さっさとしろ」
往々にして、私たちがまず気にしているのは「型の名前」じゃなくて「変数の名前・メソッドの名前」ではないでしょうか。
であるならば、左から読んだ時に変数名・メソッド名がまず先に表示される方が読みやすいと思うのは、私だけでしょうか。
fun verifyName(model: NameVerification?) { .... val status = if (user.isNameVerified) NameVerificationStatus.UPDATE else NameVerificationStatus.NEW saveVerificationStatusHistory(user.id, NameVerificationStatus.CONFIRMED, null) .... } fun getCertificate(userId: Int?): StreamFile? { return certificateRepository[userId] }
これは私の想像ですが、同じような文法の Golangやrustを設計したエンジニアも、同じことを考えていたのではないでしょうか?
メソッドがデフォルトでpublicであり、voidを書く必要がない
「メソッドを追加する場合、それはpublicであることが多い」
ある程度オブジェクト思考の言語でコードを書いたことのある方なら、ご納得いただけるでしょう。
public class FreeTrialCouponCodeRepository { ... public void save(FreeTrialCouponCode freeTrialCouponCode) { freeTrialCouponCodeDao.save(freeTrialCouponCode); } public void saveAll(List<FreeTrialCouponCode> freeTrialCouponCodeList) { freeTrialCouponCodeList.forEach(this::save); } public void update(FreeTrialCouponCode freeTrialCouponCode) { freeTrialCouponCodeDao.merge(freeTrialCouponCode); } }
しからば人は思うはず。
「メソッド読むたび、高確率でpublicという単語を毎回読まされるの、面倒だなぁ」
...私だけでしょうか。
kotlinならpublicなメソッドに、public
の記述は不要なんです。
なんなら void
も不要です。
class FreeTrialCouponCodeRepository { ... fun save(val freeTrialCouponCode: FreeTrialCouponCode) { freeTrialCouponCodeDao.save(freeTrialCouponCode); } fun saveAll(val freeTrialCouponCodeList: List<FreeTrialCouponCode>) { freeTrialCouponCodeList.forEach(this::save); } fun update(val freeTrialCouponCode: FreeTrialCouponCode) { freeTrialCouponCodeDao.merge(freeTrialCouponCode); } }
左から読み始めた時、メソッド名に目が辿り着くまでの時間が圧倒的に短いですね。
public
と void
という単語を書かなくなった分、一行の圧迫感も減りました。
IntelliJで開発ができる
最強のJVM開発IDE IntelliJ
で開発できます。
IntelliJ IDEA - Java と Kotlin の最先端 IDE
改善の余地があるコードは指摘してくれて、ショートカット一つでリファクタリング完了です。 各種プラグインも充実。
しかも、.java
から .kt
ファイルへのコンバート機能まで付いています。

それでいて Community Edition なら無料。 こんな贅沢があって良いのだろうか。
最後に
さんざん褒めちぎりましたが、もちろんデメリットもあります。
例えばGo言語と比べると、シンプルさやDocker Imageのサイズでひけを取るでしょう。
rustには処理速度で敵わないでしょう。
また、Java本体の方の機能が充実してくれば、kotlin自体が廃れる可能性もあります。
ですが、歴史と実績のあるJavaライブラリ・フレームワークの恩恵を受けつつ、それをシンプルかつ安全に使えるのは、 Kotlinならではの魅力ではないでしょうか?
もしKotlinに興味を持たれた方は、アンドロイドアプリやSpringBootのAPIをkotlinで書くところから始めてみると、その恩恵を感じやすいかと思います。
Kotlinの魅力、ぜひ味わってみてください!