Infinito Nirone 7

白羽の矢を刺すスタイル

Kotlin 1.4 の開発版を試す

やっていますか?

昨年の KotlinConf 2019 で Kotlin 1.4 についてのアナウンスがあり、今春のリリースを目指して開発が進んでいるそうです。 Releases · JetBrains/kotlin · GitHub を見ると、すでに現時点でも 1.4 向けの変更にタグが打たれているので、1.4-dev 版を試すことができるようになっています。

ビルドスクリプトとプロジェクトが見る Maven リポジトリを追加する

1.4-dev のタグがついた成果物は https://dl.bintray.com/kotlin/kotlin-dev/ に上がっています。 Gradle Plugin もここにあるので、Project Roon の build.gradle にある buildscript と、各モジュールの build.gradle にある repositories に次のようにして Maven リポジトリを設定しておきます。

maven {
  url "https://dl.bintray.com/kotlin/kotlin-dev/"
}

1.4-dev のビルド番号

この記事を書いた時点では一番新しいビルドとして 1.4.0-dev-1316 が Maven リポジトリにあり、ちょうど10秒くらい前に 1.4.0-dev-1325 のタグが GitHub に表示されるようになりました。そのうち 1.4.0-dev-1325 も Maven リポジトリに上がると思います。 ビルドスクリプトの dependencies には、implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.0-dev-1325" と記述すれば OK です。

注意

RC どころかアルファ版でもない開発版なので、ガンガン変更が入ります。1.4 でどうなるかちょっと試すくらいの気持ちで使ってください。

kotlinx.serialization でオブジェクトをシリアライズする #potatotips 67

potatotips #67 で kotlinx.serialization を使ったオブジェクトのシリアライズの手順について話してきたのでまとめておきます。いくつか発表のときにはできなかった部分を掘り下げています。

イベントリンク

potatotips #67 (iOS/Android開発Tips共有会) - connpass

資料

speakerdeck.com

kotlinx.serialization とは

Kotlin の各種オブジェクトを直列化して JSON や Protocol Buffers などのフォーマットに落とすためのしくみです。 2019年12月現在で最新版は 0.14.0 です。

導入

Project root にある build.gradle に kotlinx.serialization の classpath を追加し、各モジュールの build.gradle で apply plugin します。 Plugin のバージョンは Kotlin のバージョンと合わせます。

buildscript {
  ext.kotlin_version = '1.3.61'

  repositories {
    jcenter()
  }

  dependencies {
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
  }

}
apply plugin: "kotlin"
apply plugin: "kotlinx-serialization"

そして kotlinx-serialization-runtime を各モジュールの build.gradle の dependencies に追加します。

dependencies {
  implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
}

使い方

kotlinx.serialization では、オブジェクトを直列化する方法と、直列化したものを何らかのフォーマットに落とす方法が分かれていて、ここでは直列化する方法の部分を見ていきます。

まずクラスが Serializable かどうかをアノテーションで表明します。

import kotlinx.serialization.Serializable

@Serializable
data class SampleScreenState(
  val familyName: String,
  val givenName: String,
  val age: Int
)

メンバーがすべてプリミティブな型か、@Serializable がついている型の場合は自動で直列化のロジックも吐き出してくれます。これは直列化の方法を知っている KSerializer<T> の実装をプラグインが作ってくれて、そのインスタンスを返すメソッドを Companion Object に設定してくれるようになっています。

val sampleScreenState: SampleScreenState = // ...

// 直列化のロジックを持った KSerializer<SampleScreenState> の実装が得られる
val serializer = SampleScreenState.serializer()

次のように、@Serializable がついていない型をメンバーに持つ場合は、Serializer has not been found for type 'Any'. To use context serializer as fallback, explicitly annotate type or property with @ContextualSerialization というメッセージを吐き出してコンパイルエラーになります。なぜなら直列化の方法がわからないメンバーは直列化できないからです。java.io.Serializable とおなじく、@Serializable をつけたクラスのメンバーはすべてプリミティブな型 (IntDoubleString、あるいは enum class)か KSerializer<T> を定義した型 (@Serializable) でなければなりません。

