0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Gemini に Go 言語で Java の ArrayListを作らせてみた (第一回 まずは作ってもらった)

Last updated at Posted at 2025-07-09

なぜ Go 言語にコレクションクラスがないんだ!そうだ。 AI に作らせよう。

という訳で、Gemini だけで ArrayListGo で作らせてみました。

そのときの内容を6回に分けてお伝えしていこうと思います。

第一回 まずは作ってもらった

分割して掲載する都合上、実際に質問を投げた順番と掲載の順番とを少々入れ替えてあります。

XD.GROWTHで私が担当しているアプリケーションはGo言語で作られているのですが、
標準のスライスやマップの操作になかなか慣れることができず、

例えばJavaのようなコレクションやストリームのように
扱うことのできるものがほしかったので、
必要なメソッドだけ備えたライブラリを自作して運用しています。

このライブラリのメンテナンスに意外と時間を取られてしまうこともあるので、
今回はそれをAIに作ってもらうことにしました。

質問

ほぼ丸投げで質問を投げてみました。

Go言語でJavaのArrayListと完全に互換性のある構造体を作ってください

回答

承知いたしました。Go言語でJavaのArrayListと完全に互換性のある構造体を直接作成することはできません。

Go言語とJavaのArrayListの根本的な違い

JavaのArrayListは、オブジェクト指向の継承やインターフェース、ジェネリクスを多用しており、実行時に要素の型安全性を保証しつつ、動的なサイズ変更が可能です。

一方、Go言語はシンプルさを重視した設計思想を持ち、ジェネリクスが比較的新しく導入されたこともあり、Javaのような完全なオブジェクト指向の継承モデルはありません。また、Go言語のスライスは、ArrayListと同様の動的配列の機能を提供しますが、完全に互換性があるわけではありません。

Go言語で同等の機能を持つ構造体を作成するアプローチ

ArrayListの主要な機能 (要素の追加、削除、取得、サイズ取得など) をGo言語で模倣するには、スライスとジェネリクスを組み合わせて、汎用的な動的配列を実装することになります。

以下に、JavaのArrayListと似た機能を提供するGo言語の構造体の例を示します。

package main

import (
	"errors"
	"fmt"
)

// MyArrayList はJavaのArrayListに似た機能を提供するジェネリックな動的配列です。
type MyArrayList[T any] struct {
	elements []T
	size     int
}

// NewMyArrayList は新しいMyArrayListを初期化して返します。
func NewMyArrayList[T any]() *MyArrayList[T] {
	return &MyArrayList[T]{
		elements: make([]T, 0),
		size:     0,
	}
}

// Add はリストの末尾に指定された要素を追加します。
func (list *MyArrayList[T]) Add(element T) {
	list.elements = append(list.elements, element)
	list.size++
}

// Get はリスト内の指定された位置にある要素を返します。
// インデックスが範囲外の場合はエラーを返します。
func (list *MyArrayList[T]) Get(index int) (T, error) {
	if index < 0 || index >= list.size {
		var zeroValue T // Tのゼロ値
		return zeroValue, errors.New("IndexOutOfBoundsException: Index " + fmt.Sprint(index) + " out of bounds for length " + fmt.Sprint(list.size))
	}
	return list.elements[index], nil
}

// Remove はリスト内の指定された位置にある要素を削除します。
// 削除された要素を返し、リストのサイズを小さくします。
// インデックスが範囲外の場合はエラーを返します。
func (list *MyArrayList[T]) Remove(index int) (T, error) {
	if index < 0 || index >= list.size {
		var zeroValue T
		return zeroValue, errors.New("IndexOutOfBoundsException: Index " + fmt.Sprint(index) + " out of bounds for length " + fmt.Sprint(list.size))
	}
	removedElement := list.elements[index]
	list.elements = append(list.elements[:index], list.elements[index+1:]...)
	list.size--
	return removedElement, nil
}

// Size はリスト内の要素の数を返します。
func (list *MyArrayList[T]) Size() int {
	return list.size
}

// IsEmpty はリストに要素が含まれていない場合にtrueを返します。
func (list *MyArrayList[T]) IsEmpty() bool {
	return list.size == 0
}

// Clear はリストからすべての要素を削除します。
func (list *MyArrayList[T]) Clear() {
	list.elements = make([]T, 0)
	list.size = 0
}

