イディオマティックコトリン、一連の優れた実践





Kotlinのすべての利点を完全に明らかにするために、 Javaで使用しているアプローチのいくつかを見てみましょう。 それらの多くは、 Kotlinの最高のアナログに置き換えることができます。 慣用的なKotlinコードを書く方法を見てみましょう。



慣用的なコトリンに関する記事の翻訳を紹介します。 著者は多くの点で、 Kotlinでの記述方法と、言語の組み込み機能を使用してシンプルで表現力豊かなコードを記述する方法を非常によく示しました。



注:以下のリストは完全なものではなく、私の謙虚な意見を表しています。 さらに、言語の一部の機能は細心の注意を払って使用する必要があります。 悪用されると、コードが読みにくくなる可能性があります。 たとえば、すべてを1つの判読できない式に圧縮しようとする場合。

フィリップ・ハウル




Kotlinの組み込み機能と一般的なJavaパターンおよびイディオムとの比較。



Javaでは、いくつかのパターンとイディオムを実装するために非常に多くの定型コードを記述する必要があります。 幸いなことに、多くのパターンにはKotlin言語またはその標準ライブラリに直接サポートが組み込まれています。

Javaイディオムまたはパターン Kotlinの実装
オプショナル ヌル値
ゲッター、セッター、 バッキングフィールド 物性
静的ユーティリティクラス トップレベル関数、拡張関数
不変性、値オブジェクト 不変のプロパティを持つdata class



copy()



流Fluなセッター(Wither) 名前付き引数、およびデフォルト値を持つ引数、 apply()



メソッドチェーン デフォルト値を持つ引数
シングルトン(シングルトン) object



委任(委任) 財産の委任
遅延初期化(スレッドセーフ) プロパティの委任by: lazy()



オブザーバー プロパティ委任by: Delegates.observable()





関数型プログラミング



他の利点の中でも、関数型プログラミングは副作用を減らすことができ、それによりコードが次のようになります。



-起こりやすいエラー

-わかりやすい

-テストが簡単

-スレッドセーフ



Java 8と比較して、 Kotlinはより優れた関数型プログラミングサポートを備えています。

-不変性、変数とプロパティのval



、不変data classes



copy()





-すべての式は結果を返します: if



when



およびtry-catch



は式です。 これらを他の式や関数と組み合わせることができます。

-ファーストクラスタイプとして機能

-簡潔なラムダ式

-Kotlin Collections API



これにより、機能的コードを安全かつ簡潔で表現力豊かな方法で記述することができます。 その結果、純粋な関数(副作用なし)をはるかに簡単に作成できます。



式の使用:



 // Don't fun getDefaultLocale(deliveryArea: String): Locale { val deliverAreaLower = deliveryArea.toLowerCase() if (deliverAreaLower == "germany" || deliverAreaLower == "austria") { return Locale.GERMAN } if (deliverAreaLower == "usa" || deliverAreaLower == "great britain") { return Locale.ENGLISH } if (deliverAreaLower == "france") { return Locale.FRENCH } return Locale.ENGLISH }
      
      





 // Do fun getDefaultLocale2(deliveryArea: String) = when (deliveryArea.toLowerCase()) { "germany", "austria" -> Locale.GERMAN "usa", "great britain" -> Locale.ENGLISH "france" -> Locale.FRENCH else -> Locale.ENGLISH }
      
      





経験則: if



を記述if



たびにif



when



を使用して短いレコードに置き換えることができることに留意してください。



try-catch



も便利な式です。



 val json = """{"message":"HELLO"}""" val message = try { JSONObject(json).getString("message") } catch (ex: JSONException) { json }
      
      





トップレベル関数、拡張関数



Javaでは、ユーティリティの静的メソッドを使用して静的クラスを作成することがよくあります。 Kotlinでのこのパターンの直接実装は次のようになります。



 //Don't object StringUtil { fun countAmountOfX(string: String): Int{ return string.length - string.replace("x", "").length } } StringUtil.countAmountOfX("xFunxWithxKotlinx")
      
      





Kotlinでは、トップレベル関数を使用して、クラス内の不要なラッピングを削除できます。 多くの場合、拡張機能を追加して読みやすくすることもできます。 したがって、コードは「ストーリーテリング」のようになります。



 //Do fun String.countAmountOfX(): Int { return length - replace("x", "").length } "xFunxWithxKotlinx".countAmountOfX()
      
      





Fluent Setterではなく名前付き引数。



Javaに戻ると、 流れるようなセッター (「ウィザー」とも呼ばれます)を使用して、名前付き引数と引数をデフォルト値でエミュレートします。 これにより、パラメーターリストをより読みやすく、エラーが少なくなります。



 //Don't val config = SearchConfig() .setRoot("~/folder") .setTerm("kotlin") .setRecursive(true) .setFollowSymlinks(true)
      
      





