5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Go言語入門 学習メモ 8 インタフェース、型アサーション、型スイッチ

Posted at

はじめに

船井総研デジタルのoswです。業務でGo言語を使うことになったのでこれから学習していきます。その備忘録です。参考になる方がいらっしゃれば幸いです。

対象読者

  • これからGo言語を学習する方
  • 既に他の言語で基本構文を学習されている方

学習環境

学習環境は次のようになっています。この環境の構築メモは下記記事にまとめてあります。ご興味がある方はご参照ください。

  • Windows 11 Home / 22H2
  • VSCode / 1.72.2
  • go version go1.19.2 windows/amd64
  • git version 2.38.0.windows.1

前回までの学習

前回は メソッド を学習しました。

インタフェース

実装を抽象化、ポリモーフィズムを実現するための手段として用意されてるのはJavaやC++と同じようです。使い方としてはJavaなら抽象メソッド、C++なら純粋仮想関数と言われる定義のない関数宣言だけをまとめたinterfaceを定義し、それを「実装」して使います。

ただ、言い回しがいまいちピンと来てなかったのですが、どうやらGoにおけるインタフェースの実装とは、interfaceで宣言されている関数をメソッドとして定義し、レシーバと紐づけることでインタフェースを「実装」した、と表現するようです。イメージとして次のような感じだそうです。

  1. インタフェースを定義
  2. インタフェースの関数を実装する
  3. レシーバを実装した関数に紐づけ、メソッドにする
  4. メソッドに紐付いたレシーバはインタフェースを「実装している」状態になる

構文のユーザ型をレシーバとして紐づけた場合、そのユーザ型はインタフェースを実装していることになります。Javaのように"implements"というキーワードはありません。

構文
// ユーザ型の定義
type ユーザ型名 

// インタフェース定義
type インタフェース名 interface {
    メソッド名
}

// メソッドの実装
func (レシーバ ) メソッド名() {
	// 処理
}
サンプルコード
package main

import "fmt"

// ユーザ型の定義
type AmericanShorthair int
type Munchkin          int

// にゃーインタフェース定義
type IMeow interface {
    Meow()
}

// アメショの実装
// この時点でAmericanShorthair型はIMeowインタフェースを実装している
func (cat AmericanShorthair) Meow() {
	fmt.Println("にゃー")
}

// マンチカンの実装
// この時点でMunchkin型はIMeowインタフェースを実装している
func (cat Munchkin) Meow() {
	fmt.Println("なおーん")
}

func main() {
	// インタフェース型変数にそれぞれ格納
	// 格納される要素それぞれの型はインタフェースを実装済みなので代入できる
	cats := []IMeow{
		AmericanShorthair(0),
		Munchkin(0),
	}

	for _, cat := range cats {
		cat.Meow()
	}
}
実行結果
にゃー
なおーん

空インタフェース

何でも受け取れる型です。とは言ってもインタフェースに代わりはないようです。インタフェース型変数はそのインタフェースを実装していないものは代入できないため、空インタフェースは何でも受け取れることから全ての型が空インタフェースを実装してると解釈するようです。

構文
var v interface{}
サンプルコード
package main

import "fmt"

func main() {
	var v interface{}

	v = 1
	fmt.Println(v)

	v = "test"
	fmt.Println(v)

	v = [...]int{0, 1, 2, 3, 4}
	fmt.Println(v)

	v = []int{0, 1, 2, 3, 4}
	fmt.Println(v)

	v = map[int]string {
		0: "abc",
		1: "def",
	}
	fmt.Println(v)
}
実行結果
1
test
[0 1 2 3 4]
[0 1 2 3 4]
map[0:abc 1:def]

型アサーション

空インタフェースはどんな値でも受け取れる反面、もともとの型情報は見なくなり、もっているフィールドや値にアクセスができなくなるようです。そこで、インタフェース型で受け取った値を任意の型にキャストする方法が用意されており、それが型アサーション、のようです。