シリアライズについての実装の間違いをコンパイル時に検知できてよいですね。

import kotlinx.serialization.Serializable

data class SampleData(
  val text: String
)

@Serializable
data class SampleScreenState(
  val familyName: String,
  val givenName: String,
  val age: Int,
  val something: SampleData = SampleData("sample") // ここがコンパイルエラー
)

SampleData を直列化するには、SampleData にも @Serializable をつけます。

import kotlinx.serialization.Serializable

@Serializable
data class SampleData(
  val text: String
)

@Serializable
data class SampleScreenState(
  val familyName: String,
  val givenName: String,
  val age: Int,
  val something: SampleData = SampleData("sample")
)

もしメンバーを直列化の対象外としたい場合は、@kotlinx.serialization.Transient アノテーションをつかいます。Kotlin JVM に標準の @kotlin.jvm.Transient ではないことに気をつけてください。

import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient

@Serializable
data class SampleScreenState(
  val familyName: String,
  val givenName: String,
  val age: Int,
  @Transient val something: SampleData = SampleData("sample")
)

KSerializer が生成できたら、あとはフォーマッターにシリアライズしたいオブジェクトと KSerializer を渡せば、好きなフォーマットに落とせます。

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

fun main() {
    val state = SampleScreenState(/* ... */)
    val json = Json(JsonConfiguration.Stable)

    val serialized: String = json.stringify(SampleScreenState.serializer(), state)

    val deserialized: SampleScreenState = json.parse(SampleScreenState.serializer(), serialized)
}

ライブラリに定義された型をシリアライズする

次のような型がライブラリに定義されているとします。

sealed class Abstract

class Sample4(
    val text: String,
    val number: Int
) : Abstract()

class class Sample5(
    val number: Int
) : Abstract()

ライブラリにあるので直接 @Serializable をつけられません。また KSerializer<T> も生成されないので自分で直列化の方法を定義する必要があり、上記の Abstract 型をメンバーとすると、Abstract 型と Sample4Sample5 の親子関係も知らせなければなりません。

kotlinx.serialization では、クラスの親子関係を後付けで定義するための仕組みとして SerializersModule を用意しています。これをつかって親子関係を知らせたうえで、Sample4Sample5 の直列化の方法も合わせて定義します。

import kotlinx.serialization.Decoder
import kotlinx.serialization.Encoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.internal.SerialClassDescImpl
import kotlinx.serialization.modules.SerializersModule

@UseExperimental(ImplicitReflectionSerializer::class)
// フォーマッタに渡して使う
val abstractTypeModule = SerializersModule {
    polymorphic<Abstract> {
        addSubclass(Sample4Serializer)
        addSubclass(Sample5Serializer)
    }
}

object Sample4Serializer : KSerializer<Sample4> {
    override val descriptor: SerialDescriptor = object : SerialClassDescImpl(Sample4::class.java.name) {
        init {
            addElement("string") // 0 番目のプロパティ
            addElement("number") // 1 番目のプロパティ
        }
    }

    override fun deserialize(decoder: Decoder): Sample4 {
        val structure = decoder.beginStructure(descriptor)
        val string = structure.decodeStringElement(descriptor, 0) // 0 番目のプロパティから String をデコードする
        val number = structure.decodeIntElement(descriptor, 1) // 1 番目のプロパティから Int をデコードする
        structure.endStructure(descriptor)
        return Sample4(string)
    }

    override fun serialize(encoder: Encoder, obj: Sample4) {
        val structure = encoder.beginStructure(descriptor)
        structure.encodeStringElement(descriptor, 0, obj.string) // 0 番目のプロパティに String をエンコードする
        structure.encodeIntElement(descriptor, 1, obj.number) // 1 番目のプロパティに Int をエンコードする
        structure.endStructure(descriptor)
    }
}

