2章ではstringやintなど、
type
次のような、
func ProcessTask(id, priority int) {
}
この関数を呼び出す側は次のようになります。
id := 3 // int
priority := 5 // int
ProcessTask(id, priority)
正しく呼び出せていますが、
id := 3
priority := 5
ProcessTask(priority, id) // コンパイルは通る
引数の型が合っているため、
このような場合、type
を用いて既存の型を拡張した独自の型を定義できます。
type ID int
type Priority int
func ProcessTask(id ID, priority Priority) {
}
type
のあとには、
呼び出す際には、
var id ID = 3
var priority Priority = 5
ProcessTask(priority, id) // コンパイルエラー
このように適切な型を用意することで、
構造体(struct)
Goには、
構造体型の宣言
ここでは、id、
done
Task
という型を定義してみます。
type Task struct {
ID int
Detail string
done bool
}
構造体型もtype
を用いて宣言し、
この型から値を生成するには、
func main() {
var task Task = Task{
ID: 1,
Detail: "buy the milk",
done: true,
}
fmt.Println(task.ID) // 1
fmt.Println(task.Detail) // "buy the milk"
fmt.Println(task.done) // true
}
変数task
には、
構造体に定義した順でパラメータを渡すことで、
func main() {
var task Task = Task{
1, "buy the milk", true,
}
fmt.Println(task.ID) // 1
fmt.Println(task.Detail) // "buy the milk"
fmt.Println(task.done) // true
}
構造体の生成時に値を明示的に指定しなかった場合は、
func main() {
task := Task{}
fmt.Println(task.ID) // 0
fmt.Println(task.Detail) // ""
fmt.Println(task.done) // false
}
ポインタ型
構造体型もアドレスを取得し、&
を付けると、Task
のポインタ型は*Task
という型になります。
var task Task = Task{} // Task型
var task *Task = &Task{} // Taskのポインタ型
たとえば、
type Task struct {
ID int
Detail string
done bool
}
func Finish(task Task) {
task.done = true
}
func main() {
task := Task{done: false}
Finish(task)
fmt.Println(task.done) // falseのまま
}
この関数の引数をポインタ型にするには、*Task
とします。ポインタを渡すことで、
func Finish(task *Task) {
task.done = true
}
func main() {
task := &Task{done: false}
Finish(task)
fmt.Println(task.done) // true
}
このように、
new()
構造体は、new()
を用いて初期化することもできます。new()
は、
type Task struct {
ID int
Detail string
done bool
}
func main() {
var task *Task = new(Task)
task.ID = 1
task.Detail = "buy the milk"
fmt.Println(task.done) // false
}
コンストラクタ
Goには構造体のコンストラクタにあたる構文がありません。代わりにNew
で始まる関数を定義し、Task
をNew
する関数はNewTask()
という関数にし、Task
を生成し、
func NewTask(id int, detail string) *Task {
task := &Task{
ID: id,
Detail: detail,
done: false,
}
return task
}
func main() {
task := NewTask(1, "buy the milk")
// &{ID:1 Detail:buy the milk done:false}
fmt.Printf("%+v", task)
}
メソッド
型にはメソッドを定義できます。メソッドは、Task
の文字列表現を返すString()
というメソッドをTask
に定義してみます。
func (task Task) String() string {
str := fmt.Sprintf("%d) %s", task.ID, task.Detail)
return str
}
func main() {
task := NewTask(1, "buy the milk")
fmt.Printf(“%s”, task) // 1) buy the milk
}
このメソッドは、Task
のコピーがレシーバとして渡されるため、
呼び出し側に変更を反映したい場合は、Finish()
というメソッドをTask
に定義してみます。
func (task *Task) Finish() {
task.done = true
}
func main() {
task := NewTask(1, "buy the milk")
task.Finish()
// &{ID:1 Detail:buy the milk done:true}
fmt.Printf("%+v", task)
}
今回は、Finish()
が実行されたTask
のポインタを受け取り、
インタフェース
Goのインタフェースは、
インタフェースの宣言
たとえば、Task
に実装したString()
という振る舞いが規定されていることを表すインタフェースは、
type Stringer interface {
String() string
}
インタフェースの名前は、er
を加えた名前を付ける慣習があります。よってString()
を実装するインタフェースはStringer
となります。
インタフェースの実装
Goでは、Task
にはString()
メソッドを実装しているため、Stringer
を引数に取る次のような関数に渡すことができます。
func Print(stringer Stringer) {
fmt.Println(stringer.String())
}
Print(task)
実は、Stringer
インタフェースはGoのfmtパッケージに標準で定義されており、
ほかにも、
インタフェース名 | 定義 | 説明 |
---|---|---|
io. | Read(p []byte) (n int,err error) | リソースからデータの読み出しを行う |
io. | Write(p []byte) (n int,err error) | リソースへのデータの書き込みを行う |
io. | Close()error | リソースのクローズ処理を行う |
http. | ServeHTTP (ResponseWriter,*Request) | HTTPリクエストに対するレスポンスを行う |
json. | MarshalJSON() ([]byte,error) | 構造体やスライスなどをJSONに変換する |
json. | UnmarshalJSON ([]byte)error | JSONを構造体やスライスなどに変換する |
interface{}
次のようなインタフェースを考えてみます。
type Any interface {
}
このインタフェースは、
これを利用すると、
func Do(e Any) {
// do something
}
Do("a") // どのような型も渡すことができる
また、
func Do(e interface{}) {
// do something
}
Do("a") // どのような型も渡すことができる
たとえば、fmt.
などのいわゆるプリント関数は、
// fmt.Printlnの定義
// 任意の型を可変長引数で受け取る
func Println(a ...interface{}) (n int, err error)
型の埋め込み
Goでは、
構造体の埋め込み
例として、Task
に対して、User
の情報を埋め込んでみましょう。
User
構造体の定義は以下とし、FullName()
とコンストラクタ関数を実装します。
type User struct {
FirstName string
LastName string
}
func (u *User) FullName() string {
fullname := fmt.Sprintf("%s %s",
u.FirstName, u.LastName)
return fullname
}
func NewUser(firstName, lastName string) *User {
return &User{
FirstName: firstName,
LastName: lastName,
}
}
これをTask
に埋め込みます。Task
の構造体型宣言時に、
type Task struct {
ID int
Detail string
done bool
*User // Userを埋め込む
}
func NewTask(id int, detail,
firstName, lastName string) *Task {
task := &Task{
ID: id,
Detail: detail,
done: false,
User: NewUser(firstName, lastName),
}
return task
}
埋め込まれたUser
のフィールドやメソッドは、Task
が実装しているかのように振る舞います。また、
func main() {
task := NewTask(1, "buy the milk", "Jxck", "Daniel")
// TaskにUserのフィールドが埋め込まれている
fmt.Println(task.FirstName)
fmt.Println(task.LastName)
// TaskにUserのメソッドが埋め込まれている
fmt.Println(task.FullName())
// Taskから埋め込まれたUser自体にもアクセス可能
fmt.Println(task.User)
}
インタフェースの埋め込み
インタフェースも埋め込み可能です。主な用途は、
たとえばioパッケージでは、Reader
、Writer
といったインタフェースが定義されています。
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
このとき、Read()
とWrite()
を両方定義したインタフェースであるReadWriter
は、
type ReadWriter interface {
Reader
Writer
}
ioパッケージには同様に、Closer
インタフェースを埋め込んだReadCloser
、WriteCloser
、ReadWrite Closer
なども定義されています。このように既存のインタフェースをさらに拡張したインタフェースを定義するのにも、
型の変換
Goでは、
キャスト
キャストは、
var i uint8 = 3
var j uint32 = uint32(i) // uint8 -> uint32
fmt.Println(j) // 3
var s string = "abc"
var b []byte = []byte(s) // string -> []byte
fmt.Println(b) // [97 98 99]
// cannot convert "a" (type string) to type int
a := int("a")
キャストに失敗した場合はパニックが発生します。
Type Assertion――型の検査
あるインタフェース値が指定した型であるかを調べるには、interface{}
型で受け取る関数内で、
func Print(value interface{}) {
s, ok := value.(string) // Type Assertion
if ok {
fmt.Printf("value is string: %s\n", s)
} else {
fmt.Printf("value is not string\n")
}
}
func main() {
Print("abc") // value is string: abc
Print(10) // value is not string
}
第一戻り値には判定が成功した場合にその型に変換された値が返り、
Type Switch――型による分岐
Type Assertionは単一の型に対する検査しかできませんが、switch
と組み合わせることで型ごとに処理を分岐できます。
type Stringer interface {
String() string
}
func Print(value interface{}) {
switch v := value.(type) {
case string:
fmt.Printf("value is string: %s\n", v)
case int:
fmt.Printf("value is int: %d\n", v)
case Stringer:
fmt.Printf("value is Stringer: %s\n", v)
}
}
func main() {
Print("abc") // value is string: abc
Print(10) // value is int: 10
}
まとめ
本章では、