プログラム言語 ジェネリック

概要

設計時でなく、インスタンス作成時に型を決められる配列(クラス)
型だけ違って処理の内容が同じクラスを設計する際に用いる
ジェネリクス、ジェネリックス
(generic/generics)

ラッパークラス

ラップ(包む)
クラスの機能を利用する際に、
継承するのではなく、クラス内でそのクラスをインスタンス化&メソッドを呼び出し、
外部からは利用するクラスが見えない様に包み込む方法
class Other{
  void method(){ ~ }
}
class Mine{
  Other other;
  Mine(){
    this.other = new Other();
  }
  void method(){
    this.other.method();
  }
}
public static void main( ~ ){
  Mine mine = new Mine();
  mine.method();
  ※内部ではOther.method()が呼ばれている。
}
または、JAVAにおいて、基本型をObjectとして用いる為のクラス
ここではジェネリックに渡す引数
void method(Object obj)
  ~
}
method(引数);
※引数にはObject型を基底クラスとする型しか使えない
int等の基本型は×
Integer i = new Integer(100);
method(i);
は可。
なお、IDEの機能により結局直接数値を渡してもコンパイル可。

byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

汎用的な型

using System.Collections.Generic

//Tは汎用的な型(命名は自由)
public class MyList<T>
{
  T[] _list = new T[1000];
  
  public T this[int index]
  {
    get { return _list[index]; }
    set { _list[index] = value; }
  }
}

public void Form1_Load(object sender, EventArgs e)
{
  MyList<int> myList = new MyList<int>();
  ※T:intとしてインスタンスを作成
  
  for (var i = 0; i < 10; i++)
  {
    myList[i] = i;
    ※myList[i]にstring型の値を入れるとエラー
  }
}

【テンプレート】
コンパイル時にコードを生成する機能
型だけが異なる大量のコードを生成する時等に役立つ。

ベクターについては「プログラム言語 配列・リスト/リスト(コレクション)」参照

#include <vector>
#include <string>

汎用的な型:Tを定義
template <typename T>
class Stack
{
private:
  std::vector<T> stack_;
public:
  void Push(T value);
  T Pop();
  bool IsEmpty();
};

template <typename T>
void Stack<T>::Push(T value)
{
  this->stack_.push_back(value);
}

template <typename T>
T Stack<T>::Pop()
{
  T value = this->stack_.back();
  this->stack_.pop_back();
  return value;
}

template <typename T>
bool Stack<T>::IsEmpty()
{
  if (this->stack_.empty())
  {
    return true;
  }
  else
  {
    return false;
  }
}

int main(array<System::String ^> ^args)
{
  // インスタンス作成時に型を決められる

  Stack<std::string> *myStack = new Stack<std::string>();
  
  bool myBool = myStack->IsEmpty();
  // myBool:True
  myStack->Push("A");
  myStack->Push("B");
  myStack->Push("C");
  std::string myStr = myStack->Pop();
  // myStr:"C"
  myBool = myStack->IsEmpty();
  // myBool:False

  return 0;
}

Imports System.Collections.Generic

Public Class MyList(Of T)
  Dim _list(1000) As T
  
  Default Public Property list(ByVal index As Integer) As T
    Get
      Return _list(index)
    End Get
    Set(value As T)
      _list(index) = value
    End Set
  End Property
End Class

Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
  
  Dim myList As New MyList(Of Integer)
  ※T:Integerとしてインスタンスを作成
  
  For i As Integer = 0 To 10 Step 1
    myList(i) = i
    ※myList[i]にstring型の値を入れるとエラー
  Next i
  
End Sub

ジェネリッククラス
public class MyGeneric<T> {
 T t;
 public MyGeneric(T prmT){
  this.t = prmT;
 }
 public T getT(){
  return this.t;
 }
}
public class StudyMain {
 public static void main(String[] args) {

  MyGeneric<String> m1 = new MyGeneric<String>("java");
  String s = m1.getT();
  MyGeneric<Integer> m2 = new MyGeneric<Integer>(100);
  Integer i = m2.getT();
 }
}



ジェネリックメソッド
public class MyGeneric {
 public <T> T retT(T prmT){
  T t = prmT;
  return t;
 }
}