object Sample5Serializer : KSerializer<Sample5> {
    override val descriptor: SerialDescriptor = object : SerialClassDescImpl(Sample5::class.java.name) {
        init {
            addElement("number")
        }
    }

    override fun deserialize(decoder: Decoder): Sample5 {
        val structure = decoder.beginStructure(descriptor)
        val number = structure.decodeIntElement(descriptor, 0)
        structure.endStructure(descriptor)
        return Sample5(number)
    }

    override fun serialize(encoder: Encoder, obj: Sample5) {
        val structure = encoder.beginStructure(descriptor)
        structure.encodeIntElement(descriptor, 0, obj.number)
        structure.endStructure(descriptor)
    }
}

これらを使って次の Sample6 を JSON にフォーマットすると、{"abstractSample":{"type":"dev.keithyokoma.Sample4","string":"foo","number":1}}が出力されます。"abstractSample"に対応する部分が JsonObject となり、この JsonObject ではもとがどの型だったかを覚えておくために "type" に FQCN が出力されます。

import kotlinx.serialization.Polymorphic
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

@Serializable
data class Sample6(
    @Polymorphic val abstractSample: Abstract
)

fun main() {
    val json = Json(JsonConfiguration.Stable, abstractTypeModule)
    val serialized = json.stringify(Sample6.serializer(), Sample6(Sample4("foo", 1)))
    println(serialized)
}

sealed class のシリアライズ

sealed class もシリアライズできます。ただし、Kotlin と kotlinx.serialization のバージョンの組み合わせでJSON フォーマッタを使った場合の結果が変わります。

Kotlin 1.3.61 と kotlinx.serialization 0.14.0

次のように @Serializable で表明します。sealed class とその子クラスすべてに @Serializable をつけます。

import kotlinx.serialization.Serializable

@Serializable
sealed class Sealed

@Serializable
data class Sample2(
    val text: String
) : Sealed()

@Serializable
data class Sample3(
    val number: Int
) : Sealed()

@Serializable
data class Sample1(
    val text: String,
    val sealed: Sealed
)

これを次のように JSON でフォーマットすると、{"text":"foo","sealed":{"type":"dev.keithyokoma.Sample2","text":"bar"}} が出力されます。

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

