覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Go言語 - メソッド

Go言語には、任意の型に関数を紐づけるためのメソッドという機能が存在しています。


メソッドの定義方法

メソッドの定義は以下のように記述します。レシーバーに関する記述をする部分が、関数と異なる箇所になります

func (<レシーバー>) <関数名>([引数]) [戻り値の型] {
    [関数の本体]
}

例えばPersonという構造体を定義して、そのPersonのプロフィールを返すメソッドを定義する場合は以下のような記述をします

type Person struct {
    name string
    age  uint
}

func (p *Person) profile(header string) string {
    return fmt.Sprintf("%s Profile[name: %s, age: %d]", header, p.name, p.age)
}

func (p Person) profile2(header string) string {
    return fmt.Sprintf("%s Profile[name: %s, age: %d]", header, p.name, p.age)
}


定義したメソッドは、 <レシーバ>.<メソッド> という形式で呼び出すことができます

func main() {
    person1 := &Person{name: "KimuraJiro", age: 28}
    fmt.Printf("person1 -> %s\n", person1.profile("!!!"))

    person2 := &Person{name: "KimuraTaro", age: 38}
    fmt.Printf("person2 -> %s\n", person2.profile2("@@@"))
}

■実行結果

person1 -> !!! Profile[name: KimuraJiro, age: 28]
person2 -> @@@ Profile[name: KimuraTaro, age: 38]

定義したメソッドの内容が実行されていることが分かります


メソッドは関数としてどう定義されているか

<レシーバの型>.<メソッド>でメソッドを関数型として参照することができます。

これを利用してPersonに定義したprofileメソッドとprofile2メソッドの関数定義を確認してみます

f1 := (*Person).profile
f2 := (Person).profile2

fmt.Printf("Person.profile -> %T\n", f1)
fmt.Printf("Person.profile2 -> %T\n", f2)

■実行結果

Person.profile -> func(*main.Person, string) string
Person.profile2 -> func(main.Person, string) string

実行結果から、メソッドのレシーバを第1引数にした関数になっていることがわかります


レシーバを値型にしたりポイント型にしたり

Person型をレシーバとするリネームメソッドとPersonのポインタ型をレシーバとするリネームメソッドを以下のように定義します。
引数で与えられた文字列をレシーバPersonnameフィールドにセットしています。

type Person struct {
    name string
    age  uint
}

func (p *Person) rename1(newName string) {
    p.name = newName
}

func (p Person) rename2(newName string) {
    p.name = newName
}


値型に定義したメソッドとポインタに定義したメソッドを実行して結果の違いを確認してみます

fmt.Printf("Person.rename1 -> %T\n", (*Person).rename1)
fmt.Printf("Person.rename2 -> %T\n", (Person).rename2)

personA := &Person{name: "Hanako", age: 19}
oldName := personA.name

personA.rename1("Kumiko")

fmt.Printf("rename1 exec renamed. %s -> %s\n", oldName, personA.name)

personB := &Person{name: "Rika", age: 19}
oldName = personB.name

personB.rename2("Miho")

fmt.Printf("rename2 exec renamed. %s -> %s\n", oldName, personB.name)


■実行結果

Person.rename1 -> func(*main.Person, string)
Person.rename2 -> func(main.Person, string)
rename1 exec renamed. Hanako -> Kumiko
rename2 exec renamed. Rika -> Rika

実行結果から、
ポインタ型に対するメソッド呼び出しの場合はレシーバ(構造体)の値が変更されていますが、
値型に対するメソッド呼び出しの場合はレシーバ(構造体)の値が変更されていないことが分かります。

これは、以下の事を勘案すれば納得いく結果です

  • メソッドがレシーバを第1引数に取る関数であること
  • 値渡しした引数はコピーされて関数に渡される



関連エントリ