構文
// 変換できればokにtrue、valueにその値が返る。できなければfalse, ゼロ値が返る
value, ok := インタフェース型変数.(任意の型)
サンプルコード
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	var i interface{}

	/*
	 * アクセスできないことの確認
	 */
	fmt.Println("構造体で中身にアクセスできるか確認")
	i = Person{
		"tanaka",
		99,
	}
	fmt.Println(i)
	// fmt.Println(i.Name) i.Name undefined (type interface{} has no field or method Name)
	// fmt.Println(i.Age)  i.Age undefined (type interface{} has no field or method Age)

	fmt.Println("\n配列で中身にアクセスできるか確認")
	i = [...]int{0, 1, 2, 3, 4}
	fmt.Println(i)
	// fmt.Println(i[0]) invalid operation: cannot index i (variable of type interface{})

	fmt.Println("\nスライスで中身にアクセスできるか確認")
	i = []int{0, 1, 2, 3, 4}
	fmt.Println(i)
	// fmt.Println(i[0]) invalid operation: cannot index i (variable of type interface{})

	fmt.Println("\nマップで中身にアクセスできるか確認")
	i = map[int]string{
		0: "abc",
		1: "def",
	}
	fmt.Println(i)
	// fmt.Println(i[0]) invalid operation: cannot index i (variable of type interface{})

	/*
	 * 型アサーションでアクセスできることの確認
	 */
	fmt.Println("\n\n構造体にキャストし、中身にアクセスできるか確認")
	i = Person{
		"tanaka",
		99,
	}
	person, ok := i.(Person)
	if ok {
		fmt.Println("person.Name:", person.Name)
	}

	fmt.Println("\n配列にキャストし、中身にアクセスできるか確認")
	i = [...]int{0, 1, 2, 3, 4}
	array, ok := i.([5]int)
	if ok {
		fmt.Println("array[4]:", array[4])
	}

	fmt.Println("\nスライスにキャストし、中身にアクセスできるか確認")
	i = []int{0, 1, 2, 3, 4}
	slice, ok := i.([]int)
	if ok {
		fmt.Println("slice[4]:", slice[4])
	}

	fmt.Println("\nマップにキャストし、中身にアクセスできるか確認")
	i = map[int]string{
		0: "abc",
		1: "def",
	}
	mp, ok := i.(map[int]string)
	if ok {
		fmt.Println("mp[0]:", mp[0])
	}

	/*
	 * 型アサーションでわざとキャストを失敗させる
	 */
	fmt.Println("\n\n構造体 -> intにキャストし、戻り値を確認する")
	i = Person{
		"tanaka",
		99,
	}
	p, ok := i.(int)
	if ok {
		fmt.Println("person.Name:", person.Name)
	} else {
		fmt.Println("キャストに失敗しました p:", p)
	}

	fmt.Println("\n配列 -> intにキャストし、戻り値を確認する")
	i = [...]int{0, 1, 2, 3, 4}
	a, ok := i.(int)
	if ok {
		fmt.Println("array[4]:", array[4])
	} else {
		fmt.Println("キャストに失敗しました a:", a)
	}

	fmt.Println("\nスライス -> intにキャストし、戻り値を確認する")
	i = []int{0, 1, 2, 3, 4}
	s, ok := i.(int)
	if ok {
		fmt.Println("slice[4]:", slice[4])
	} else {
		fmt.Println("キャストに失敗しました s:", s)
	}

	fmt.Println("\nマップ -> intにキャストし、戻り値を確認する")
	i = map[int]string{
		0: "abc",
		1: "def",
	}
	m, ok := i.(int)
	if ok {
		fmt.Println("mp[0]:", mp[0])
	} else {
		fmt.Println("キャストに失敗しました m:", m)
	}
}
実行結果
構造体で中身にアクセスできるか確認
{tanaka 99}

配列で中身にアクセスできるか確認
[0 1 2 3 4]

スライスで中身にアクセスできるか確認
[0 1 2 3 4]

マップで中身にアクセスできるか確認
map[0:abc 1:def]


構造体にキャストし中身にアクセスできるか確認
person.Name: tanaka

配列にキャストし中身にアクセスできるか確認
array[4]: 4

スライスにキャストし中身にアクセスできるか確認
slice[4]: 4

マップにキャストし中身にアクセスできるか確認
mp[0]: abc


構造体 -> intにキャストし戻り値を確認する
キャストに失敗しました p: 0

配列 -> intにキャストし戻り値を確認する
キャストに失敗しました a: 0

スライス -> intにキャストし戻り値を確認する
キャストに失敗しました s: 0

マップ -> intにキャストし戻り値を確認する
キャストに失敗しました m: 0

型switch

通常のswitchとは異なり、caseに型を指定することで分岐する。評価する式はインタフェース型変数の「もともとの型」のようです。型はインタフェース型変数.(type)で取得。

構文
// 戻り値はinterface{}型ではなく、もともとの型に合った値が返る
switch value := i.(type) {
case int:
    fmt.Println("iはint型です 値:", value)
case string:
    fmt.Println("iはstring型です 値:", value)
default:
    fmt.Println("iはそれ以外です")
}
サンプルコード
package main

import "fmt"

type Person struct {
	Name string
}

func main() {

	var i interface{} = Person{"tanaka"}

	switch value := i.(type) {
	case int:
		fmt.Println("iはint型です 値:", value)
	case string:
		fmt.Println("iはstring型です 値:", value)
	case Person:
        // 返ってきたvalueはフィールドにアクセスできるためPerson型。interface{}型ではない
		fmt.Println("iはPerson型です 値:", value.Name)
	default:
		fmt.Println("iはそれ以外です 値:", value)
	}
}
実行結果
iはPerson型です : tanaka

おわりに

今回はここまでです。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?