Kotlinでは、名前付き引数とデフォルト値を持つ引数は同じ目的を果たしますが、同時に言語自体に組み込まれます:



 //Do val config2 = SearchConfig2( root = "~/folder", term = "kotlin", recursive = true, followSymlinks = true )
      
      





オブジェクト初期化呼び出しを結合するためにapply()





 //Don't val dataSource = BasicDataSource() dataSource.driverClassName = "com.mysql.jdbc.Driver" dataSource.url = "jdbc:mysql://domain:3309/db" dataSource.username = "username" dataSource.password = "password" dataSource.maxTotal = 40 dataSource.maxIdle = 40 dataSource.minIdle = 4
      
      





apply()



拡張関数は、オブジェクトの初期化コードを組み合わせるのに役立ちます。 さらに、変数の名前を何度も繰り返す必要はありません。



 //Do val dataSource = BasicDataSource().apply { driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://domain:3309/db" username = "username" password = "password" maxTotal = 40 maxIdle = 40 minIdle = 4 }
      
      





apply()



は、 Kotlinの Javaライブラリとやり取りするときにも非常に便利です。



デフォルト値で引数をシミュレートするためにメソッドをオーバーロードする必要はありません



引数をデフォルト値で実装するためにメソッドとコンストラクターをオーバーロードする必要はありません(メソッドチェーン「メソッドチェーン」またはコンストラクターチェーン「コンストラクターチェーン」とも呼ばれます)



 //Don't fun find(name: String){ find(name, true) } fun find(name: String, recursive: Boolean){ }
      
      





これはすべて松葉杖です。 このため、 Kotlinにはデフォルト値の引数があります。



 //Do fun (name: String, recursive: Boolean = true){ }
      
      





実際、デフォルト値を持つ引数を使用すると、ほとんどの場合、デフォルト値を持つ引数を作成するためにオーバーロードが使用されるため、メソッドおよびコンストラクターのオーバーロードのほとんどすべてのケースを削除できます。



Nullabilityを使用した簡潔さと簡潔さ



if-null



チェックを避けます。



null



をチェックするJavaメソッドnull



扱いにくく、エラーを簡単にスキップできます。



 //Don't if (order == null || order.customer == null || order.customer.address == null){ throw IllegalArgumentException("Invalid Order") } val city = order.customer.address.city
      
      





null



チェックを書き込むたびに停止します。 Kotlinは、このような状況に対処する簡単な方法を提供します。 多くの場合、安全な通話を使用できます?.



または単に演算子「elvis」 ?:







 //Do val city = order?.customer?.address?.city ?: throw IllegalArgumentException("Invalid Order")
      
      





型チェックを避ける



上記のすべては、型チェックにも当てはまります。



 //Don't if (service !is CustomerService) { throw IllegalArgumentException("No CustomerService") } service.getCustomer()
      
      





as?



を使用as?



および?:



型を確認したり、適切な型に自動的に変換したり(スマートキャスト)、型が予期したものでない場合は例外をスローしたりできます。 すべて1つの表現で!



 //Do service as? CustomerService ?: throw IllegalArgumentException("No CustomerService") service.getCustomer()
      
      





でチェックせずに呼び出しを避ける!!





 //Don't order!!.customer!!.address!!.city
      
      





きっとあなたはそれに気づいた!!



彼らはかなり荒く見えます。 これは実際にコンパイラーに叫ぶ方法です。 したがって、偶然に見えることはありません。 Kotlinの開発者は、コンパイラーによって検証できない式を使用しないように、より良い解決策を見つけるために少しだけ微調整しようとしています。

「Kotlin in Action」、Dmitry Zhemerov、Svetlana Isakova。


let()



使用します



状況によっては、 let()



if



を置き換えることができます。 ただし、コードを読みやすくするために、注意して使用する必要があります。 ただし、 let()



使用について考えてほしい。



 val order: Order? = findOrder() if (order != null){ dun(order.customer) }
      
      





let()



、追加の変数は必要ありません。 そのため、1つの式を扱っています。



 findOrder()?.let { dun(it.customer) } //or findOrder()?.customer?.let(::dun)
      
      





値オブジェクトの使用



data classes



不変の値オブジェクトを非常に簡単に記述できます。 プロパティが1つだけ含まれている場合でも。 それらを使用しない理由はもうありません。



 //Don't fun send(target: String){}
      
      





 //Do fun send(target: EmailAddress){} // expressive, readable, type-safe data class EmailAddress(val value: String)
      
      