fun main() {
    val sample = Sample1("foo", Sample2("bar"))
    val json = Json(JsonConfiguration.Stable)
    val serialized: String = json.stringify(Sample1.serializer(), sample)
    println(serialized)

Kotlin 1.3.30 以上 と kotlinx.serialization 0.11.0 から 0.13.0 まで

このバージョンでは、sealed class には @Serializable をつけずその子クラスに @Serializable をつけます。また sealed class の型のメンバーには @Polymorphic をつけます。 しかし実はこれだけでは不十分で、ライブラリの型をシリアライズするときに利用したSerializersModule で sealed class の親と子の継承関係を教えてあげる必要があります。今回は sealed class の子クラスには KSerializer がアノテーションで生成されるので、自分で記述することはありません。

import kotlinx.serialization.Polymorphic
import kotlinx.serialization.Serializable
import kotlinx.serialization.modules.SerializersModule

sealed class Sealed

@Serializable
data class Sample2(
    val text: String
) : Sealed()

@Serializable
data class Sample3(
    val number: Int
) : Sealed()

// フォーマッタに渡して使う
@UseExperimental(ImplicitReflectionSerializer::class)
val sampleTypeModule = SerializersModule {
    polymorphic<Sealed> {
        addSubclass<Sample2>()
        addSubclass<Sample3>()
    }
}

@Serializable
data class Sample1(
    val text: String,
    @Polymorphic val sealed: Sealed
)

これを次のように JSON でフォーマットすると、{"text":"foo","sealed":{"type":"dev.keithyokoma.Sample2","text":"bar"}} が出力されます。

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

fun main() {
    val sample = Sample1("foo", Sample2("bar"))
    val json = Json(JsonConfiguration.Stable, sampleTypeModule)
    val serialized: String = json.stringify(Sample1.serializer(), sample)
    println(serialized)

kotlinx.serialization 0.10.0 まで

このバージョンでは次の記述で問題なくシリアライズできます。

import kotlinx.serialization.Polymorphic
import kotlinx.serialization.Serializable

sealed class Sealed

@Serializable
data class Sample2(
    val text: String
) : Sealed()

@Serializable
data class Sample3(
    val number: Int
) : Sealed()

@Serializable
data class Sample1(
    val text: String,
    @Polymorphic val sealed: Sealed
)

これを次のようにフォーマットすると {"text":"foo","sealed":["dev.keithyokoma.Sample2",{"text":"bar"}]} が出力されます。"sealed"の構造に注目すると、kotlinx.serialization 0.10.0 では JsonArray にして出力することがわかります。JsonArray の最初の要素が元の型の FQCN で、2番目の要素にメンバーをフォーマットした JsonObject が入ります。

import kotlinx.serialization.json.Json

fun main() {
    val sample = Sample1("foo", Sample2("bar"))
    val json = Json()
    val serialized: String = json.stringify(Sample1.serializer(), sample)
    println(serialized)

互換モード

元がだどの型だったかを覚えておくための FQCN が 0.10.0 以前は JsonArray の最初の要素に入っていたのに対し、0.11.0 以降は JsonObject のプロパティに出力されます。これを、0.11.0 以降でも 0.10.0 以前のように JsonArray の形に出力して互換性を保つようにするオプションが用意されています。

val json = Json(JsonConfiguration.Stable.copy(useArrayPolymorphism = true), abstractTypeModule)

"type" という名前のメンバーを持つ型をシリアライズする

次のように、sealed class で表される型のひとつに type というメンバーを持つものがあるとします。

import kotlinx.serialization.Polymorphic
import kotlinx.serialization.Serializable

@Serializable
sealed class Sealed

@Serializable
data class Sample2(
    val type: String
) : Sealed()

@Serializable
data class Sample3(
    val number: Int
) : Sealed()

@Serializable
data class Sample1(
    val text: String,
    val sealed: Sealed
)

これをそのまま次のようにフォーマットすると、{"text":"foo","sealed":{"type":"dev.keithyokoma.Sample2","type":"bar"}}と出力されます。見事に "type" が 2 つできてしまいました。これをデシリアライズすると、2つ目の "type" を見に行ってしまい知らない型 (bar) になってしまいます。

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

fun main() {
    val json = Json(JsonConfiguration.Stable)
    val serialized = json.stringify(Sample1.serializer(), Sample1("foo", Sample2("bar")))
    println(serialized)
}

これを回避する方法は3つあります。

1つめはメンバーに Json にフォーマットするときの名前を与える方法です。次に示すように @SerialName で Json のプロパティ名を設定すると、結果は{"text":"foo","sealed":{"type":"dev.keithyokoma.Sample2","my_type":"bar"}}となります。

@Serializable
data class Sample2(
    @SerialName("my_type")
    val type: String
) : Sealed()

2つめはJsonの設定で FQCN を入れるプロパティ名を変える方法です。JsonConfiguration#classDiscriminator に任意の文字列を指定して、FQCN のプロパティ名が変えられます。次の例では {"text":"foo","sealed":{"clazz":"dev.keithyokoma.Sample2","type":"bar"}} を出力します。

import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

fun main() {
    val json = Json(JsonConfiguration.Stable.copy(classDiscriminator = "clazz"))
    val serialized = json.stringify(Sample1.serializer(), Sample1("foo", Sample2("bar")))
    println(serialized)
}

3つめは先ほどの互換モードを利用する方法です。JsonArray の要素の位置で決め打ちになるので名前が衝突することがなくなります。

OAuth 2.0 の Redirect URI で戻ってくる画面に Navigation Architecture Component の DeepLink を設定すると直面する問題と回避策

みなさん、やっていますか?私はやっています。

Slack API を使いたくて、Slack の OAuth の仕様通り Redirect URI を設定してアプリに auth codestate を戻すために Navigation Architecture Component を使った DeepLink の実装をしたところ、盛大にハマったので記録を残しておきます。

続きを読む

つよいレンズをもっていろいろ写真を撮ってきた

つい先日、Canon EF 24-70mm f/2.8L II USM というつよいレンズを購入しました。

目的はカンファレンス会場内でバッチリ撮影できるレンズをもつことなわけですが、素振りも必要ということで、RedBull Air Race に誘ってもらったのを機に写真を撮ったのと、DroidKaigi roadshow 福岡へ行ったのを機にイベントの撮影をしつつ翌日門司港でフラフラしたときに写真を撮ってきました。

RedBull Air Race

今回でシリーズそのものが終了となる RedBull Air Race。日本人パイロットが活躍している事は知っていたものの、競技のルールなどは予備知識ゼロで行きました。会場での解説を聴きながらでもルールが理解できるようになっていていいですね。

f:id:KeithYokoma:20190907135119j:plain

24-70mm なのでもちろん寄れないんですが、飛行機にフォーカスを合わせつつ観戦している人々をフレームに入れるにはちょうどいい感じでした。

f:id:KeithYokoma:20190907141904j:plain

f:id:KeithYokoma:20190907141906j:plain

門司港

戦時中、祖父が輸送船の乗組員だったころに来たことがあるという門司港に行ってみようと思って来てみました。ほぼ何も調べずに来てしまいましたが、地ビール醸造所があったり、唐揚げフェスをやっていたり、観光客向けに展望デッキがあったりと、短時間でもスッと楽しめる街でした。 町並みもレトロな感じでいいですね。

f:id:KeithYokoma:20190915140953j:plain

門司港に来るのが初めてなので、関門海峡を眺めるのも初めてです。

f:id:KeithYokoma:20190915141306j:plain

クラシックカーのあつまりもありました。かっこいいですね。

f:id:KeithYokoma:20190915141620j:plain

f:id:KeithYokoma:20190915143650j:plain

f:id:KeithYokoma:20190915144344j:plain

f:id:KeithYokoma:20190915144917j:plain

ゴンチャではなくコンチャが出店してました。

f:id:KeithYokoma:20190915150346j:plain

f:id:KeithYokoma:20190915152657j:plain

f:id:KeithYokoma:20190915155734j:plain

帰りは飛行機をいくつか。

f:id:KeithYokoma:20190915181141j:plain

f:id:KeithYokoma:20190915181148j:plain

f:id:KeithYokoma:20190915181150j:plain

f:id:KeithYokoma:20190915212019j:plain

What's next?

望遠のつよいやつほしい

Kotlin の enum class とシリアライズで気をつけること

Kotlin の enum クラスは、ほぼ Javaenum と同じように扱うことができます。

次の Kotlin コードは FOOBAR という Hoge 型の定数を定義しています。

enum class Hoge {
  FOO,
  BAR
}

enum クラスで定義した定数は String への変換 (nameメソッド) と、 String からの変換 (valueOfメソッド) をサポートしているので、簡単にシリアライズ・デシリアライズができます。またenum クラスに振る舞いを記述できるので、フィールドをもたせたり、メソッドを呼び出したりするコードも動きます。

enum class Hoge(val flag: Boolean) {
  FOO(true),
  BAR(false);

  fun getSomething(): String {
    return "Hello, world!"
  }
}

メソッドは abstract で定義したり、インタフェースを実装したりもできます。この場合、個々の定数の定義で具体的な処理を記述します。

enum class Hoge {
  FOO {
    override fun getSomething(): String {
      return "Hello, world!"
    }
  },
  BAR {
    override fun getSomething(): String {
      return "Hi, world!"
    }
  };

  abstract fun getSomething(): String
}

ここまでがおさらいです。

ここで次のように String? から任意の型のオブジェクトへ変換する Generic なメソッドを考えます。ストレージに書き込んだ文字列から期待する型への変換を行うようなメソッドです。

@Suppress("UNCHECKED_CAST")
fun <T : Any> String?.convert(fallback: T): T {
  return when (fallback) {
    is String -> {
      this ?: fallback
    }
    is Int -> {
      this?.let {
        Integer.valueOf(this)
      } ?: 0
    }
    is Enum<*> -> {
      this?.let {
        val method = fallback.javaClass.getDeclaredMethod("valueOf", String::class.java)
        method.invoke(null, this)
      } ?: fallback
    }
    else -> {
      throw IllegalArgumentException("")
    }
  } as T
}

このメソッドを、次のように標準入力で与えられた文字列からデシリアライズする目的で使ってみましょう。標準入力で FOO と入力すると、正しく定数に変換されます。

fun main(args: Array<String>) {
  println(readLine().convert<Hoge>(Hoge.FOO))
}

enum class Hoge {
  FOO,
  BAR
}

では次の場合はどうなるでしょうか。Hoge には abstract メソッドが定義されています。

fun main(args: Array<String>) {
  println(readLine().convert<Hoge>(Hoge.FOO))
}

enum class Hoge {
  FOO {
    override fun getSomething(): String {
      return "Hello, world!"
    }
  },
  BAR {
    override fun getSomething(): String {
      return "Hi, world!"
    }
  };

  abstract fun getSomething(): String
}

上記のコードの場合、FOO を標準入力に入れると次のようなエラーで異常終了します。

Exception in thread "main" java.lang.NoSuchMethodException: dev.keithyokoma.Hoge$FOO.valueOf(java.lang.String)
    at java.lang.Class.getDeclaredMethod(Class.java:2130)
    at dev.keithyokoma.MainKt.convert(Main.kt:22)
    at dev.keithyokoma.MainKt.main(Main.kt:6)

この挙動の差は生成されるバイトコードの違いを見ると分かります。

メソッドの定義がなかったり、実装を enum クラスの宣言に書く場合は次のようなバイトコードが生成されます。

public final enum dev/keithyokoma/Hoge extends java/lang/Enum  {
  // access flags 0x4019
  public final static enum Ldev/keithyokoma/Hoge; FOO
  // access flags 0x4019
  public final static enum Ldev/keithyokoma/Hoge; BAR
}

一方で、abstract メソッドやインタフェースのメソッドの実装を個別の定数で行う場合は次ようなバイトコードが生成されます。

public abstract enum dev/keithyokoma/Hoge extends java/lang/Enum  {

  // access flags 0x18
  final static INNERCLASS dev/keithyokoma/Hoge$FOO dev/keithyokoma/Hoge FOO
  // access flags 0x18
  final static INNERCLASS dev/keithyokoma/Hoge$BAR dev/keithyokoma/Hoge BAR

  // access flags 0x4019
  public final static enum Ldev/keithyokoma/Hoge; FOO
  // access flags 0x4019
  public final static enum Ldev/keithyokoma/Hoge; BAR
}

なにやら増えていますね。内部クラスとして Hoge$FOOHoge$BAR があるように見えます。そして実行時のエラーでは、Hoge$FOO に対して valueOf メソッドを探したが見つからなかった、というメッセージが出ています。

enum クラス内の個別の定数で実装を定義する場合 Hoge.FOOHoge$FOO 型で、Hoge.BARHoge$BAR 型として解釈されます。これらはすべて単なるクラスなので、Generic な型変換コードの val method = fallback.javaClass.getDeclaredMethod("valueOf", String::class.java)valueOf メソッドを見つけられません (Hoge$FOO 型の Hoge.FOO が格納された fallback 変数に対し valueOf を探そうとするが、valueOf メソッドは Hoge のクラスメソッドとして定義されるので見つからない)。

Hoge$FOO 型と Hoge$BAR 型はどちらも Hoge 型のサブクラスになっているので、次のように修正すると期待通りの動作をします。 fallback の型が enum かどうかを判別し、enum でなければその親クラスから valueOf メソッドを探します。

@Suppress("UNCHECKED_CAST")
fun <T : Any> String?.convert(fallback: T): T {
  return when (fallback) {
    // ...
    is Enum<*> -> {
      this?.let {
        val clazz = fallback.javaClass
        val method = if (clazz.isEnum) {
          clazz.getDeclaredMethod("valueOf", String::class.java)
        } else {
          clazz.superclass.getDeclaredMethod("valueOf", String::class.java)
        }
        method.invoke(null, this)
      } ?: fallback
    }
    // ...
  } as T
}

コンパイラの吐き出すバイトコードまでを考慮しないといけないコードになってしまいました (enum クラスは文法上継承できないはずなので、isEnumfalse のときに superclass を参照しにいくのは一見とても奇妙)。

ちなみに、Java で同様のコードを書いて検証してみたところ、Java でも同じ挙動をしました。

もし、型パラメータの T から直接 Class クラスを参照できれば、このような回りくどい記述は必要ありません。 次のコードでは正しく valueOf メソッドが探せます。

inline fun <reified T : Any> String?.convert(fallback: T): T {
  return when (fallback) {
    // ...
    is Enum<*> -> {
      this?.let {
        val method = T::class.java.getDeclaredMethod("valueOf", String::class.java)
        method.invoke(null, this)
    } ?: fallback
  }
  // ...
  } as T
}

2019/09/03 16:00 追記

型パラメータ TEnum<T> に限定できる場合は、リフレクションは不要になり、代わりに enumValueOf を使います (thank you @red_fat_daruma !)。

inline fun <reified T : Enum<T>> String?.convert(fallback: T): T {
  return this?.let { value ->
    enumValueOf<T>(value)
  } ?: fallback
}

カンファレンスとハイエースと筋肉

カンファレンスとハイエース

カンファレンスを運営するにあたって、当日使用する物資の運搬は非常に重要です。登壇者の後ろに配置するバックパネルや会場案内図、立て看板など、重量があったり、縦横の大きさがそこそこあるものなどの運搬について、一般的な配送方法(宅配便など)では運搬できなかったり、業者に依頼する量ではないにしても電車等での公共交通機関での運搬は不可能な状況があったりします。このような場合に、自分たちで物資を運搬する手段を確保するというオプションがあります。

自分たちで物資の運搬をする手段をとる場合、前提として、大きなサイズの車を運転できる人の確保と、物資輸送にあたって重量物や大きな物を運ぶ体力・筋力の確保が要求されます。 前者については実際に運搬する物資の物量に依存しますが、軽ワゴン車で足りる場合から、ハイエースやキャラバンなどのバンですら容量不足で2tトラックが必要な場合まで様々で、免許区分で NG となることもあります。後者については、バン以上の車であれば、運転手・助手席の間にさらにもう一席あるので、3人の体力・筋力が得られます。

2019年現在の普通免許を持っていればバンなら運転可能です。私の免許では5tトラック(最大積載量3t)までなら運転可能ですが、今までで運転したことのある一番大きな車はハイエースやキャラバンなどのバンに相当する車です。カンファレンスでの物資輸送以外に、自宅の引っ越しでハイエースを使って自力輸送をしたこともあります。

この記事では、タイトルにあるとおりバンを用いたカンファレンスにおける物資輸送について、いくらかの Tips を書き残しておこうと思います。

輸送計画

まず物資輸送の計画を立てるにあたって、運転手と車両の確保を考えることになります。 運転手についてはバンの運転ができる人を地道に探すしかありませんが、車両の確保は比較的容易で、レンタカーでバンを借りれます(もちろん、運営チーム内でバンまたはそれに相当する輸送力をもった車両を所有している人がいれば、その人に協力を仰ぐのが最適なはずです)。昨今はカーシェアリングサービスも盛んですが、物資輸送に最適なバンを借りるのであれば、トヨタレンタカーやニッポンレンタカー、ジャパンレンタカー、オリックスレンタカーが大手で取り扱っています。トヨタレンタカーであれば確実にハイエースを借りられます。

レンタカーで物資輸送を計画する場合、借りる時間をどう設定するかが問題になります。レンタカーである以上契約で使える時間に制約があるため、借りる手続きをしてから荷物をピックアップし、荷物を保管する場所まで移動したあとでその荷物を人手で積み下ろしたうえで、給油して返却するまでが計画されなければなりません。レンタカーを借りる店舗ごとに営業時間が異なるので、24時間営業でない場合には特に返却時間に気を配る必要があります(24時間営業であっても、22時以降の返却には料金の上乗せがあったりしますし、借りた店舗とは異なる店舗で返却するワンウェイレンタルに制約があることも多いです)。

レンタカーでもカーナビが標準装備とはいえ、運転者はどういう経路で物資を運搬するかをよく把握し、返却にあたっては店舗の最寄りにあるガソリンスタンドを把握するなど、経路について事前によく調べておく必要があります。また、荷捌き場への入り方はビルごとに異なるため、事前によく認識を合わせておきましょう。

運転

ハイエースやキャラバンなどバンに相当する車両はそのサイズ感から敬遠されがちだったり、用途からして普段遣い向きではなかったりすることから運転したくないと思われがちですが、扱いづらい車ではないように思います。

よく利用されるコンパクトカーやミニバンとの一番おおきな差異はやはり車体の大きさでしょう。車体そのものが長く幅も広いなど、とにかく小回りとは無縁そうなイメージがありますが、その車体の見た目に反して小回りはきくほうです。ミニバンよりも大きいように見えて、実はミニバンよりも小回りがきくなんてこともあります。

バンとその他の車で違うこともいくつかあります。

パーキングブレーキは車種によってまちまちですが、トヨタハイエースならば左手側に引っ張るレバー(ステッキ式)があり、日産・キャラバンであれば左側にフットペダルがあります。ステッキ式は取っ手にボタンが付いていて、パーキングブレーキを解除するときにそのボタンを押しながらすこし引っ張って解除します。 サイドミラーは手動で調整するものがあります。レンタカー屋さんで借りる車の年式などで変わるので、調整が必要なときは出発前に見ておきましょう。 その他、前輪の位置がミニバンとは違って運転席の真下にくるので、ハンドルをきるタイミングに気をつけましょう。もちろん、荷物が空の状態とある程度積んだ状態では重量が違うので、ブレーキをかけるタイミングやらアクセルの踏み方も状況に応じて調整しましょう。

荷物の積み込み

バンは荷室がかなり広く使えます。ただ後輪のでっぱりや両側スライドドアのステップがあったりするので、純粋に長方形のスペースをフルに使えるわけではないことに注意します。 最近のレンタカーではバックカメラがついていて、バックギアに入れるとカーナビの画面がカメラの映像に切り替わるようになっていますが、とはいえミラーから見える視界を遮るほどのたかさまで荷物を積んでしまうのもよくないので、荷物の積み上げは程々に。また角の硬い荷物を積むときは緩衝材で保護するなどして他の荷物や車内を傷つけないようにしましょう。

積み込む量にもよりますが、ハイエースを借りるということはそこそこの物資を積み込み・積み下ろしするはずなので、荷運び人員が必要です。カンファレンス会場の荷捌き場との行き来もあるので、それなりの筋力が求められます。台車がある場合はこれを活用しましょう。レンタカー屋さんで借りることもできます。ただし会場によっては台車の使用に制限(台車を使用していい出入り口やエレベータに限りがあるなど)を設定しているところもあるので、会場のルールに従って運搬します。

重いものがあるときは無理せず複数人でゆっくり運びましょう。とくに腰は大事です。持ち上げる・下ろすときに腰を壊しやすいので、しっかり腰を落として持ちましょう。

まとめ

ハイエースやキャラバンなどのバンは荷物を運搬するために車体が大きいものの、扱いやすさで言えばミニバンよりも小回りがきくなど便利に使えます。荷室も広いのでかなりの物資を輸送できますが、輸送にあたっては人手も必要です。怪我や事故の無いよう安全に十分配慮しつつ荷物を運びましょう。