// String はMyArrayListの文字列表現を返します。
func (list *MyArrayList[T]) String() string {
	return fmt.Sprintf("%v", list.elements)
}

func main() {
	// 文字列型のMyArrayListの例
	stringList := NewMyArrayList[string]()
	stringList.Add("apple")
	stringList.Add("banana")
	stringList.Add("cherry")
	fmt.Println("String List:", stringList) // [apple banana cherry]
	fmt.Println("Size:", stringList.Size()) // 3

	val, err := stringList.Get(1)
	if err == nil {
		fmt.Println("Get(1):", val) // banana
	} else {
		fmt.Println("Error:", err)
	}

	removed, err := stringList.Remove(0)
	if err == nil {
		fmt.Println("Removed:", removed) // apple
	} else {
		fmt.Println("Error:", err)
	}
	fmt.Println("String List after removal:", stringList) // [banana cherry]
	fmt.Println("Size after removal:", stringList.Size()) // 2

	// int型のMyArrayListの例
	intList := NewMyArrayList[int]()
	intList.Add(10)
	intList.Add(20)
	fmt.Println("Int List:", intList)    // [10 20]
	fmt.Println("Size:", intList.Size()) // 2

	// 範囲外アクセス
	_, err = stringList.Get(10)
	if err != nil {
		fmt.Println("Error accessing out of bounds:", err) // Error accessing out of bounds: IndexOutOfBoundsException: Index 10 out of bounds for length 2
	}
}

解説

  • MyArrayList[T any]: Go 1.18で導入されたジェネリクスを使用しています。これにより、Tという型パラメータを使って、任意の型の要素を格納できる動的な配列を作成できます。anyは任意の型を表します。
  • elements []T: 内部的にはGo言語のスライスを使用しており、これが動的な配列の役割を果たします。
  • size int: 現在リストに格納されている要素の数を保持します。JavaのArrayListにおけるsize()メソッドに対応します。
  • メソッド:
    • Add(element T): 要素をリストの末尾に追加します。append関数を利用しています。
    • Get(index int) (T, error): 指定されたインデックスの要素を取得します。範囲外アクセスの場合にはerrorを返します。
    • Remove(index int) (T, error): 指定されたインデックスの要素を削除します。Goのスライス操作を使って要素を削除し、サイズを更新します。削除された要素とエラーを返します。
    • Size() int: 現在の要素数を返します。
    • IsEmpty() bool: リストが空かどうかを返します。
    • Clear(): リストからすべての要素を削除し、内部のスライスを再初期化します。
    • String(): fmt.Stringer インターフェースを実装しており、fmt.Printlnなどで構造体を出力した際に内容が表示されるようにします。

制限事項と考慮事項

  • 完全な互換性ではない: 上述の通り、Go言語とJavaの言語設計思想の違いから、JavaのArrayListが持つすべての細かな挙動 (例: trimToSize(), ensureCapacity(), 複雑なイテレータのサポート、コレク> ションフレームワークとの連携など)を完全に模倣することは困難です。
  • エラーハンドリング: Javaでは例外 (IndexOutOfBoundsExceptionなど)をスローしますが、Go言語では慣習的にエラーを返す形になります。上記の例でもエラーを返すように実装しています。
  • パフォーマンス: 内部的にはスライス操作が行われます。特にRemove操作は、要素の移動が発生するため、大規模なリストで頻繁に中央の要素を削除するとパフォーマンスに影響を与える可能性があります。

このMyArrayListは、Go言語のイディオムに則りながら、JavaのArrayListが提供する主要な「動的配列」としての機能を提供するものです。

感想

まさかたった一言でここまでのものが出てくるとは思っていませんでした。
プログラミング言語の参考書のような使用例や解説付きで回答がくるのは感動しました。

size フィールド

先ほど作られたソースをぱっと見て気づいたのは、
スライスを使っているのにMyArrayList構造体にsizeフィールドが定義されていたこと。

確かに、ArrayListではsizeフィールドを持っていて、
配列のどこまで使っているかを持っていますが、
Goのスライスはそもそも配列へのポインタとサイズで構成されているものなので不要です。

質問

以下のように質問してみました。

sizeフィールドはlen()を使ってください

回答

承知いたしました。size フィールドを持たず、len() 関数を使って現在の要素数を取得するように変更します。Go言語のスライスは、len() 関数で現在の要素数を取得するのがイディオムであり、size フィールドを別途持つ必要はありません。