単一式関数



 // Don't fun mapToDTO(entity: SnippetEntity): SnippetDTO { val dto = SnippetDTO( code = entity.code, date = entity.date, author = "${entity.author.firstName} ${entity.author.lastName}" ) return dto }
      
      





単一の式と名前付き引数で構成される関数を使用すると、オブジェクト間の関係を簡単に、簡潔に、そして表現力豊かに説明できます。



 // Do fun mapToDTO(entity: SnippetEntity) = SnippetDTO( code = entity.code, date = entity.date, author = "${entity.author.firstName} ${entity.author.lastName}" ) val dto = mapToDTO(entity)
      
      





拡張機能を使用する場合は、それらを使用して宣言を作成し、同時により簡潔で表現力豊かに使用できます。 同時に、追加のロジックで値オブジェクトを汚染しません。



 // Do fun SnippetEntity.toDTO() = SnippetDTO( code = code, date = date, author = "${author.firstName} ${author.lastName}" ) val dto = entity.toDTO()
      
      





プロパティの初期化でコンストラクターオプションを使用することをお勧めします。



プロパティを初期化するためだけに、コンストラクターの本体で初期化ブロック(initブロック)を使用する前によく考えてください。



 // Don't class UsersClient(baseUrl: String, appName: String) { private val usersUrl: String private val httpClient: HttpClient init { usersUrl = "$baseUrl/users" val builder = HttpClientBuilder.create() builder.setUserAgent(appName) builder.setConnectionTimeToLive(10, TimeUnit.SECONDS) httpClient = builder.build() } fun getUsers(){ //call service using httpClient and usersUrl } }
      
      





プロパティの初期化では、メインコンストラクターのパラメーターを参照できることに注意してください( init



ブロック内だけでなく)。 apply()



は、グループの初期化コードを支援し、単一の式と協調することもできapply()







 // Do class UsersClient(baseUrl: String, appName: String) { private val usersUrl = "$baseUrl/users" private val httpClient = HttpClientBuilder.create().apply { setUserAgent(appName) setConnectionTimeToLive(10, TimeUnit.SECONDS) }.build() fun getUsers(){ //call service using httpClient and usersUrl } }
      
      





ステートレスインターフェイス実装のobject





Kotlinの object



は、状態を保存しないフレームワークインターフェイスを実装する必要がある場合に便利です。 たとえば、 Vaadin 8の Converterインターフェイス。



 //Do object StringToInstantConverter : Converter<String, Instant> { private val DATE_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss Z") .withLocale(Locale.UK) .withZone(ZoneOffset.UTC) override fun convertToModel(value: String?, context: ValueContext?) = try { Result.ok(Instant.from(DATE_FORMATTER.parse(value))) } catch (ex: DateTimeParseException) { Result.error<Instant>(ex.message) } override fun convertToPresentation(value: Instant?, context: ValueContext?) = DATE_FORMATTER.format(value) }
      
      





Kotlin、Spring Boot、Vaadinの相互作用の詳細については、 この投稿を参照してください。



破壊する



一方では、関数から複数の値を返す必要がある場合に、構造化が役立ちます。 独自のdata class



使用するか(望ましい)、またはPair



使用できます(ペアはセマンティクスを保持しないため、表現力が低下します)。



 //Do data class ServiceConfig(val host: String, val port: Int) fun createServiceConfig(): ServiceConfig { return ServiceConfig("api.domain.io", 9389) } //destructuring in action: val (host, port) = createServiceConfig()
      
      





一方、 map



から要素を複数回検索するには、構造化が便利です。



 //Do val map = mapOf("api.domain.io" to 9389, "localhost" to 8080) for ((host, port) in map){ //... }
      
      





構造を作成するための特別な設計



listOf



listOf



、および関数へのインlistOf



、構造( JSONなど)をすばやく作成するために使用できます。 もちろん、これはまだPythonJavaScriptほどコンパクトではありませんが、Javaよりも優れています。



注:Andrei Breslavは最近、Jpoint 2017で、これを改善する方法を検討していると述べたため、近い将来にいくつかの改善が期待できます。



 //Do val customer = mapOf( "name" to "Clair Grube", "age" to 30, "languages" to listOf("german", "english"), "address" to mapOf( "city" to "Leipzig", "street" to "Karl-Liebknecht-Straße 1", "zipCode" to "04107" ) )
      
      





確かに、通常、 data class



またはオブジェクトマッピングを使用してJSONを作成する必要があります。 しかし、時々(テストを含む)、そのようなレコードは非常に便利です。



ソースコード



ソースコードは、私のgithubの慣用的なkotlinプロジェクトにあります。



この翻訳がお役に立てば幸いです。 翻訳の不正確さや誤りに気づいたすべての人々に非常に感謝し、通信でこれについて書きます。

ご清聴ありがとうございました!



All Articles