public class StudyMain {
 public static void main(String[] args) {

  MyGeneric m1 = new MyGeneric();
  String s = m1.retT("Java");
  MyGeneric m2 = new MyGeneric();
  int i = m2.retT(5);
 }
}



ジェネリックインターフェース
public interface MyInterface<T> {
 public T myMethod();
 public void myMethod(T t);
}

public class MyGeneric implements MyInterface<String> {
 @Override
 public String myMethod() {
  return "Java";
 }
 @Override
 public void myMethod(String t) {
  ~
 }
}

public class StudyMain {
 public static void main(String[] args) {
  MyGeneric m = new MyGeneric();
  String s = m.myMethod();
  m.myMethod("Java");
 }
}

静的ジェネリッククラスコンストラクタ

static class MyClass<T>
{
 public static T myValue { get; set; }
 static MyClass()
 {
  Console.WriteLine(@"初期化");
 }
}

MyClass<string>.myValue = @"aaa";
MyClass<int>.myValue = 100;
MyClass<string>.myValue = @"bbb";
Console.WriteLine(MyClass<string>.myValue);
Console.WriteLine(MyClass<int>.myValue);


初期化 ※T=string時
初期化 ※T=int時
bbb
100
※最初の使用時のみコンストラクタが実行される。

制約

基本クラス制約

型(T)=基本クラス(基底クラス)

using System.Collections.Generic;

class Parent
{
 public string pFunc(string str) { return str; }
}

class Child : Parent { }

class Other {}

型(T)=基本クラス
class Test<T> where T : Parent
{
 T obj;
 public Test(T o) { this.obj = o; }
 T はParent型。o はParentのインスタンス
 public void testFunc(string prm)
 {
  string revStr = this.obj.pFunc(str: prm);
  this.objはParentのインスタンスなので、Parentのメンバー関数を呼び出し可能
  Console.WriteLine(revStr);
 }
}

class Program
{
 static void Main(string[] args)
 {

  Parent p = new Parent();
  Child c = new Child();
  
  Test<Parent> t1 = new Test<Parent>(o: p);
  t1.testFunc(prm: @"aaa");
  
  基本クラス制約の型(クラス)は派生クラスでもOK
  Test<Child> t2 = new Test<Child>(o: c);
  t2.testFunc(prm: @"aaa");
  
  Other o = new Other();
  Test<Other> t3 = new Test<Other>(o: o);
  →コンパイルエラー:Parentを継承していないクラスを型パラメータとして指定できない
  
  Test<string> t3 = new Test<string>(o: @"aaa");
  →コンパイルエラー:Parentを継承していないクラスを型パラメータとして指定できない

 }
}

public class MyGeneric<T extends Number> {
 public T myMethod() {
  T t = null;
  return t;
 }
 public void myMethod(T t) {
  ~
 }
}

public class StudyMain {
 public static void main(String[] args) {
  MyGeneric<Integer> m1 = new MyGeneric<Integer>();
  int i = m1.myMethod();
  m1.myMethod(100);
  m1.myMethod(5.5); →エラー

  MyGeneric<Double> m2 = new MyGeneric<Double>();
  Double d = m2.myMethod();
  m2.myMethod(5.5);
  m2.myMethod(100); →エラー

  MyGeneric<String> m1 = new MyGeneric<String>(); →エラー
 }
}

ワイルドカード
public class X{
 public String toString(){
  return "X";
 }
}

public class Y extends X{
 public String toString(){
  return "Y";
 }
}

public class Main {

 list = X、またはXの子クラス
 public static void method1(List<? extends X> list){

  実行時まで型不明の為、型を指定したaddはコンパイルエラー
  list.add(new X()); エラー
  list.add(new Y()); エラー
  System.out.println(list.get(0));
 }

 list = Y、またはYの親クラス
 public static void method2(List<? super Y> list){
  実行時まで型不明の為、型を指定したaddはコンパイルエラー
  list.add(new X()); エラー
  list.add(new Y()); YはOK
  System.out.println(list.get(0));
  System.out.println(list.get(1));
 }

 public static void main(String[] args) {
  List<X> l1 = new ArrayList<>(); l1.add(new X());
  List<Y> l2 = new ArrayList<>(); l2.add(new Y());

  method1(l1); →X
  method1(l2); →Y

  method2(l1); →X Y
  method2(l2); →Y Y
 }
}