以下に修正したコードを示します。

package main

import (
	"errors"
	"fmt"
)

// MyArrayList はJavaのArrayListに似た機能を提供するジェネリックな動的配列です。
type MyArrayList[T any] struct {
	elements []T
	// size int // sizeフィールドは不要
}

// NewMyArrayList は新しいMyArrayListを初期化して返します。
func NewMyArrayList[T any]() *MyArrayList[T] {
	return &MyArrayList[T]{
		elements: make([]T, 0),
	}
}

// Add はリストの末尾に指定された要素を追加します。
func (list *MyArrayList[T]) Add(element T) {
	list.elements = append(list.elements, element)
}

// Get はリスト内の指定された位置にある要素を返します。
// インデックスが範囲外の場合はエラーを返します。
func (list *MyArrayList[T]) Get(index int) (T, error) {
	if index < 0 || index >= len(list.elements) { // len()を使用
		var zeroValue T // Tのゼロ値
		return zeroValue, errors.New("IndexOutOfBoundsException: Index " + fmt.Sprint(index) + " out of bounds for length " + fmt.Sprint(len(list.elements)))
	}
	return list.elements[index], nil
}

// Remove はリスト内の指定された位置にある要素を削除します。
// 削除された要素を返し、リストのサイズを小さくします。
// インデックスが範囲外の場合はエラーを返します。
func (list *MyArrayList[T]) Remove(index int) (T, error) {
	if index < 0 || index >= len(list.elements) { // len()を使用
		var zeroValue T
		return zeroValue, errors.New("IndexOutOfBoundsException: Index " + fmt.Sprint(index) + " out of bounds for length " + fmt.Sprint(len(list.elements)))
	}
	removedElement := list.elements[index]
	list.elements = append(list.elements[:index], list.elements[index+1:]...)
	return removedElement, nil
}

// Size はリスト内の要素の数を返します。
func (list *MyArrayList[T]) Size() int {
	return len(list.elements) // len()を使用
}

// IsEmpty はリストに要素が含まれていない場合にtrueを返します。
func (list *MyArrayList[T]) IsEmpty() bool {
	return len(list.elements) == 0 // len()を使用
}

// Clear はリストからすべての要素を削除します。
func (list *MyArrayList[T]) Clear() {
	list.elements = make([]T, 0)
}

// String はMyArrayListの文字列表現を返します。
func (list *MyArrayList[T]) String() string {
	return fmt.Sprintf("%v", list.elements)
}

func main() {
	// 文字列型のMyArrayListの例
	stringList := NewMyArrayList[string]()
	stringList.Add("apple")
	stringList.Add("banana")
	stringList.Add("cherry")
	fmt.Println("String List:", stringList)
	fmt.Println("Size:", stringList.Size()) // Size()メソッドはlen()を使用

	val, err := stringList.Get(1)
	if err == nil {
		fmt.Println("Get(1):", val)
	} else {
		fmt.Println("Error:", err)
	}

	removed, err := stringList.Remove(0)
	if err == nil {
		fmt.Println("Removed:", removed)
	} else {
		fmt.Println("Error:", err)
	}
	fmt.Println("String List after removal:", stringList)
	fmt.Println("Size after removal:", stringList.Size()) // Size()メソッドはlen()を使用

	// int型のMyArrayListの例
	intList := NewMyArrayList[int]()
	intList.Add(10)
	intList.Add(20)
	fmt.Println("Int List:", intList)
	fmt.Println("Size:", intList.Size())

	// 範囲外アクセス
	_, err = stringList.Get(10)
	if err != nil {
		fmt.Println("Error accessing out of bounds:", err)
	}
}

変更点

  • MyArrayList 構造体から size int フィールドを削除しました。
  • Add メソッドから list.size++ を削除しました。
  • Remove メソッドから list.size-- を削除しました。
  • Get, Remove, Size, IsEmpty メソッドで list.size の代わりに len(list.elements) を直接使用するように変更しました。
  • Clear メソッドでは list.elements = make([]T, 0) のみでよく、list.size = 0 は不要になりました。

これにより、Go言語のスライスのイディオムに沿った、より簡潔な実装になりました。

感想

すばらしい!

まとめ

でもやっぱり、完璧 (=頭の中に思い描いた通り) なものは出てくるはずもなく、
次回からは質問を重ね、頭の中に思い描いたArrayListへと近づけていきたいと思います。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?