interface
| 対応: | Go 1.0(2012) |
|---|
インターフェースは、型が持つべきメソッドの集合を定義します。Goのインターフェースは暗黙的に実装され、明示的な宣言は不要です。
構文
// インターフェースを定義する
type インターフェース名 interface {
メソッド名(引数) 戻り値型
}
// 空インターフェース(any)はすべての型を受け入れる
var v any = "Hello"
var v interface{} = 42
インターフェース一覧
| 構文・型 | 概要 |
|---|---|
| type I interface { M() } | メソッド『M()』を要求するインターフェースを定義します。 |
| any | 『interface{}』の組み込みエイリアスです(Go 1.18+)。すべての型を受け入れます。 |
| interface{} | メソッドが0個の空インターフェースです。すべての型が実装しています。 |
| 型アサーション: v.(T) | インターフェース値を具体的な型に変換します。変換に失敗するとパニックが発生します。 |
| 型アサーション: v, ok := v.(T) | 変換に失敗しても『ok』が『false』になるだけでパニックは発生しません。 |
| 型スイッチ: switch v.(type) | インターフェース値の具体的な型によって処理を分岐させます。 |
サンプルコード
interface.go
package main
import (
"fmt"
"math"
)
// Shapeインターフェースを定義する
type Shape interface {
Area() float64
Perimeter() float64
}
// Circle構造体を定義する
type Circle struct {
Radius float64
}
// CircleがShapeインターフェースを暗黙的に実装する
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// Rectangle構造体を定義する
type Rectangle struct {
Width, Height float64
}
// RectangleもShapeインターフェースを実装する
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// インターフェース型を引数に取る関数
func printShape(s Shape) {
fmt.Printf("面積: %.2f, 周囲: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
c := Circle{Radius: 5}
r := Rectangle{Width: 3, Height: 4}
printShape(c) // CircleはShapeを満たす
printShape(r) // RectangleもShapeを満たす
// 空インターフェースはすべての型を受け入れる
var anything any = "文字列"
fmt.Println(anything) // 『文字列』と出力される
anything = 123
fmt.Println(anything) // 『123』と出力される
// 型アサーションで具体的な型に変換する
var s Shape = Circle{Radius: 3}
if circle, ok := s.(Circle); ok {
fmt.Println("半径:", circle.Radius) // 『半径: 3』と出力される
}
// 型スイッチで型ごとに処理を分岐する
values := []any{42, "hello", true, 3.14}
for _, v := range values {
switch val := v.(type) {
case int:
fmt.Printf("整数: %d\n", val)
case string:
fmt.Printf("文字列: %s\n", val)
default:
fmt.Printf("その他: %v\n", val)
}
}
}
go run interface.go 面積: 78.54, 周囲: 31.42 面積: 12.00, 周囲: 14.00 文字列 123 半径: 3 整数: 42 文字列: hello その他: true その他: 3.14
暗黙的実装の仕組み
Goのインターフェースは暗黙的に実装されます。JavaやKotlinのように『implements Interface』と宣言する必要はなく、インターフェースが要求するメソッドをすべて定義するだけで自動的にそのインターフェースを満たします。これを「ダックタイピング」と呼びます。
この仕組みにより、インターフェースの定義と実装を別々のパッケージに置けます。既存の型に後からインターフェースを当てはめることもできます。
sample_implicit_interface.go
package main
import "fmt"
// Stringer インターフェース(fmt パッケージにも同名のものがあります)
type Stringer interface {
String() string
}
// 既存の型に後からインターフェースを適用できます
// - この構造体を定義したときに Stringer を意識する必要はありません
type Character struct {
Name string
Power int
}
// Character に String() を定義すると Stringer を自動的に満たします
func (c Character) String() string {
return fmt.Sprintf("%s (Power: %d)", c.Name, c.Power)
}
// Stringer を満たすものは何でも受け取れます
func printAll(items []Stringer) {
for _, item := range items {
fmt.Println(item.String())
}
}
// コンパイル時にインターフェース実装を確認する慣用イディオム
// var _ Stringer = Character{} // Character が Stringer を満たさなければコンパイルエラー
func main() {
chars := []Stringer{
Character{Name: "Kiryu Kazuma", Power: 95},
Character{Name: "Majima Goro", Power: 92},
}
printAll(chars)
}
go run sample_implicit_interface.go Kiryu Kazuma (Power: 95) Majima Goro (Power: 92)
空インターフェースの注意点
空インターフェース(『any』/ 『interface{}』)はすべての型を受け入れますが、使いすぎると型安全性が失われます。具体的なインターフェースを定義することでより型安全なコードになります。
あまりよくない: 何でも受け取れますが、型情報が失われます。
sample_any_bad.go
package main
import "fmt"
func printAnything(v any) {
fmt.Printf("値: %v, 型: %T\n", v, v)
}
func main() {
var v any = "Kiryu"
if s, ok := v.(string); ok {
fmt.Println("文字列:", s)
}
if n, ok := v.(int); ok {
fmt.Println("整数:", n)
} else {
fmt.Println("int ではありません")
}
}
go run sample_any_bad.go 文字列: Kiryu int ではありません
よりよい: 必要なメソッドだけを要求するインターフェースを定義します。
sample_any_interface.go
package main
import "fmt"
type Displayable interface {
Display() string
}
type Item struct {
Name string
Price int
}
func (it Item) Display() string {
return fmt.Sprintf("%s: ¥%d", it.Name, it.Price)
}
func printDisplayable(d Displayable) {
fmt.Println(d.Display())
}
func main() {
printDisplayable(Item{Name: "Kiryu's Suit", Price: 50000})
}
go run sample_any_interface.go Kiryu's Suit: ¥50000
よくあるミス1: ポインタレシーバの制約
ポインタレシーバで定義したメソッドは、値型(非ポインタ)ではインターフェースを満たせません。
package main
type Animal interface {
Sound() string
}
type Cat struct{ Name string }
// ポインタレシーバで定義: *Cat は Animal を満たすが Cat は満たしません
func (c *Cat) Sound() string { return "Meow" }
func makeSound(a Animal) {}
func main() {
// makeSound(Cat{Name: "Tama"}) // コンパイルエラー: Cat は Animal を満たしません
}
OK: ポインタを渡せばインターフェースを満たします。
sample_interface_ok.go
package main
import "fmt"
type Animal interface {
Sound() string
}
type Dog struct{ Name string }
func (d Dog) Sound() string { return "Woof" }
type Cat struct{ Name string }
func (c *Cat) Sound() string { return "Meow" }
func makeSound(a Animal) {
fmt.Println(a.Sound())
}
func main() {
// Dog は値レシーバなので Dog も *Dog も Animal を満たします
makeSound(Dog{Name: "Hachiko"})
// Cat はポインタレシーバなので *Cat だけが Animal を満たします
makeSound(&Cat{Name: "Tama"}) // ポインタを渡す
}
go run sample_interface_ok.go Woof Meow
よくあるミス2: nilインターフェースの罠
nil インターフェースと nil を含むインターフェースは異なります。インターフェース変数が nil かどうか比較するとき、型情報を持つポインタを格納した場合は nil にはなりません。
sample_interface_nil.go
package main
import "fmt"
type Animal interface {
Sound() string
}
type Dog struct{ Name string }
func (d *Dog) Sound() string { return "Woof" }
func main() {
var a Animal = nil // インターフェース自体が nil
var d *Dog = nil // 型情報はあるが値が nil の Dog ポインタ
var a2 Animal = d // インターフェースに nil ポインタを格納
fmt.Println(a == nil) // true: a は完全に nil
fmt.Println(a2 == nil) // false: a2 は型情報を持つため nil ではありません!
}
go run sample_interface_nil.go true false
概要
Goのインターフェースは他の言語と異なり、型がインターフェースを実装することを明示的に宣言しません。型が必要なメソッドをすべて実装していれば自動的にそのインターフェースを満たします。これを「ダックタイピング」と呼びます。ダックタイピングとは、明示的にインターフェースを宣言しなくても、必要なメソッドを持っていれば自動的にそのインターフェースを満たすとみなす仕組みです。
型アサーション『v.(T)』は変換に失敗するとパニックを引き起こします。安全に変換するには『v, ok := v.(T)』の2値形式を使用してください。
空インターフェース『any』(旧『interface{}』)はすべての型を格納できますが、使用時は型アサーションや型スイッチで具体的な型に変換する必要があります。具体的なインターフェースを定義することで型安全性を保てます。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。