Spring 5でフレームワークのコア部分でKotlin対応が入る。
KotlinサポートのポイントはExtension FunctionsとReified type parameters。
Spring 5での対応がどのようなものを見る前に、この二つのKotlinの言語仕様を知っておくと理解しやすい。
次のJavaコードを例に簡単に説明する。
package com.example;
public class Foo {
public <T> T create(Class<T> clazz) {
try {
return clazz.newInstance();
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
この機能をJavaで使うと
Foo foo = new Foo();
Bar bar = foo.create(Bar.class);
となる。Javaユーザーにとっては特に違和感のない、よくある使い方の一つだと思う。
ではこのコードをそのままKotlinで書くとどうなるか。
val foo = Foo()
val bar = foo.create(Bar::class.java)
Bar::class
で得られるのはKClass<Bar>
(kotlin.reflect.KClass
)であり、Class<Bar>
(java.lang.Class
)に変換するのに.java
をつける必要がある。
このままでも使えないわけではないが、Javaで書くより簡単になってる?感が出てくる。 Kotlin対応していないJavaライブラリ、フレームワークとはこのような付き合い方になる。
Extension Functions
val bar = foo.create(Bar::class)
って書きたい。でもJavaフレームワーク側で直接Kotlinのクラスを使いたくない。
そんな時にKotlinのExtension Functionsが使うと、あたかもFoo
クラスにメソッドを追加したかのように見せられる。
FooExtensions.kt
というファイルに
package com.example
import kotlin.reflect.KClass
fun <T : Any> Foo.create(kclass: KClass<T>) = create(kclass.java)
を書くとFoo
クラスのメソッドとしてcreate(KClass<T>)
を追加し、中でjava.lang.Class
に変換してcreate(java.lang.Class)
を呼び出すことができる。
これで
val foo = Foo()
val bar = foo.create(Bar::class)
と素直にKotlinコードを書くことができるようになる。
Reified type parameters
もう一歩進もう。KotlinにはReified type parametersという仕組みがあり、Javaではコンパイル時に消えてしまうジェネリクスの型を、inline展開することでコード中で参照することができる。ということはJavaでは書けなかったT
をごにょごにょ...ってのが可能になり、メソッド引数からClass
を消すことができる。
これを使ってFooExtensions.kt
に次のメソッドを追加する。
inline fun <reified T : Any> Foo.create() = create(T::class.java)
これで
val foo = Foo()
val bar = foo.create<Bar>()
こう書ける。
あるいは左辺の型推論を使って
val foo = Foo()
val bar: Bar = foo.create()
こう書くこともできる。
このようなコードが"idiomatic Kotlin code"(Kotlinらしいコード)と呼ばれる。
Spring 5の"idiomatic Kotlin code"
Springユーザーならすでに気づいているかもしれないが、Springの中にはClass<T> clazz
を引数に取るメソッドが多くある。
それらに対して上記のような**Extensions.kt
が用意されている。外部ライブラリではなくSpring Framework本体に含まれているのである。
例えば、今まで書いていた
ApplicationContext context = ...;
Bar bar = context.getBean(Bar.class);
が
val bar = context.getBean(Bar::class)
こう書けるし
val bar = context.getBean<Bean>()
こう書けるし
val bar: Bar = context.getBean()
こう書くこともできる。
今まで書いていた
Long count = jdbcTemplate.queryForObject("SELECT count(*) FROM foo", Long.class);
が
val count = jdbcTemplate.queryForObject("SELECT count(*) FROM foo", Long::class)
こう書けるし
val count = jdbcTemplate.queryForObject<Long>("SELECT count(*) FROM foo")
こう書けるし
val count: Long = jdbcTemplate.queryForObject("SELECT count(*) FROM foo")
こう書くこともできる。
特に恩恵を受けるのはRestTemplate
だろう。
String foo = restTemplate.getForObject("http://example.com", String.class);
が
val foo: String = restTemplate.getForObject("http://example.com")
書けるのはわかった。
けれども、
List<Foo> foos = restTemplate.exchange("http://api.example.com/foos", HttpMethod.GET, null, new ParameterizedTypeReference<List<Foo>>() { }).getBody();
これが
val foos: List<Foo> = restTemplate.getForObject("http://api.example.com/foos")
こう書けるようになるのは素晴らしくないか。
Spring 5ではこのようなKotlinによる改善が至るところで利用できるようになる。
その他
- Functional Router Functions
- Functional Bean Registration
といった、Spring 5自体の全く新しい機能にも初めからKotlinのExtensionsが用意されているし、
org.springframework.ui.Model
にArray like setterが追加され、
model.addAttribute("foo", foo);
を
model["foo"] = foo
と書けるようになったりする。
この記事を読んだ後なら
https://spring.io/blog/2017/01/04/introducing-kotlin-support-in-spring-framework-5-0
を読めるようになっていると思う。
ちなみにKotlinのメソッドがデフォルトでfinal
になるのでopen
を明示的につけなけらばいけなかった問題はkotlin-springプラグインで解決されている。
このあたりはSPRING INITIALIZRでLanguageにKotlinを選択することで初めから設定済みになっている。
なのですぐにSpring + Kotlinなアプリケーションを始めることができる。
Spring ❤️ Kotlin
Spring 5に備えてKotlinを学んでおきたければKotlinスタートブック -新しいAndroidプログラミングがとっつきやすい。この記事で説明したExtension FunctionsやReified type parametersにも触れられている。