Share
Embed
Transcript
53 slides
-
Copyright © Sansan, Inc. All rights reserved. > みんな⼤好き拡張関数 Jumpei
Yamamoto 2017.6.29 第6回 Kotlin勉強会 in Sansan #kotlin_sansan -
Copyright © Sansan, Inc. All rights reserved. > ⾃⼰紹介 -
⼭本純平 - twitter: @boohbah - github: https://github.com/yamamotoj -
Copyright © Sansan, Inc. All rights reserved. > 便利! fun
String.lastChar() = get(length - 1) print(“Kotlin”.lastChar()) >> n -
Copyright © Sansan, Inc. All rights reserved. > propertyアクセス val
String.isNumeric get() = StringUtils.isNumeric(this) “30”.isNumeric // -> True -
Copyright © Sansan, Inc. All rights reserved. > いくつかの疑問がありますね? -
どういう仕組み? - 元のクラスとメソッドが被ったら? - 元のクラスが継承している場合は? - 標準的なクラスに、どんどんメソッドを追加 していったらメンテナンスが⼤変そう -
Copyright © Sansan, Inc. All rights reserved. > どういう仕組み? (StringUtils.kt)
fun String.lastChar() = get(length - 1) // Java͔Βݺͼग़͢ StringUtilsKt.lastChat(“Java”); -
Copyright © Sansan, Inc. All rights reserved. > どういう仕組み? (StringUtils.kt)
fun String.lastChar() = get(length - 1) // Java͔Βݺͼग़͢ StringUtilsKt.lastChat(“Java”); 内部的にはthisを引数にした static methodの呼び出し -
Copyright © Sansan, Inc. All rights reserved. > どういう仕組み? (StringUtils.kt)
fun String.lastChar() = get(length - 1) // Java͔Βݺͼग़͢ StringUtilsKt.lastChat(“Java”); ↑Ktまで含めたクラス名 -
Copyright © Sansan, Inc. All rights reserved. > 元のメソッドと被ったら? class
Universe{ fun answer() = 42 } fun Universe.answer() = 100 -
Copyright © Sansan, Inc. All rights reserved. > 元のメソッドと被ったら? class
Universe{ fun answer() = 42 } fun Universe.answer() = 100 Universe().answer() // -> 42 -
Copyright © Sansan, Inc. All rights reserved. > 元のメソッドと被ったら? class
Universe{ fun answer() = 42 } fun Universe.answer() = 100 Universe().answer() // -> 42 必ず、定義元のクラスのメンバーが実⾏される 拡張関数が実⾏されることはない -
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(“͜Μʹͪ”) 違うパッケージなら定義可能 -
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 -
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() // -> ͜Μʹͪ -
Copyright © Sansan, Inc. All rights reserved. > 元のクラスが継承されている場合 open
class ParentClass class ChildClass: ParentClass() fun ParentClass.greeting() = println("hello!") ParentClass().greeting() >> hello! ChildClass().greeting() >> hello! -
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! -
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() } -
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! -
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! このクラスに対して静的に解決される -
Copyright © Sansan, Inc. All rights reserved. > 拡張関数の問題点1 -
どこにでも定義できる - → どこに書いたかわからなくなる -
Copyright © Sansan, Inc. All rights reserved. > 拡張関数の問題点2 -
同じ名前の拡張関数でもimportで切り替え可能 - → コードを読んでいるときにはどの拡張関数を呼んでい るかはわかりにくい - → 継承しているときなどの挙動も複雑になる -
Copyright © Sansan, Inc. All rights reserved. > 拡張関数の問題点3 -
元クラスのメンバと拡張関数の名前がかぶった場合には元 クラスのメンバのメソッドが必ず呼ばれる。 - → 定義時には名前がかぶっていなくても、元クラスの バージョンアップにより、拡張関数が上書きされるリス ク -
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) ֦ுؔΛఆٛ͢ΔΫϥεͷॴɺ ϑΝΠϧ໊ͷίϯϕϯγϣϯΛ͖ΊΔɻ -
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を指定 -
Copyright © Sansan, Inc. All rights reserved. > ガイドライン2 ʢͨͱ͑ύοέʔδ͕͕ͪͬͨͱͯ͠ʣ
ಉ໊͡લͷ֦ுؔΛఆٛ͠ͳ͍ ͋·Γʹ൚༻తͳ໊લؾΛ͚ͭͨ΄͏͕Α͍ -
Copyright © Sansan, Inc. All rights reserved. > ガイドライン3 ΉΈʹ֦ுؔΛఆٛ͠ͳ͍ɻ
ಛఆͷίϯςΩετʹґଘ͢ΔΑ͏ͳ֦ுؔආ͚Δ -
Copyright © Sansan, Inc. All rights reserved. > せっかくの拡張関数 val
date = Date().before(30.minutes) val size = 30.GB もっとがんがん使いたい! とかやりたい!! -
Copyright © Sansan, Inc. All rights reserved. > 拡張関数の定義できる箇所 -
publicな関数 - クラス内 - 関数内に定義する拡張関数 - interfaceに定義する -
Copyright © Sansan, Inc. All rights reserved. > 拡張関数の定義できる箇所 -
publicな関数 - クラス内 - 関数内に定義する拡張関数 - interfaceに定義する -
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 } } 利⽤できるスコープはそのクラスの中 -
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 } } サブクラス内でも使⽤可能 -
Copyright © Sansan, Inc. All rights reserved. > 拡張関数の定義できる箇所 -
publicな関数 - クラス内 - 関数内に定義する拡張関数 - interfaceに定義する -
Copyright © Sansan, Inc. All rights reserved. > 関数内で拡張関数を定義 fun
updateView(view: View){ fun Boolean.toVisibility() = if (this) View.VISIBLE else View.GONE view.visibility = true.toVisibility() } 関数内でのみ使⽤可能 -
Copyright © Sansan, Inc. All rights reserved. > 関数内で拡張関数を定義 fun
updateView(view: View){ fun Boolean.toVisibility() = if (this) View.VISIBLE else View.GONE view.visibility = true.toVisibility() } 関数内でのみ使⽤可能 -
Copyright © Sansan, Inc. All rights reserved. > 拡張関数の定義できる箇所 -
publicな関数 - クラス内 - 関数内に定義する拡張関数 - interfaceに定義する -
Copyright © Sansan, Inc. All rights reserved. > interface内で拡張関数を定義 interface
MyInterface{ val Long.seconds get() = this * 1000 val Long.minutes get() = this.seconds * 60 } -
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を実装したクラス内で使⽤可能 -
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() -
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() レシーバー付き関数リテラル -
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 } } -
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 } } このラムダ式の中だけで拡張関数を使うことができる -
Copyright © Sansan, Inc. All rights reserved. > まとめ -
publicな拡張関数の利⽤するときは - 拡張関数を定義するクラスの場所、ファイル名のコンベ ンションをきめる。 - 同じ名前の拡張関数を定義しない - むやみに拡張関数を定義しない。 - クラス内、関数内、インターフェイス内の定義を使い、適 切なスコープで拡張関数の仕様が可能 -
$PQZSJHIU4BOTBO *OD"MMSJHIUTSFTFSWFE 4BOTBOҰॹʹ৽͍͠ՁΛ࡞͍ͬͯ͘ ؒΛ͕͍ͯ͞͠·͢ɻ 3VCZ 3VCZPO3BJMT ʢ8FCΞϓϦέʔγϣϯʣ $ɼ"41/&5.7$ ʢ8FCΞϓϦέʔγϣϯʣ
J04"OESPJEΞϓϦ ݸਓ໊͚ཧΞϓϦʮ&JHIUʯ ໊σʔλԽࢄॲཧγεςϜ ๏ਓ໊͚ཧαʔϏεʮ4BOTBOʯ ๏ਓ໊͚ཧαʔϏε ʮ4BOTBOʯ ݸਓ໊͚ཧΞϓϦʮ&JHIUʯ ΤϯδχΞืूத 4BOTBO࠾༻ ݕࡧ SFDSVJU!TBOTBODPN·Ͱ ͓ؾܰʹ͝࿈བྷ͍ͩ͘͞ɻ ڵຯͷ͋Δํ