💬

コンストラクタとメソッドのオーバーロード

に公開

まえがき

chat-gptで作った学習ロードマップをプログラミング初学者が勉強する試みです
ロードマップはchat-gptを使用してますが、学習は公式のチュートリアルや技術系ブログなどを参考にしています
Goの復習も兼ねているのでGoとの違いについても言及します
今回はコンストラクタとメソッドのオーバーロードについて内容をまとめていきます

コンストラクタとは?

コンストラクタはクラスのインスタンスを初期化するときに呼び出されるメンバー
以下のふたつを両方満たすものはコンストラクタとして使える

  1. 返り値を持たない
  2. クラス名と同じメソッド
最小限のコンストラクタ
class クラス名 {
	// コンストラクタ
	クラス名() {
	}
}

定義の通り返り値は持てないが引数はとることはできる

メンバー内にコンストラクタが無い場合はスーパークラスに引数の無いコンストラクタを探しにいく

  1. スーパークラスが無い場合
    全てのクラスはObjectを継承してるのでObjectクラスから引数の無いコンストラクタを呼び出す(デフォルトのコンストラクタ)
  2. スーパークラスが有る場合
    スーパークラスに引数を持つコンストラクタしか存在しない場合はデフォルトコンストラクタの呼び出しが行われないためコンパイルエラー

2の場合はスーパークラスを確認しないとコンパイルエラーを起こすので注意

基本的な使い方

Java
class Sum {

	// フィールド
	int i1;
	int i2;

	// コンストラクタ
	Sum(int left, int right) {
		i1 = left;
		i2 = right;
	}
}

public class Main() {
	public static void Main(String[] args) {
		// コンストラクタを利用して初期化
		Sum sum1 = new Sum(5, 10);
	}
}

明示的にコンストラクタを使う場合はフィールドの設定が簡潔に記述できたりする

メソッドのオーバーロードとは?

引数の数や型が違う、同じ名前のメソッドを複数定義すること
同じ名前なので管理しやすく、それでいて柔軟性を持たせることができる

オーバーロードの使い方

例えばふたつのprintメソッドを用意する

  1. 片方はintを引数に持つ
  2. もう片方はStringを引数に持つ

これはprintメソッドをオーバーロードしたことになり、同じprintメソッドでint型とString型のどちらでも引数に渡すことができる

オーバーロード
public class Main {

    // int型の引数を持つメソッド
    static void print(int left, int right) {
      System.out.print(left + right);
    }
  
    // String型の引数を持つメソッド
    static void print(String left, String right) {
      System.out.print(Integer.valueOf(left) + Integer.valueOf(right));
    }

  public static void main(String[] args) {
    print(5, 10);	// 15
    print("5", "10");	// 15
  }
}

printメソッドが複数あっても実行できる
引数の型と数が同じ場合はコンパイルエラー

Object型のような上位の型がオーバーロードしている場合は、intなどのより限定的な型が優先される

JavaとGoの違い

Goのコンストラクタ

Goでコンストラクタを利用する場合は関数を定義する
関数名は New+構造体名 とするのが慣習

Goのコンストラクタ
type Numbers struct {
	i1 int
	i2 int
}

func NewNumbers(left, right int) *Numbers {
	return &Numbers {
		i1: left,
		i2: right,
	}
}

func main() {
	n := NewNumbers(5, 10)
	fmt.Print(n)	// &{5, 10}
}

Goの場合は返り値にオブジェクトを定義する
構造体の利用目的を考えると上記のようにポインタを返すことが多い(メソッドを定義するときにポインタレシーバを使うため)

Goのオーバーロード

Goでオーバーロードはできない
なので他の方法で似たような機能を実装する

いくつか方法はあるがここでは以下のふたつを紹介する

  • ジェネリクス+タイプスイッチ
  • インターフェース+メソッド

1. ジェネリクス+タイプスイッチ

Goでオーバーロードを再現
func print[T int | string](values... T) {
	var result int
	for _, value := range values {
		
		switch v := any(value).(type) {
		case int:
			result += v
		case string:
			strNum, _ := strconv.Atoi(v)
			result += strNum
		}
	}
	fmt.Println(result)
}

func main() {
	print(5, 10)	// 15
	print("8", "2")	// 10
}

かなりめんどくさい

ジェネリクスで[]T型にintかstringのみを許可して引数にとる
範囲の繰り返し毎にany型にキャストしてタイプスイッチで型を判別する
=>intならresultに加算
=>stringならstrconvパッケージのAtoi関数を使ってintに変換してからresultに加算
最後にresultを出力

float64型とか受け入れる型が多い場合はジェネリクスじゃなくて素直にinterface{}型を引数にとった方が楽

2. インターフェース+メソッド

インターフェース
type myInter interface {
	print()
}

type Integer []int
func (i Integer)print() {
	var result int
	for _, v := range i {
		result += int(v)
	}
	fmt.Print(result)
}

type String []string
func (s String)print() {
	var result int
	for _, v := range s {
		strNum, _ := strconv.Atoi(v)
		result += strNum
	}
	fmt.Print(result)
}

func main() {
	i := Integer{5, 10}
	i.print()
	
	s := String{"2", "8"}
	myInter.print(s)
}

コード量が増えているがメソッドの数が増えればこちらの方がコード量が少なくなる(はず)

type キーワードを使って独自の型を実装して、それをもとにメソッドを作る
型が違えば同じメソッド名が使えるので要件の数だけ用意する
あとはメソッドの呼び出しをすれば同じようなことは実現できる

最後の myInter.print(s) はメソッド式の応用、あまり使わないと思うけど一応こういう使い方もできる

まとめ

コンストラクタもオーバーロードも直感的に使えて便利
オブジェクト指向の優秀さがわかる章だと感じた

コンストラクタの挙動など調べていて思ったが、こういう細かい仕様を調べるのは結構好きかもしれない

Discussion