Goのメソッドは、特定の型に関連付けられた関数です。この関数は特定の型のオブジェクトや変数と直接結びついており、その型のインスタンスに対して呼び出すことができます。
カスタム型はGoプログラミング言語の強力な機能で、開発者が独自のデータ型を作成することを可能にします。カスタム型は型のエイリアスや構造体を定義することで作成され、Goの他の組み込み型と同様に使用することができます。
type Person struct {
name string
}
typeキーワードを使用して定義した型は、他の型と同様に使用できるだけでなく、メソッドを定義させることで、既存の型を拡張して使用することができます。
func (p Person) Greet() string {
return "Hello, my name is " + p.name
}
この例では、Person型に、挨拶を返すメソッドGreetがあります。このメソッドはfuncキーワードを使用して宣言され、レシーバは (p Person) 構文を使用して指定されます。pはレシーバと呼ばれます。レシーバとは、Goで構造体のフィールドやその他のデータ型にアクセスする方法を提供するメソッドの引数のことです。
メソッドは、関数と同じように型のインスタンスに対して呼び出すことができます。違いは、レシーバがすでにインスタンスと関連付けられているため、パラメータとしてインスタンスを渡す必要がないことです。
p := Person{name: "John"}
fmt.Println(p.Greet()) // Hello, my name is John
Note: Goにはクラスの仕組みはありませんが、GoのレシーバはPythonのselfやJavaScriptのthisとほぼ同じ意味合いを持ちます。Goの慣習では、レシーバ名は1文字程度の変数名が良いとされているので、長くとも2文字以下にしたレシーバ名を付けるようにしましょう。
メソッドは、ある型へのポインタに対して宣言することもできます。
func (p *Person) ChangeName(newName string) {
p.name = newName
}
p := &Person{name: "John"}
p.ChangeName("Jane")
fmt.Println(p.Greet()) // Hello, my name is Jane
この例では、ChangeNameメソッドはPersonへのポインタをレシーバとして使用します。このメソッドが呼び出されると、Personインスタンスのnameフィールドが変更され、その変更が元のインスタンスに反映されます。
コンストラクタ関数は、型のインスタンスを作成および初期化するために使用される特殊なタイプの関数です。
Goでは、Newを先頭につけた関数はコンストラクタ関数を示す慣習になっています。このNewがついた関数は、特に標準パッケージを使用するときに頻繁に見ることになるのでこの機会に是非覚えておきましょう。
type Person struct {
name string
age int
}
func NewPerson(name string, age int) Person {
return Person {
name: name,
age: age,
}
}
コンストラクタ関数では、引数で受け取った値を元にPerson型の構造体を生成し、それを返しています。
p := NewPerson("John", 30)
fmt.Println(p.name) // John
fmt.Println(p.age) // 30
コンストラクタ関数は値だけではなく、ポインタを返すこともできます。同じ例を用いて、ポインタを返すコンストラクタ関数について見てみましょう。
type Person struct {
name string
age int
}
func NewPerson(name string, age int) *Person {
return &Person{
name: name,
age: age,
}
}
関数から値が返されると、その値のコピーが作成され、呼び出し元に渡されます。つまり、コンストラクタ関数で作成した構造体を返す場合、その構造体は独自のメモリアドレスを持つ別の構造体となります。コンストラクタ関数で作成した構造体と全く同じ構造体を返したい状況では、その構造体へのポインタを返すのがよいでしょう。
以下の簡単な例で、返された値は独自のメモリアドレスを持っており、元の値とは別物であることを確認しましょう。
package main
import "fmt"
func returnValue(i int) int {
return i
}
func main() {
x := 5
y := returnValue(x)
fmt.Println("x:", &x, "y:", &y)
// x: 0xc0000b2000 y: 0xc0000b2008
}
Note: また、ポインタで返された構造体に定義されているメソッドは、そのまま実行することができます。main関数の中の変数pは構造体のポインタが格納されているため、本来であれば(*p).method()のように、実体である値に変更してからメソッドを呼ぶのが本来の流れですが、Goのランタイムが上手く処理してくれているため、その部分を意識する必要はありません。
レシーバは、値レシーバとポインタレシーバの2つの使い方があります。
値レシーバとは、型の値そのものを受け取るレシーバのことです。
type Person struct {
name string
age int
}
func (p Person) IncreaseAge() {
p.age++
}
p := Person{name: "John", age: 30}
p.IncreaseAge()
fmt.Println(p.age) // 30
この例では、IncreaseAgeメソッド内でレシーバに加えられた変更は、元のインスタンスには反映されません。Personインスタンスのageフィールドは30歳のままです。
値レシーバでは、メソッド内でレシーバに加えられた変更は、元のインスタンスには反映されません。
ポインタレシーバとは、型の値のポインタを受け取るレシーバのことです。
type Person struct {
name string
age int
}
func (p *Person) IncreaseAge() {
p.age++
}
p := Person{name: "John", age: 30}
p.IncreaseAge()
fmt.Println(p.age) // 31
この例では、IncreaseAgeメソッド内でレシーバに加えられた変更は元のインスタンスに反映されます。Personインスタンスのageフィールドが31にインクリメントされます。
ポインタレシーバでは、メソッド内でレシーバに加えられた変更は、元のインスタンスに反映されます。
これはGoが値渡し型の言語であるためです。関数に値を渡すと、Goはその値のコピーを作成し、それが関数に渡されます。関数が値を変更した場合、変更はコピーにのみ行われ、元の値には行われません。
しかし、関数にポインタを渡すと、Goは元の値への参照を渡します。そして、関数はそのポインタを使用して、元の値にアクセスし、変更することができます。これは、ポインタレシーバを使用すると値が変更される理由です。メソッドは、型の単なるコピーではなく、型のオリジナルのインスタンスにアクセスして変更するからです。
構造体の埋め込みとは、構造体が他の構造体のフィールドを持つことを指します。 埋め込まれた構造体は、内側の構造体として扱われ、外側の構造体は内側の構造体のフィールドを含むことになります。これによって、外側の構造体は内側の構造体に直接アクセスすることが可能になります。
例えば、PersonとEmployeeの2つの構造を考えてみましょう。Personには、名前、年齢など、人に関する情報が含まれています。Employeeには、名前、年齢、役職など、従業員に関する情報が含まれています。
type Person struct {
name string
age int
}
type Employee struct {
Person
jobTitle string
}
この例では、EmployeeがPersonを埋め込んでおり、Employeeに Personのフィールドが含まれることを意味します。Employeeは、追加のコードを必要とせずに、Personのフィールドに直接アクセスできます。
e := Employee{
Person: Person{
name: "John",
age: 30,
},
jobTitle: "Software Engineer",
}
fmt.Println(e.name) // John
fmt.Println(e.age) // 30
fmt.Println(e.jobTitle) // Software Engineer
それではメソッドを追加してみましょう。
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s and I am %d years old\n", p.name, p.age)
}
func (e Employee) DisplayJobTitle() {
fmt.Printf("My job title is %s\n", e.jobTitle)
}
メソッドの呼び出しは、以下のようになります。
e := Employee{
Person: Person{
name: "John",
age: 30,
},
jobTitle: "Software Engineer",
}
e.Greet() // Hello, my name is John and I am 30 years old
e.DisplayJobTitle() // My job title is Software Engineer
この構造体の埋め込みは、クラスにおける継承と非常に似ています。しかし、Go言語においては継承ではなく埋め込み(=embedding)という考え方になっています。
クラスを継承するような場面では、継承したクラスが親クラスとなり、オーバーライドが行われます。一方で、構造体を埋め込むような場面では、埋め込んだ構造体はあくまでも子供としてぶら下がるだけで、オーバーライドのようなことは行われません。