インターフェイス制約

型(T)=指定のインターフェイスを実装している型(クラス)

using System.Collections.Generic;

interface IParent
{
 string pFunc(string str);
}

class Child : IParent{ }

class Other {}

型(T)=インターフェイス
class Test<T> where T : IParent
{
 T obj;
 public Test(T o) { this.obj = o; }
 T はIParentを継承した型(クラス)。o はそのインスタンス
 public void testFunc(string prm)
 {
  string revStr = this.obj.pFunc(str: prm);
  this.objはIParentを継承したクラスのインスタンスなので、IParentのメンバー関数を呼び出し可能
  Console.WriteLine(revStr);
 }
}

class Program
{
 static void Main(string[] args)
 {

  Child c = new Child();
  Test<Child> t1 = new Test<Child>(o: c);
  t1.testFunc(prm: @"aaa");
  
  Other o = new Other();
  Test<Other> t2 = new Test<Other>(o: o);
  →コンパイルエラー:IParentを継承していないクラスを型パラメータとして指定できない
  
  Test<string> t3 = new Test<string>(o: @"aaa");
  →コンパイルエラー:IParentを継承していないクラスを型パラメータとして指定できない

 }
}

コンストラクタ制約

型(T)=デフォルトコンストラクタが実装されている型(クラス)

class MyClass1
{
 public MyClass1(string prm){}
}

class MyClass2
{
 public MyClass2(){}
 public MyClass2(string prm){}
}

class Test<T> where T : new()
{
 public Test()
 {
  T t = new T();
  コンストラクタ制約があるのでジェネリック型(クラス)のインスタンスを作成できる。
  制約が無ければコンパイルエラー

 }
}

static void Main(string[] args)
{

 Test<MyClass1> t1 = new Test<MyClass1>();
 MyClass1はデフォルトコンストラクタが定義されていないのでコンパイルエラー

 
 Test<MyClass2> t2 = new Test<MyClass2>();
 MyClass2はデフォルトコンストラクタがあるので多重定義があってもOK
}

参照型制約

型(T)=参照型

class Test<T> where T : class
{
 T obj;
 public Test(T prm)
 {
  this.obj = prm;
 }
}

static void Main(string[] args)
{

 Test<string> t = new Test<string>(prm: @"aaa");
 Test<string> t = new Test<string>(prm: null);
 Test<string> t = new Test<string>(prm: string.Empty);
 Test<MyClass> t = new Test<MyClass>(prm: new MyClass());
 Test<Test<string>> t = new Test<Test<string>>(prm: new Test<string>(prm: @"aaa"));

 ↓ コンパイルエラー
 Test<int> t = new Test<int>(prm: 5);
 Test<bool> t = new Test<bool>(prm: true);

}

値照型制約

型(T)=値型

class Test<T> where T : struct
{
 T obj;
 public Test(T prm)
 {
  this.obj = prm;
 }
}

struct MyStruct
{
 public int myInt;
}

class MyClass { }

static void Main(string[] args)
{

 Test<int> t = new Test<int>(prm: 5);
 Test<bool> t = new Test<bool>(prm: true);
 Test<MyStruct> t = new Test<MyStruct>(prm: new MyStruct() { myInt = 5 });
 
 ↓ コンパイルエラー
 Test<string> t = new Test<string>(prm: string.Empty);
 Test<MyClass> t = new Test<MyClass>(prm: new MyClass());

}

制約の順番

(1)→(3)の順で記述
(1)参照型制約、値型制約、基本クラス制約
どれか1つだけ
(2)インターフェイス制約
(3)コンストラクタ制約

interface IMyInterface{}
class MyClass{}

○:class Test<T> where T : MyClass, new() { }
○:class Test<T> where T : class, IMyInterface, new() { }
×:class Test<T> where T : IMyInterface, MyClass, new() { } ※順序違反
×:class Test<T> where T : new(), MyClass, IMyInterface { } ※順序違反
×:class Test<T> where T : class, struct { } ※(1)重複違反

複数の型パラメーターに別の制約を付ける場合
class Test<T1, T2>
 where T1 : class
 where T2 : struct
{ }