Upgrade to Pro — share decks privately, control downloads, hide ads and more …

みんな大好き拡張関数 #kotlin_sansan

みんな大好き拡張関数 #kotlin_sansan

第6回 Kotlin勉強会 in Sansan

Jumpei Yamamoto

June 29, 2017
Tweet

More Decks by Jumpei Yamamoto

Other Decks in Programming

Transcript

  1. Copyright © Sansan, Inc. All rights reserved. > みんな⼤好き拡張関数 Jumpei

    Yamamoto 2017.6.29 第6回 Kotlin勉強会 in Sansan #kotlin_sansan
  2. Copyright © Sansan, Inc. All rights reserved. > ⾃⼰紹介 -

    ⼭本純平 - twitter: @boohbah - github: https://github.com/yamamotoj
  3. Copyright © Sansan, Inc. All rights reserved. > 便利! fun

    String.lastChar() = get(length - 1) print(“Kotlin”.lastChar()) >> n
  4. Copyright © Sansan, Inc. All rights reserved. > propertyアクセス val

    String.isNumeric get() = StringUtils.isNumeric(this) “30”.isNumeric // -> True
  5. Copyright © Sansan, Inc. All rights reserved. > いくつかの疑問がありますね? -

    どういう仕組み? - 元のクラスとメソッドが被ったら? - 元のクラスが継承している場合は? - 標準的なクラスに、どんどんメソッドを追加 していったらメンテナンスが⼤変そう
  6. Copyright © Sansan, Inc. All rights reserved. > どういう仕組み? (StringUtils.kt)

    fun String.lastChar() = get(length - 1) // Java͔Βݺͼग़͢ StringUtilsKt.lastChat(“Java”);
  7. Copyright © Sansan, Inc. All rights reserved. > どういう仕組み? (StringUtils.kt)

    fun String.lastChar() = get(length - 1) // Java͔Βݺͼग़͢ StringUtilsKt.lastChat(“Java”); 内部的にはthisを引数にした static methodの呼び出し
  8. Copyright © Sansan, Inc. All rights reserved. > どういう仕組み? (StringUtils.kt)

    fun String.lastChar() = get(length - 1) // Java͔Βݺͼग़͢ StringUtilsKt.lastChat(“Java”); ↑Ktまで含めたクラス名
  9. Copyright © Sansan, Inc. All rights reserved. > 元のメソッドと被ったら? class

    Universe{ fun answer() = 42 } fun Universe.answer() = 100 Universe().answer() // -> 42
  10. Copyright © Sansan, Inc. All rights reserved. > 元のメソッドと被ったら? class

    Universe{ fun answer() = 42 } fun Universe.answer() = 100 Universe().answer() // -> 42 必ず、定義元のクラスのメンバーが実⾏される 拡張関数が実⾏されることはない
  11. Copyright © Sansan, Inc. All rights reserved. > 他の拡張メソッドと名前が被る場合 //

    Extension1.kt package hoge.extension1 fun SomeClass.hello() = println(“hello”) ————— // Extension2.kt package hoge.extension2 fun SomeClass.hello() = println(“͜Μʹͪ͸”) 違うパッケージなら定義可能
  12. Copyright © Sansan, Inc. All rights reserved. > 他の拡張メソッドと名前が被る場合 //

    Extension1.kt package hoge.extension1 fun SomeClass.hello() = println(“hello”) ————— // Extension2.kt package hoge.extension2 fun SomeClass.hello() = println(“͜Μʹͪ͸”) import hoge.extension1.hello SomeClass().hello() // -> hello
  13. Copyright © Sansan, Inc. All rights reserved. > 他の拡張メソッドと名前が被る場合 //

    Extension1.kt package hoge.extension1 fun SomeClass.hello() = println(“hello”) ————— // Extension2.kt package hoge.extension2 fun SomeClass.hello() = println(“͜Μʹͪ͸”) import hoge.extension2.hello SomeClass().hello() // -> ͜Μʹͪ͸
  14. Copyright © Sansan, Inc. All rights reserved. > 元のクラスが継承されている場合 open

    class ParentClass class ChildClass: ParentClass() fun ParentClass.greeting() = println("hello!") ParentClass().greeting() >> hello! ChildClass().greeting() >> hello!
  15. Copyright © Sansan, Inc. All rights reserved. > 元のクラスが継承されている場合 open

    class ParentClass class ChildClass: ParentClass() fun ParentClass.greeting() = println("hello!") fun ChildClass.greeting() = println(“good bye!”) ParentClass().greeting() >> hello! ChildClass().greeting() >> good bye!
  16. Copyright © Sansan, Inc. All rights reserved. > 元のクラスが継承されている場合 open

    class ParentClass class ChildClass: ParentClass() fun ParentClass.greeting() = println("hello!") fun ChildClass.greeting() = println(“good bye!”) fun printGreeting(obj: ParentClass) { obj.greeting() }
  17. Copyright © Sansan, Inc. All rights reserved. > 元のクラスが継承されている場合 open

    class ParentClass class ChildClass: ParentClass() fun ParentClass.greeting() = println("hello!") fun ChildClass.greeting() = println(“good bye!”) fun printGreeting(obj: ParentClass) { obj.greeting() } printGreeting(ChildClass()) >> hello!
  18. Copyright © Sansan, Inc. All rights reserved. > 元のクラスが継承されている場合 open

    class ParentClass class ChildClass: ParentClass() fun ParentClass.greeting() = println("hello!") fun ChildClass.greeting() = println(“good bye!”) fun printGreeting(obj: ParentClass) { obj.greeting() } printGreeting(ChildClass()) >> hello! このクラスに対して静的に解決される
  19. Copyright © Sansan, Inc. All rights reserved. > 拡張関数の問題点1 -

    どこにでも定義できる - → どこに書いたかわからなくなる
  20. Copyright © Sansan, Inc. All rights reserved. > 拡張関数の問題点2 -

    同じ名前の拡張関数でもimportで切り替え可能 - → コードを読んでいるときにはどの拡張関数を呼んでい るかはわかりにくい - → 継承しているときなどの挙動も複雑になる
  21. Copyright © Sansan, Inc. All rights reserved. > 拡張関数の問題点3 -

    元クラスのメンバと拡張関数の名前がかぶった場合には元 クラスのメンバのメソッドが必ず呼ばれる。 - → 定義時には名前がかぶっていなくても、元クラスの バージョンアップにより、拡張関数が上書きされるリス ク
  22. Copyright © Sansan, Inc. All rights reserved. > ガイドライン1 (com.xxx.utils.StringUtils.kt)

    @file:JvmName(“StringUtils”) package com.xxx.utils fun String.lastChar() = get(length - 1) ֦ுؔ਺Λఆٛ͢ΔΫϥεͷ৔ॴɺ ϑΝΠϧ໊ͷίϯϕϯγϣϯΛ͖ΊΔɻ
  23. Copyright © Sansan, Inc. All rights reserved. > ガイドライン1 (com.xxx.utils.StringUtils.kt)

    @file:JvmName(“StringUtils”) package com.xxx.utils fun String.lastChar() = get(length - 1) ֦ுؔ਺Λఆٛ͢ΔΫϥεͷ৔ॴɺ ϑΝΠϧ໊ͷίϯϕϯγϣϯΛ͖ΊΔɻ たとえばJavaの慣習にしたがい、 StringならStringUtilsというファイル名 またJavaからも使⽤する場合は@file:JvmNameを指定
  24. Copyright © Sansan, Inc. All rights reserved. > ガイドライン2 ʢͨͱ͑ύοέʔδ͕͕ͪͬͨͱͯ͠΋ʣ

    ಉ໊͡લͷ֦ுؔ਺Λఆٛ͠ͳ͍ ͋·Γʹ൚༻తͳ໊લ΋ؾΛ͚ͭͨ΄͏͕Α͍
  25. Copyright © Sansan, Inc. All rights reserved. > せっかくの拡張関数 val

    date = Date().before(30.minutes) val size = 30.GB もっとがんがん使いたい! とかやりたい!!
  26. Copyright © Sansan, Inc. All rights reserved. > 拡張関数の定義できる箇所 -

    publicな関数 - クラス内 - 関数内に定義する拡張関数 - interfaceに定義する
  27. Copyright © Sansan, Inc. All rights reserved. > 拡張関数の定義できる箇所 -

    publicな関数 - クラス内 - 関数内に定義する拡張関数 - interfaceに定義する
  28. Copyright © Sansan, Inc. All rights reserved. > クラス内の拡張関数 class

    MyClass{ val Int.seconds get() = this * 1000 val Int.minutes get() = this.seconds * 60 fun calc(){ val s = 20.seconds } } 利⽤できるスコープはそのクラスの中
  29. Copyright © Sansan, Inc. All rights reserved. > クラス内の拡張関数 open

    class MyClass{ val Int.seconds get() = this * 1000 val Int.minutes get() = this.seconds * 60 } class SubClass: MyClass(){ fun calc(){ val s = 20.seconds } } サブクラス内でも使⽤可能
  30. Copyright © Sansan, Inc. All rights reserved. > 拡張関数の定義できる箇所 -

    publicな関数 - クラス内 - 関数内に定義する拡張関数 - interfaceに定義する
  31. Copyright © Sansan, Inc. All rights reserved. > 関数内で拡張関数を定義 fun

    updateView(view: View){ fun Boolean.toVisibility() = if (this) View.VISIBLE else View.GONE
 view.visibility = true.toVisibility() } 関数内でのみ使⽤可能
  32. Copyright © Sansan, Inc. All rights reserved. > 関数内で拡張関数を定義 fun

    updateView(view: View){ fun Boolean.toVisibility() = if (this) View.VISIBLE else View.GONE
 view.visibility = true.toVisibility() } 関数内でのみ使⽤可能
  33. Copyright © Sansan, Inc. All rights reserved. > 拡張関数の定義できる箇所 -

    publicな関数 - クラス内 - 関数内に定義する拡張関数 - interfaceに定義する
  34. Copyright © Sansan, Inc. All rights reserved. > interface内で拡張関数を定義 interface

    MyInterface{ val Long.seconds get() = this * 1000 val Long.minutes get() = this.seconds * 60 }
  35. Copyright © Sansan, Inc. All rights reserved. > interface内で拡張関数を定義 interface

    MyInterface{ val Int.seconds get() = this * 1000 val Int.minutes get() = this.seconds * 60 } class MyClass : MyInterface{ fun calc(){ val s = 20.seconds } } interfaceを実装したクラス内で使⽤可能
  36. Copyright © Sansan, Inc. All rights reserved. > interface内で拡張関数を定義 interface

    MyInterface{ val Int.seconds get() = this * 1000 val Int.minutes get() = this.seconds * 60 } fun calcTime(f:MyInterface.() -> Int):Int = object : MyInterface{}.f()
  37. Copyright © Sansan, Inc. All rights reserved. > interface内で拡張関数を定義 interface

    MyInterface{ val Int.seconds get() = this * 1000 val Int.minutes get() = this.seconds * 60 } fun calcTime(f:MyInterface.() -> Int):Int = object : MyInterface{}.f() レシーバー付き関数リテラル
  38. Copyright © Sansan, Inc. All rights reserved. > interface内で拡張関数を定義 interface

    MyInterface{ val Int.seconds get() = this * 1000 val Int.minutes get() = this.seconds * 60 } fun calcTime(f:MyInterface.() -> Int):Int = object : MyInterface{}.f() fun calc(){ calcTime{ 30.minutes } }
  39. Copyright © Sansan, Inc. All rights reserved. > interface内で拡張関数を定義 interface

    MyInterface{ val Int.seconds get() = this * 1000 val Int.minutes get() = this.seconds * 60 } fun calcTime(f:MyInterface.() -> Int):Int = object : MyInterface{}.f() fun calc(){ calcTime{ 30.minutes } } このラムダ式の中だけで拡張関数を使うことができる
  40. Copyright © Sansan, Inc. All rights reserved. > まとめ -

    publicな拡張関数の利⽤するときは - 拡張関数を定義するクラスの場所、ファイル名のコンベ ンションをきめる。 - 同じ名前の拡張関数を定義しない - むやみに拡張関数を定義しない。 - クラス内、関数内、インターフェイス内の定義を使い、適 切なスコープで拡張関数の仕様が可能
  41. $PQZSJHIU˜4BOTBO *OD"MMSJHIUTSFTFSWFE  4BOTBO͸Ұॹʹ৽͍͠Ձ஋Λ࡞͍ͬͯ͘ ஥ؒΛ͕͍ͯ͞͠·͢ɻ 3VCZ 3VCZPO3BJMT ʢ8FCΞϓϦέʔγϣϯʣ $ɼ"41/&5.7$ ʢ8FCΞϓϦέʔγϣϯʣ

    J04"OESPJEΞϓϦ   ݸਓ޲໊͚ࢗ؅ཧΞϓϦʮ&JHIUʯ   ໊ࢗσʔλԽ෼ࢄॲཧγεςϜ   ๏ਓ޲໊͚ࢗ؅ཧαʔϏεʮ4BOTBOʯ   ๏ਓ޲໊͚ࢗ؅ཧαʔϏε ʮ4BOTBOʯ   ݸਓ޲໊͚ࢗ؅ཧΞϓϦʮ&JHIUʯ ΤϯδχΞืूத 4BOTBO࠾༻ ݕࡧ SFDSVJU!TBOTBODPN·Ͱ ͓ؾܰʹ͝࿈བྷ͍ͩ͘͞ɻ ڵຯͷ͋Δํ͸