Go言語のメソッドって、なんだか難しそう?大丈夫、基本からゆっくり見ていきましょう!
プログラミングには「関数」という命令をまとめたものがありますが、メソッドは特定のデータ(プログラミングの世界では「型」と呼びます)専用の特別な関数みたいなものです。
例えば、「犬」というデータがあったとします。「犬」専用の命令として「吠える()」や「尻尾を振る()」があったら便利ですよね。Go言語のメソッドは、まさにそんな感じで、特定の型に紐づいた処理を定義できる仕組みなんです。
オブジェクト指向という考え方の一部ですが、まずは「特定の型専用の関数なんだな」くらいの理解でOKです!
この記事を読むと、以下のことができるようになりますよ。
- Go言語のメソッドの正体がわかる
- メソッドの基本的な書き方が身につく
- 値レシーバとポインタレシーバの違いと使い分けがわかる
- 関数との違いがスッキリする
- メソッドを使うときの注意点がわかる
Go言語メソッドの基本的な書き方を覚えよう
メソッドの正体がなんとなく掴めたら、次はどうやって書くのかを見ていきましょう。
Go言語のメソッドは、こんな形で書きます。
func (レシーバ変数 型) メソッド名(引数リスト) 戻り値リスト { // メソッドの処理内容 }
通常の関数定義 `func 関数名(引数リスト) 戻り値リスト { ... }` と似ていますが、`func` と `メソッド名` の間に `(レシーバ変数 型)` が入っているのが特徴です。
一つずつ見ていきましょう。
・func
関数やメソッドを定義するときの合言葉です。
・(レシーバ変数 型)
メソッドがどの型に属するかを指定します。「レシーバ」と呼ばれ、メソッドの中で自分自身のデータにアクセスするために使います。変数名は何でも良いですが、型の頭文字の小文字などがよく使われます (例 型 `User` なら `u`)。
・メソッド名
メソッドの名前です。どんな処理をするかわかりやすい名前を付けましょう。
・(引数リスト)
メソッドに追加で渡したい情報があれば書きます。なければ `()` だけ書きます。
・戻り値リスト
メソッドの処理結果を返す場合に書きます。なければ省略できます。
・{ // メソッドの処理内容 }
メソッドが実際に行う処理を波括弧 `{}` の中に書きます。
最初はちょっと複雑に見えるかもしれませんが、基本の形はいつも同じなので、何度か書いているうちに慣れてきますよ。
メソッドの要「レシーバ」を理解しよう
Go言語のメソッドを語る上で欠かせないのが「レシーバ」です。
さっきの書き方 `func (レシーバ変数 型) メソッド名(...) ...` の `(レシーバ変数 型)` の部分ですね。
レシーバは、メソッドと型を結びつける接着剤のような役割を持っています。
「このメソッドは、この型のためにあるんだよ!」と宣言している部分です。
そして、メソッドの中で、その型のデータ(例えば、犬の名前や年齢など)を使いたいときに、レシーバ変数(例 `u`)を通じてアクセスできます。
レシーバには大きく分けて2種類、「値レシーバ」と「ポインタレシーバ」があります。
この2つの違いを理解することが、Go言語のメソッドを使いこなす上でとてもポイントになります。
Go言語メソッドの使い方 - 値レシーバで基本を学ぶ
まずは「値レシーバ」から見ていきましょう。
値レシーバは、メソッドを呼び出すときに、元のデータのコピーがメソッドに渡される仕組みです。
例えるなら、元の書類をコピーして、そのコピーに対して作業するようなイメージです。コピーに変更を加えても、元の書類には影響がないですよね。
値レシーバのメソッドは、主に元のデータを変更する必要がない場合に使われます。
例えば、データの情報を表示するだけ、とか、計算結果を返すだけ、といった場合に向いています。
値レシーバのサンプルプログラム紹介
百聞は一見にしかず!実際のコードを見てみましょう。
ここでは、ユーザーの名前(Name)と年齢(Age)を持つ `User` という構造体を定義し、その情報を表示する `ShowInfo` というメソッドを作ってみます。
package main import "fmt" // ユーザー情報を表す構造体 type User struct { Name string Age int } // 値レシーバを持つメソッド (ユーザー情報を表示) func (u User) ShowInfo() { fmt.Printf("名前: %s, 年齢: %d歳\n", u.Name, u.Age) } func main() { // User型の変数を作成 user1 := User{Name: "太郎", Age: 30} // ShowInfoメソッドを呼び出す user1.ShowInfo() // ドット(.)でメソッドを呼び出す // メソッド呼び出し後も、元のuser1の値は変わらない fmt.Printf("元のデータ - 名前: %s, 年齢: %d歳\n", user1.Name, user1.Age) }
`func (u User) ShowInfo()` の `(u User)` が値レシーバです。`User` 型のデータ `u` を受け取っています。
上のプログラムを実行すると、こんな結果が表示されます。
名前: 太郎, 年齢: 30歳 元のデータ - 名前: 太郎, 年齢: 30歳
値レシーバのサンプルプログラム解説
コードと実行結果を見ていきましょう。
まず、`type User struct { ... }` で `User` というオリジナルのデータ型(構造体)を定義しました。
// 値レシーバを持つメソッド (ユーザー情報を表示) func (u User) ShowInfo() { fmt.Printf("名前: %s, 年齢: %d歳\n", u.Name, u.Age) }
ここで `ShowInfo` メソッドを定義しています。 `(u User)` が値レシーバで、`User` 型のデータを受け取ることを示しています。
メソッドの中では、レシーバ変数 `u` を使って、`User` の `Name` フィールドと `Age` フィールドにアクセスし、`fmt.Printf` で表示しています。
func main() { user1 := User{Name: "太郎", Age: 30} user1.ShowInfo() // メソッド呼び出し fmt.Printf("元のデータ - 名前: %s, 年齢: %d歳\n", user1.Name, user1.Age) }
`main` 関数の中で、`user1` という `User` 型の変数を作り、「太郎」、30歳という情報を入れました。
そして `user1.ShowInfo()` のように、変数名の後にドット `.` を付けてメソッドを呼び出しています。
実行結果を見ると、`ShowInfo` メソッドはちゃんとユーザー情報を表示してくれていますね。
そして、メソッド呼び出しの後に `user1` の中身を表示しても、元のデータは全く変わっていないことが確認できます。値レシーバは元のデータのコピーを使うので、メソッド内で何か変更(今回はしていませんが)しても、呼び出し元の `user1` には影響しないのです。
Go言語メソッドの使い方- ポインタレシーバで値を変更する
次は「ポインタレシーバ」です。
値レシーバがコピーを扱ったのに対し、ポインタレシーバは元のデータの場所(アドレス)をメソッドに渡します。
例えるなら、書類そのものが置いてある場所を教えて、直接その書類に変更を加えてもらうイメージです。元の書類が直接書き換わります。
ポインタレシーバのメソッドは、元のデータを変更したい場合に使われます。
例えば、ユーザーの年齢を1つ増やしたり、設定を変更したりする場合に活躍します。
「ポインタ」と聞くと難しく感じるかもしれませんが、「元のデータを直接いじれるレシーバなんだな」と理解しておけば大丈夫です!
ポインタレシーバのサンプルプログラム紹介
では、ポインタレシーバのコードを見てみましょう。
先ほどの `User` 構造体に、年齢を1つ増やす `AddAge` というメソッドを追加します。
package main import "fmt" // ユーザー情報を表す構造体 type User struct { Name string Age int } // 値レシーバを持つメソッド (ユーザー情報を表示) func (u User) ShowInfo() { fmt.Printf("名前: %s, 年齢: %d歳\n", u.Name, u.Age) } // ポインタレシーバを持つメソッド (年齢を1増やす) func (u *User) AddAge() { u.Age = u.Age + 1 // レシーバを通じて元のAgeを直接変更 } func main() { // User型の変数を作成 user1 := User{Name: "花子", Age: 25} fmt.Println("--- AddAgeメソッド呼び出し前 ---") user1.ShowInfo() // AddAgeメソッドを呼び出す user1.AddAge() // ドット(.)で呼び出すのは同じ fmt.Println("--- AddAgeメソッド呼び出し後 ---") user1.ShowInfo() // 再度表示して、年齢が変わったか確認 }
`func (u *User) AddAge()` の `(u *User)` がポインタレシーバです。型の前にアスタリスク `*` が付いていますね。これがポインタレシーバの目印です。
実行すると、今度はこうなります。
--- AddAgeメソッド呼び出し前 --- 名前: 花子, 年齢: 25歳 --- AddAgeメソッド呼び出し後 --- 名前: 花子, 年齢: 26歳
お、年齢がちゃんと増えていますね!
ポインタレシーバのサンプルプログラム解説
何が起こったのか、詳しく見てみましょう。
// ポインタレシーバを持つメソッド (年齢を1増やす) func (u *User) AddAge() { u.Age = u.Age + 1 // レシーバを通じて元のAgeを直接変更 }
`AddAge` メソッドの定義です。レシーバが `(u *User)` と、型の前に `*` が付いています。これで「`User` 型のポインタを受け取りますよ」という意味になります。
メソッドの中では `u.Age = u.Age + 1` として、レシーバ `u` が指し示している元の `User` データの `Age` フィールドを直接変更しています。(Go言語ではポインタでも `.` でフィールドにアクセスできるのが便利なところです)
func main() { user1 := User{Name: "花子", Age: 25} fmt.Println("--- AddAgeメソッド呼び出し前 ---") user1.ShowInfo() user1.AddAge() // メソッド呼び出し fmt.Println("--- AddAgeメソッド呼び出し後 ---") user1.ShowInfo() }
`main` 関数では、`user1` を作り、まず情報を表示します。
次に `user1.AddAge()` でメソッドを呼び出します。値レシーバのときと同じように、ドット `.` で呼び出せます。(Goがうまいことやってくれるんです)
そして、呼び出した後にもう一度 `user1.ShowInfo()` で表示すると… 実行結果の通り、年齢が25歳から26歳に増えています!
ポインタレシーバを使うと、このようにメソッドの中から呼び出し元のデータを変更できる、というわけです。これが値レシーバとの大きな違いですね。
Go言語メソッドと関数の違いを整理しよう
ここまでメソッドを見てきましたが、「あれ、普通の関数と何が違うの?」と思った方もいるかもしれません。
良い質問です!ここで違いをスッキリさせておきましょう。
大きな違いは、やはり「レシーバ」の有無です。
・メソッド
特定の型(レシーバの型)に紐づいている。
その型のデータに対して何か操作をする、という目的で使われることが多い。
呼び出すときは `変数名.メソッド名()` の形になる。
・関数
特定の型に紐づいていない。
より汎用的な処理や、単独で意味を持つ処理に使われることが多い。
呼び出すときは `関数名()` や `パッケージ名.関数名()` の形になる。
例えるなら、メソッドは「犬クラス」に属する「吠える()」機能、関数は誰でも使える「計算する()」機能、みたいなイメージでしょうか。
どちらが良い悪いではなく、目的に応じて使い分けるのがコツです。
Go言語メソッドを使う上での注意点
メソッド、便利そうですよね! でも、使う上でいくつか知っておくと良い点があります。
値レシーバ?ポインタレシーバ?どっちを使う?
迷ったら、まずはポインタレシーバを検討するのがGo言語では一般的です。理由は、大きなデータをコピーするコストを避けられたり、メソッド内で一貫して値を変更できたりするためです。ただし、元の値を絶対に変更したくない、あるいは変更する必要がない場合は、値レシーバを選びましょう。
nilレシーバに注意
ポインタレシーバの場合、レシーバが `nil` (データが存在しない状態) でメソッドが呼ばれる可能性があります。メソッドの中で、レシーバが `nil` かどうかチェックする処理を入れると、予期せぬエラーを防げます。func (u *User) SomeMethod() { if u == nil { fmt.Println("レシーバがnilです!") return // 何もせず終了 } // uがnilでない場合の処理 // ... }
メソッド名はわかりやすく
どんな処理をするメソッドなのか、名前を見ただけでわかるようにしましょう。`ShowInfo` や `AddAge` のように、動詞を含めると分かりやすくなることが多いです。
同じ型のメソッドは一貫性を持たせる
ある型に対して、値レシーバのメソッドとポインタレシーバのメソッドが混在するのはOKですが、レシーバ変数の名前(例 `u`)は統一するとコードが読みやすくなります。これらの点を頭の片隅に置いておくと、よりスムーズにメソッドを活用できるようになりますよ。
【まとめ】Go言語メソッドをマスターしてコードをレベルアップ!
今回はGo言語のメソッドについて、基本から使い方、注意点まで一通り見てきました。
・メソッドは特定の型に紐づいた特別な関数であること。
・`func (レシーバ) メソッド名() ...` という形で定義すること。
・レシーバには「値レシーバ」と「ポインタレシーバ」があり、元の値を変更するかどうかで使い分けること。
・関数とはレシーバの有無が大きな違いであること。
メソッドを理解し、使いこなせるようになると、Go言語のコードがより整理され、オブジェクト指向的な考え方を取り入れやすくなります。
最初は少し戸惑う部分もあるかもしれませんが、実際にコードを書いて動かしてみるのが一番の近道です!
【関連記事】
Go言語とは?特徴・できること・将来性
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。