こんにちは、イサムです!
本記事では、Go言語のリフレクションについてまとめていきます。
動的に型や値を扱いたい場合に非常に便利なので、ご紹介したいと思います。
- Go言語のリフレクションについて知りたい!
- Go言語で動的に型や値を簡単に扱いたい!
- Go言語のリフレクションについてのまとめ
リフレクションとは
リフレクションとは、プログラム実行時に値や型の情報を取得できたり、取得した情報を元に関数を実行したり、値を生成できる機能になります。
そのため、実行時にならないと分からない情報を取得でき、インタフェース型の変数に入っている実際の値の型や型によらない汎用的な関数やメソッドを作ることができます。
Go言語では、標準パッケージのreflectパッケージを用いることで実現することができます。
リフレクションの機能には下記の3つがあります。
- インターフェース値からリフレクションオブジェクトを取得
- リフレクションオブジェクトの値を変更
- リフレクションオブジェクトからインターフェース値を生成
機能の3つ目の値の生成は使用頻度が少ないため今回は紹介しません。また、機能1つ目のメソッドやインターフェースについても使用頻度が低いため紹介しません。
リフレクションオブジェクトの取得
型情報の取得
func TypeOf(i interface{}) Type
reflectパッケージのTypeOf関数に、interface型の値を引数に渡すことで、その値の型情報を表すType型(Typeインターフェース)の値(リフレクションオブジェクト)を取得できます。
この値をTypeインターフェースに定義されているメソッドに使用することで値や型名などを取得できます。
型情報の種類の取得
func Kind() Kind
Typeインターフェースが保有するKindメソッドを、TypeOf関数で取得したType型の値に実行することで、その型の種類を表すKind型の値を取得できます。
Kind型は、データ型の種類を表し、下記のようなデータがあります。
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
型情報の型名の取得
func Name() String
Typeインターフェースが保有するNameメソッドを、TypeOf関数で取得したType型の値に実行することで、その型の型名を表す値を取得できます。
定義されていない型の場合、空文字列を返します。
型情報の要素型の取得
func Name() String
Typeインターフェースが保有するElemメソッドを、TypeOf関数で取得したType型の値に実行することで、その型の要素型を取得できます。
型の種類がArray, chan, Map, Ptr, Sliceではない場合、パニックが発生します。
実行例
import (
"fmt"
"reflect"
)
func main() {
stringValue := "12345"
intValue := 12345
fmt.Println("=== int ===")
printValue(intValue)
fmt.Println("=== string ===")
printValue(stringValue)
fmt.Println("=== *string ===")
printValue(&stringValue)
}
func printValue(v interface{}) {
rv := reflect.TypeOf(v)
fmt.Println("Kind:", rv.Kind())
fmt.Println("Name:", rv.Name())
switch rv.Kind() {
case reflect.Ptr, reflect.Array, reflect.Map, reflect.Slice, reflect.Chan:
fmt.Println("Elem:", rv.Elem())
default:
fmt.Println("Elem:")
}
}
=== int ===
Kind: int
Name: int
Elem:
=== string ===
Kind: string
Name: string
Elem:
=== *string ===
Kind: ptr
Name:
Elem: string
値情報の取得
func ValueOf(i interface{}) Value
reflectパッケージのValueOf関数に、interface型の値を引数に渡すことで、その値の値情報を表すValue型(Valueインターフェース)の値(リフレクションオブジェクト)を取得できます。
この値をValueインターフェースに定義されているメソッドを使用することで値や型名などを取得できます。
デリファレンスして参照先の値の取得
func Indirect(v Value) Value
デリファレンスとは、参照外しのことで、ポインタ変数から参照している値を取得することです。
reflectパッケージのIndirectメソッドを、ValueOf関数で取得したValue型の値を引数に指定して実行することで、その値の現在のデリファレンスして参照先の値を取得できます。
引数の値がポインタであれば、ポインタが参照する値を返し、それ以外の場合は、引数の値をそのまま返します。
値情報の種類の取得
func (v Value) Kind() Kind
Valueインターフェースが保有するKindメソッドを、ValueOf関数で取得したValue型の値に実行することで、その値の型の種類を表すKind型の値を取得できます。
TypeインターフェースのKindメソッドと同じ挙動をします。
値情報の型の取得
func (v Value) Type() Type
Valueインターフェースが保有するTypeメソッドを、ValueOf関数で取得したValue型の値に実行することで、その値の型を表すType型の値を取得できます。
値情報の現在値の取得
func (v Value) Interface() (i interface{})
Valueインターフェースが保有するInterfaceメソッドを、ValueOf関数で取得したValue型の値に実行することで、その値の現在の値を取得できます。
構造体のフィールドで外部パッケージ非公開(小文字)のフィールドに対して行うとパニックが発生するため注意が必要になります。構造体のフィールドに対して使用する場合、Typeインターフェースが保有するPkgPathメソッドを使用することでパニックを回避することができます。PkgPathメソッドは、外部公開フィールドからは空文字を、非公開フィールドからは値が返ってくるので空文字の場合にInterfaceメソッドを使用するように処理すればパニックは発生せずより安全に実行できます。
ポインタが指す値の取得
func (v Value) Elem() Value
Valueインターフェースが保有するElemメソッドを、ValueOf関数で取得したValue型の値に実行することで、その値を取得できます。
型の種類がPtrではない場合、パニックが発生します。
このメソッドは、リフレクションオブジェクトの値を変更する際にも使用します。
実行例
import (
"fmt"
"reflect"
)
func main() {
stringValue := "12345"
intValue := 12345
fmt.Println("=== int ===")
printValue(intValue)
fmt.Println("=== string ===")
printValue(stringValue)
fmt.Println("=== *string ===")
printValue(&stringValue)
}
func printValue(v interface{}) {
rv := reflect.ValueOf(v)
fmt.Println("Kind:", rv.Kind())
fmt.Println("Type:", rv.Type())
fmt.Println("Interface:", rv.Interface())
fmt.Println("Indirect:", reflect.Indirect(rv))
switch rv.Kind() {
case reflect.Ptr:
fmt.Println("Elem:", rv.Elem())
default:
fmt.Println("Elem:")
}
}
=== int ===
Kind: int
Type: int
Interface: 12345
Indirect: 12345
Elem:
=== string ===
Kind: string
Type: string
Interface: 12345
Indirect: 12345
Elem:
=== *string ===
Kind: ptr
Type: *string
Interface: 0xc00009a050
Indirect: 12345
Elem: 12345
構造体の情報取得
構造体に、型情報を取得するTypeOf関数、値情報を取得するValueOf関数を実行し、構造体用の各メソッドを実行することで、構造体のフィールドやメソッドの情報を取得することができます。
構造体のメソッド情報の取得はあまり使用する機会がないため、今回は紹介しません。
フィールド情報の取得
フィールド数の取得
func NumField() int
Type/Valueインターフェースが保有するNumFieldメソッドを、TypeOf関数/ValueOf関数を適用した構造体の値に実行することで、その構造体の型のフィールド数を取得できます。
型の種類が構造体でない場合、パニックが発生します。
指定したフィールド番号のフィールド情報取得
func Field(i int) StructField
Typeインターフェースが保有するFieldメソッドを、TypeOf関数を適用した構造体の値にフィールド番号(インデックス)を引数に指定して実行することで、その構造体の指定したフィールドのフィールド名、データ型、タグ、フィールド番号などを取得できます。
指定したインデックスが範囲外、 型の種類が構造体でない場合、パニックが発生します。
func (v Value) Field(i int) Value
Valueインターフェースが保有するFieldメソッドを、ValueOf関数を適用した構造体の値にフィールド番号(インデックス)を引数に指定して実行することで、その構造体の指定したフィールドの値を取得できます。
指定したインデックスが範囲外、 型の種類が構造体でない場合、パニックが発生します。
指定したフィールド名のフィールド情報取得
func FieldByName(name string) (StructField, bool)
Typeインターフェースが保有するFieldByNameメソッドを、TypeOf関数を適用した構造体の値にフィールド名を引数に指定して実行することで、その構造体の指定したフィールドのフィールド名、データ型、タグ、フィールド番号などを取得できます。
指定したフィールドが存在しない場合、第2戻り値でfalseを返します。
func (v Value) FieldByName(name string) Value
Valueインターフェースが保有するFieldByNameメソッドを、ValueOf関数を適用した構造体の値にフィールド名を引数に指定して実行することで、その構造体の指定したフィールドの値を取得できます。
フィールドが見つからなかった場合、型のゼロ値を返し、 型の種類が構造体でない場合、パニックが発生します。
実行例
import (
"fmt"
"reflect"
)
type user struct {
Name string
Age int
}
func main() {
user := user{
Name: "Osamu",
Age: 20,
}
fmt.Println("=== Type ===")
tv := reflect.TypeOf(user)
fmt.Println("Kind:", tv.Kind())
fmt.Println("Name:", tv.Name())
fmt.Println("NumField:", tv.NumField())
fmt.Println("Field:", tv.Field(0))
f, _ := tv.FieldByName("Age")
fmt.Println("FieldByName:", f)
fmt.Println()
fmt.Println("=== Value ===")
rv := reflect.ValueOf(user)
fmt.Println("Kind:", rv.Kind())
fmt.Println("Type:", rv.Type())
fmt.Println("Interface:", rv.Interface())
fmt.Println("NumField:", rv.NumField())
fmt.Println("Field:", rv.Field(0))
fmt.Println("FieldByName:", rv.FieldByName("Age"))
}
=== Type ===
Kind: struct
Name: user
NumField: 2
Field: {Name string 0 [0] false}
FieldByName: {Age int32 16
false}
=== Value ===
Kind: struct
Type: main.user
Interface: {Osamu 20}
NumField: 2
Field: Osamu
FieldByName: 20
タグ情報の取得
TypeインターフェイスのStructField型に埋め込まれているStructTag型のメソッドを使用することで、構造体のフィールドに定義されているタグの情報を取得することができます。
指定したタグのキーの値を取得
func (tag StructTag) Get(key string) string
Typeインターフェースが保有するStructTag型のGetメソッドを、Fieldメソッド等で取得したStructField型の値にタグのキーを引数に指定して実行することで、その構造体の指定したタグのキーの値を取得できます。
指定したタグのキーが存在しない場合、空文字を返します。
func (tag StructTag) Lookup(key string) (value string, ok bool)
LookupメソッドもGetメソッドと同様に指定したタグのキーの値を取得できますが、第2戻り値にタグが存在するかの真偽値を返します。
指定したタグのキーが存在しない場合、空文字を返します。
実行例
import (
"fmt"
"reflect"
)
type user struct {
Name string `json:"name"`
Age int
}
func main() {
user := user{
Name: "Osamu",
Age: 20,
}
tv := reflect.TypeOf(user)
for i := 0; i < tv.NumField(); i++ {
f := tv.Field(i)
fmt.Println("===", f.Name, "===")
fmt.Println("Get:", f.Tag.Get("json"))
t, ok := f.Tag.Lookup("json")
fmt.Println("Lookup:", t, ok)
}
}
=== Name ===
Get: name
Lookup: name true
=== Age ===
Get:
Lookup: false
スライス情報の取得
スライスに、値情報を取得するValueOf関数を実行し、スライス用の各メソッドを実行することで、スライスの長さや容量の情報を取得することができます。
長さの取得
func (v Value) Len() int
Valueインターフェースが保有するLenメソッドを、ValueOf関数を適用したスライスの値に実行することで、そのスライスの長さを取得できます。
型の種類がArray, Chan, Map, Slice , Stringでない場合はパニックが発生します。
容量の取得
func (v Value) Cap() int
Valueインターフェースが保有するCapメソッドを、ValueOf関数を適用したスライスの値に実行することで、そのスライスの容量を取得できます。
型の種類が Array, Chan, Sliceでない場合はパニックが発生します。
指定したインデックスの要素の取得
func (v Value) Index(i int) Value
Valueインターフェースが保有するIndexメソッドを、ValueOf関数を適用したスライスの値に実行することで、その指定したインデックスのスライスの要素を取得できます。
型の種類がArray, Slice, Stringでないまたは指定したインデックスが範囲外の場合はパニックが発生します。
スライスの一部の取得
func (v Value) Slice(i, j int) Value
スライスで部分配列を取得できるように、Valueインターフェースが保有するSliceメソッドを、ValueOf関数を適用したスライスの値に取得する範囲を引数に指定して実行することで、そのスライスの指定した部分を取得できます。
型の種類がArray, Slice, Stringでない場合はパニックが発生します。
実行例
import (
"fmt"
"reflect"
)
func main() {
s := []string{"user1", "user2", "user3"}
rs := reflect.ValueOf(s)
fmt.Println("Kind:", rs.Kind())
fmt.Println("Type:", rs.Type())
fmt.Println("Interface:", rs.Interface())
fmt.Println("Len:", rs.Len())
fmt.Println("Cap:", rs.Cap())
fmt.Println("Index:", rs.Index(0))
fmt.Println("Slice:", rs.Slice(1, 3))
}
Kind: slice
Type: []string
Interface: [user1 user2 user3]
Len: 3
Cap: 3
Index: user1
Slice: [user2 user3]
マップ情報の取得
マップに、値情報を取得するValueOf関数を実行し、マップ用の各メソッドを実行することで、マップの各要素を取得することができます。
全てのキーの取得
func (v Value) MapKeys() []Value
Valueインターフェースが保有するMapKeysメソッドを、ValueOf関数を適用したマップの値に実行することで、マップ内に存在する全てのキーを含むスライスを不定の順序で取得できます。
型の種類がMapでない場合、パニックが発生します。
また、型がnilの場合、ゼロ値を返します。
指定したインデックスの要素の取得
func (v Value) MapIndex(key Value) Value
Valueインターフェースが保有するMapIndexメソッドを、ValueOf関数を適用したマップの値に実行することで、その指定したインデックスのマップの要素を取得できます。
型の種類がMapでない場合、パニックが発生します。
また、指定したインデックスが範囲外または型がnilの場合、ゼロ値を返します。
イテレータの取得
func (v Value) MapRange() *MapIter
イテレータとは、配列、スライス、マップなどのデータに対して、各要素を順に操作して繰り返し処理を行うオブジェクトのことです。
Valueインターフェースが保有するMapRangeメソッドを、ValueOf関数を適用したマップの値に実行することで、そのマップのイテレータ(範囲反復子)を取得できます。
Nextメソッドを呼び出してイテレータを進め, Key/Valueメソッドで各キーと値にアクセスできます。 反復子が使い果たされると, Nextメソッドはfalseを返し、処理が終了します。
型の種類がMapでない場合、パニックが発生します。
実行例
import (
"fmt"
"reflect"
)
func main() {
m := map[int]string{1: "user1", 2: "user2", 3: "user3"}
rm := reflect.ValueOf(m)
fmt.Println("Kind:", rm.Kind())
fmt.Println("Type:", rm.Type())
fmt.Println("Len:", rm.Len())
fmt.Println("MapKeys:", rm.MapKeys())
fmt.Println("MapIndex:", rm.MapIndex(reflect.ValueOf(2)))
iter := rm.MapRange()
for iter.Next() {
fmt.Println("Key:", iter.Key(), "Value:", iter.Value())
}
}
Kind: map
Type: map[int]string
Len: 3
MapKeys: [<int Value> <int Value> <int Value>]
MapIndex: user2
Key: 1 Value: user1
Key: 2 Value: user2
Key: 3 Value: user3
インターフェース情報の取得
reflectパッケージの中にインターフェース用のメソッドがいくつか定義されていますが、使用頻度が低いため、今回は紹介しません。
リフレクションオブジェクトの値の変更
ValueOf関数で取得したリフレクションオブジェクトの値の変更ができます。
ただ、値の変更が不可のリフレクションオブジェクトに対して行うとするとパニックが発生するため、リフレクションオブジェクトの値を変更する際、値が変更可能かを確認してから行う必要があります。
また、値を変更する場合は、リフレクションオブジェクトをそのまま変更することはできず、先述したElemメソッドでポインタが指す値に対して行う必要があります。
値の変更が可能かの確認
func (v Value) CanSet() bool
Valueインターフェースが保有するCanSetメソッドを、ValueOf関数を適用した値に実行することで、その値が変更可能かの真偽値を取得できます。
falseの値に対して値の変更を行うとパニックが発生します。
値の変更
func (v Value) SetBool(x bool)
func (v Value) SetBytes(x []byte)
func (v Value) SetCap(n int)
func (v Value) SetLen(n int)
func (v Value) SetComplex(x complex128)
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
func (v Value) SetMapIndex(key, elem Value)
func (v Value) SetPointer(x unsafe.Pointer)
func (v Value) SetString(x string)
func (v Value) SetUint(x uint64)
Valueインターフェースが保有するSetメソッドを、ValueOf関数を適用した値に適したメソッドの引数に変更する値を指定して実行することで、値の変更ができます。
実行例
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
}
func main() {
x := 1
// リフレクションオブジェクトそのままでは値の変更不可
xv := reflect.ValueOf(x)
fmt.Println("=== int ===")
fmt.Println("x:", x)
fmt.Println()
fmt.Println("Kind of xv:", xv.Kind())
fmt.Println("Type of xv:", xv.Type())
fmt.Println("Settability of xv:", xv.CanSet())
fmt.Println()
// リフレクションオブジェクトのポインタ変数では値の変更不可
xp := reflect.ValueOf(&x)
fmt.Println("Kind of xp:", xp.Kind())
fmt.Println("Type of xp:", xp.Type())
fmt.Println("Settability of xp:", xp.CanSet())
fmt.Println()
// リフレクションオブジェクトのポインタの参照する値であれば値の変更可
xe := xp.Elem()
fmt.Println("Kind of xe:", xe.Kind())
fmt.Println("Type of xe:", xe.Type())
fmt.Println("Settability of xe:", xe.CanSet())
fmt.Println()
xe.SetInt(100)
fmt.Println("x:", x)
fmt.Println()
u := User{
Name: "user1",
Age: 10,
}
// リフレクションオブジェクトそのままでは値の変更不可
uv := reflect.ValueOf(u)
fmt.Println("=== struct ===")
fmt.Println("u:", u)
fmt.Println()
fmt.Println("Kind of uv:", uv.Kind())
fmt.Println("Type: of uv", uv.Type())
fmt.Println("Settability of uv:", uv.CanSet())
fmt.Println()
// リフレクションオブジェクトのポインタ変数では値の変更不可
up := reflect.ValueOf(&u)
fmt.Println("Kind of up:", up.Kind())
fmt.Println("Type of up:", up.Type())
fmt.Println("Settability of up:", up.CanSet())
fmt.Println()
// リフレクションオブジェクトのポインタの参照する値であれば値の変更可
ue := up.Elem()
fmt.Println("Kind:", ue.Kind())
fmt.Println("Type:", ue.Type())
fmt.Println("Settability:", ue.CanSet())
fmt.Println()
ue.FieldByName("Name").SetString("user2")
fmt.Println("u:", u)
}
=== int ===
x: 1
Kind of xv: int
Type of xv: int
Settability of xv: false
Kind of xp: ptr
Type of xp: *int
Settability of xp: false
Kind of xe: int
Type of xe: int
Settability of xe: true
x: 100
=== struct ===
u: {user1 10}
Kind of uv: struct
Type: of uv main.User
Settability of uv: false
Kind of up: ptr
Type of up: *main.User
Settability of up: false
Kind: struct
Type: main.User
Settability: true
u: {user2 10}
その他の便利なメソッド
値の比較
まず、等値と等価について整理します。
等価とは、比較対象の値が「同じ内容」であることです。アドレスは関係なく、指しているものが「同じ内容か」どうかになります。
等値とは、比較対象の値が指しているものが「完全に同一の存在」であることです。アドレスを比較し、指しているものが「完全に同一か」どうかになります。
func DeepEqual(x, y any) bool
reflectパッケージのDeepEqual関数の引数に2つの値を渡すことで、その値が等値か、等価かどうかを確認することができます。
通常の==演算子では、ポインタの参照先を見てくれないので、スライス、マップ、ポインタまたはそれらを含む構造体などの等値を確認する場合に使用します。
実行例
スライスやマップ、ポインタを含まない構造体の比較
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
}
func main() {
u1 := User {
Name: "Osamu",
Age: 20,
}
u2 := User {
Name: "Osamu",
Age: 20,
}
fmt.Println("=== 比較1 ===")
fmt.Printf("等価:u1 == u2 : %t\n", u1 == u2)
fmt.Printf("等値:&u1 == &u2 : %t\n", &u1 == &u2)
fmt.Printf("等価:reflect.DeepEqual(u1, u2) : %t\n", reflect.DeepEqual(u1, u2))
fmt.Printf("等値:reflect.DeepEqual(&u1, &u2) : %t\n", reflect.DeepEqual(&u1, &u2))
fmt.Println("=== 比較2 ===")
u3 := u1
fmt.Printf("等価:u1 == u3 : %t\n", u1 == u3)
fmt.Printf("等値:&u1 == &u3 : %t\n", &u1 == &u3)
fmt.Printf("等価:reflect.DeepEqual(u1, u3) : %t\n", reflect.DeepEqual(u1, u3))
fmt.Printf("等値:reflect.DeepEqual(&u1, &u3) : %t\n", reflect.DeepEqual(&u1, &u3))
}
=== 比較1 ===
等価:u1 == u2 : true
等値:&u1 == &u2 : false
等価:reflect.DeepEqual(u1, u2) : true
等値:reflect.DeepEqual(&u1, &u2) : true
=== 比較2 ===
等価:u1 == u3 : true
等値:&u1 == &u3 : false
等価:reflect.DeepEqual(u1, u3) : true
等値:reflect.DeepEqual(&u1, &u3) : true
構造体ポインタ型の比較
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
}
func main() {
u1 := User {
Name: "Osamu",
Age: 20,
}
u2 := User {
Name: "Osamu",
Age: 20,
}
fmt.Println("=== 同一ポインタの比較 ===")
u3 := &u1
// fmt.Printf("等価:u1 == u3 : %t\n", u1 == u3) panic
fmt.Printf("等値:&u1 == u3 : %t\n", &u1 == u3)
fmt.Printf("等価:reflect.DeepEqual(u1, u3) : %t\n", reflect.DeepEqual(u1, u3))
fmt.Printf("等値:reflect.DeepEqual(&u1, u3) : %t\n", reflect.DeepEqual(&u1, u3))
fmt.Println("=== 別ポインタの比較 ===")
u4 := &u2
// fmt.Printf("等価:u1 == u4 : %t\n", u1 == u4) panic
fmt.Printf("等値:&u1 == u4 : %t\n", &u1 == u4)
fmt.Printf("等価:reflect.DeepEqual(u1, u4) : %t\n", reflect.DeepEqual(u1, u4))
fmt.Printf("等値:reflect.DeepEqual(&u1, u4) : %t\n", reflect.DeepEqual(&u1, u4))
}
=== 同一ポインタの比較 ===
等値:&u1 == u3 : true
等価:reflect.DeepEqual(u1, u3) : false
等値:reflect.DeepEqual(&u1, u3) : true
=== 別ポインタの比較 ===
等値:&u1 == u4 : false
等価:reflect.DeepEqual(u1, u4) : false
等値:reflect.DeepEqual(&u1, u4) : true
スライスを含む構造体の比較
構造体は異なるが、スライスが同一アドレスの場合、等値になるので注意が必要です。
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
Numbers []int
}
func main() {
ns1 := []int{1, 2, 3}
ns2 := []int{1, 2, 4}
u1 := User {
Name: "Osamu",
Age: 20,
Numbers: ns1,
}
u2 := User {
Name: "Osamu",
Age: 20,
Numbers: ns2,
}
fmt.Println("=== スライスの値が別の構造体の比較 ===")
// fmt.Printf("等価:u1 == u2 : %t\n", u1 == u2) panic
fmt.Printf("等値:&u1 == &u2 : %t\n", &u1 == &u2)
fmt.Printf("等価:reflect.DeepEqual(u1, u2) : %t\n", reflect.DeepEqual(u1, u2))
fmt.Printf("等値:reflect.DeepEqual(&u1, &u2) : %t\n", reflect.DeepEqual(&u1, &u2))
fmt.Println("=== スライスの値が同じ構造体の比較 ===")
u3 := u1
// fmt.Printf("等価:u1 == u3 : %t\n", u1 == u3) panic
fmt.Printf("等値:&u1 == &u3 : %t\n", &u1 == &u3)
fmt.Printf("等価:reflect.DeepEqual(u1, u3) : %t\n", reflect.DeepEqual(u1, u3))
fmt.Printf("等値:reflect.DeepEqual(&u1, &u3) : %t\n", reflect.DeepEqual(&u1, &u3)) // trueになる
fmt.Println("=== スライスのアドレスが同じの構造体の比較 ===")
u4 := &u1
// fmt.Printf("等価:u1 == u4 : %t\n", u1 == u4) panic
fmt.Printf("等値:&u1 == u4 : %t\n", &u1 == u4)
fmt.Printf("等価:reflect.DeepEqual(u1, u4) : %t\n", reflect.DeepEqual(u1, u4))
fmt.Printf("等値:reflect.DeepEqual(&u1, u4) : %t\n", reflect.DeepEqual(&u1, u4))
}
=== スライスの値が別の構造体の比較 ===
等値:&u1 == &u2 : false
等価:reflect.DeepEqual(u1, u2) : false
等値:reflect.DeepEqual(&u1, &u2) : false
=== スライスの値が同じ構造体の比較 ===
等値:&u1 == &u3 : false
等価:reflect.DeepEqual(u1, u3) : true
等値:reflect.DeepEqual(&u1, &u3) : true
=== スライスのアドレスが同じの構造体の比較 ===
等値:&u1 == u4 : true
等価:reflect.DeepEqual(u1, u4) : false
等値:reflect.DeepEqual(&u1, u4) : true
同じ値だが異なるアドレスのデータを含む構造体の比較
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
Number *int
}
func main() {
n1 := 1
n2 := 1
u1 := User {
Name: "Osamu",
Age: 20,
Number: &n1,
}
u2 := User {
Name: "Osamu",
Age: 20,
Number: &n2,
}
fmt.Printf("等価:u1 == u2 : %t\n", u1 == u2)
fmt.Printf("等値:&u1 == &u2 : %t\n", &u1 == &u2)
fmt.Printf("等価:reflect.DeepEqual(u1, u2) : %t\n", reflect.DeepEqual(u1, u2))
fmt.Printf("等値:reflect.DeepEqual(&u1, &u2) : %t\n", reflect.DeepEqual(&u1, &u2))
}
等価:u1 == u2 : false
等値:&u1 == &u2 : false
等価:reflect.DeepEqual(u1, u2) : true
等値:reflect.DeepEqual(&u1, &u2) : true
同じ値で同じアドレスのデータを含む構造体の比較
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
Number *int
}
func main() {
num := 1
u1 := User {
Name: "Osamu",
Age: 20,
Number: &num,
}
u2 := User {
Name: "Osamu",
Age: 20,
Number: &num,
}
fmt.Printf("等価:u1 == u2 : %t\n", u1 == u2)
fmt.Printf("等値:&u1 == &u2 : %t\n", &u1 == &u2)
fmt.Printf("等価:reflect.DeepEqual(u1, u2) : %t\n", reflect.DeepEqual(u1, u2))
fmt.Printf("等値:reflect.DeepEqual(&u1, &u2) : %t\n", reflect.DeepEqual(&u1, &u2))
}
等価:u1 == u2 : true
等値:&u1 == &u2 : false
等価:reflect.DeepEqual(u1, u2) : true
等値:reflect.DeepEqual(&u1, &u2) : true
まとめ
この記事では、Go言語のリフレクションについてまとめました。
少しGo言語をいじってみたいと思ったら、ブラウザ上で実行できる公式チュートリアルでコードを実際に書いてみると雰囲気がつかめるかもしれません。
今後、さらにGo言語のことについて記事にしていきたいと思います。
もし「記事の内容が間違えている!」等ありましたら、Twitterまたはお問い合わせフォームからご連絡いただければと思います。
最後までご覧いただきありがとうございました。