🗂

プログラミング自主学習 DAY66 Generic type/wildcard type(共変性)

2023/08/01に公開

Generic

Generic Type

また、決定されていないタイプをパラメータをもっているクラスとインタフェースのことだ。

public class ClassName <A,B...> {....}
public interface InterfaceName <A,B...>{...}

Product

public class Product<K,M>{
  
  private K kind;
  private M model;
  
  public K getKind() {return this.kind;}
  public K getModel() {return this.model;}
  public void setKind(K kind) {this.kind = kind;}
  public void setModel(M model) {this.model = model;}

このように、Genericを活用する理由は、Productに様々な種類とモデルをセーブするためだ。

GenericExample

public class GenericExample {

	public static void main(String[] args) {
	
	Product<Tv, String> product1 = new Product<>() ;
	
	product1.setKind(new Tv());
	product1.setModel("Smart TV");
	
	Tv tv = product1.getKind();
	String model = product1.getModel();
	
	System.out.println(tv + " " + model);
	
	Product<Car, String> product2 = new Product<>();
	
	product2.setKind(new Car());
	product2.setModel("HONDA");
	
	Car car = product2.getKind();
	String model2 = product2.getModel();
	
	System.out.println(car + " " + model2);

	}

}

ch13.sec02.exam01.Tv@1eb44e46 Smart TV
ch13.sec02.exam01.Car@379619aa HONDA

このように、キャストなしでも、参照アドレスを代入することができる。
次は、ジェネリックタイプのインターフェースを具象してみる。

Rentable
public interface Rentable<P> {
	P rent();

}
Car
public class Car {
   public void run() {
   System.out.println("Car is moving fast");
	}
}

```java: CarAgency
public class CarAgency implements Rentable<Car>{
   @Override
   public Car rent() {
	return new Car();
    }
}

このように、タイプパラメータを持っているインターフェース「Rentable」を具象するクラスを2つの生成し、メソッドを呼び出す。

GenericExample
public class GenericExample {
public static void main(String[] args) {
	
  HomeAgency homeagency = new HomeAgency();
  Home home = homeagency.rent();
  home.turnOnLight();

  CarAgency caragency = new CarAgency();
  Car car = caragency.rent();
  car.run();
	
  }
	
}

Turn on Light
Car is moving fast

タイプパラメータは、objectタイプにみなされるため、Objectが持っているメソッドを呼び出すことができる。すべてのクラスはObjectであるためだ。

Box
public class Box<T> {
	public T content;
	
//equals
public boolean compare(Box<T> other) {
	boolean result = content.equals(other.content);
	return result;
    }
}

まだ、何が入るかわからないため、<T>で指定する。パラメータも<T>に指定し、equalsもまずはどのクラスもアップキャストができるように、objectのequalsにされている。

プログラムのランタイム時、contentsのタイプが決定され、equalsもポリモーフィズムにより、アップキャストされる。ダイナミックバインディングにより、実行時には、サブクラスのequlasを呼び出す。

GenericExample

public class GenericExample {

public static void main(String[] args) {
	Box box1 = new Box();
	box1.content ="100"; //String
		
	Box box2 = new Box();
	box2.content = "100";  //String
		
	Box box3 = new Box();
	box3.content = 100; //Integer
		
	boolean result1 = box1.compare(box2);  //String.equals
	System.out.println("result1: " + result1);
		
	boolean result2 = box1.compare(box3);  //String.equals
	System.out.println("result2: " + result2);
	}

}

result1: true
result2: false

Generic Method

Box

public class Box<T> {
	
	private T t; 
	
	public T get() {
		return t;
	}
	
	public void set(T t) {
		this.t = t;
	}
}

GenericExample
public class GenericExample {
	
	public static <T> Box<T> boxing(T t){
		Box<T> box = new Box<>();
		box.set(t);
		return box;
	}

	public static void main(String[] args) {
	
		Box<Integer> box1 = boxing(100);
		int intValue = box1.get();
		System.out.println(intValue);
		
		Box<String> box2 = boxing("홍길동");
		String strValue = box2.get();
		System.out.println(strValue);
		
	

	}         

}


Bounded type parameter(境界型パラメータ)

例えば、数字を演算するジェネリックメソッドの場合、タイプパラメータを制限する必要がある。本来は、Tは指定しないかぎり、objectを使用するが、このように、特定なタイプより入るパラメータを境界型パラメータと呼ぶ。

public <T extends Superclass> Returntype methodname (Parameter...)	
	

この場合は、アップキャストの原理を利用し、extends 上位クラスを活用する。
インタフェースを制限する場合もimplementsではなく、extendsを入力する。

public <T extends Number> boolean compare (T t1, T t2) {
   double v1 = t1.doubleValue();
   double v2 = t2.doubleValue();
   return(v1==v2);
}	

この場合は、Tが制限され、Objectのメソッドのみならず、Numberが持っているメソッドを呼び出すことができる。doubleValue()は、Numberのメソッドだ。

GenericExample

public class GenericExample {
	
public static <T extends Number> boolean compare(T t1, T t2) {
	
System.out.println("compare(" + t1.getClass().getSimpleName() + "," +t2.getClass().getSimpleName() + ")");
	
    double v1 = t1.doubleValue(); //Nu,ber's method
    double v2 = t2.doubleValue(); //Nu,ber's method

    return(v1==v2);
}

public static void main(String[] args) {
		
	 boolean result1 = compare(10, 20);
	 System.out.println(result1);	
	 System.out.println();
	 
	 boolean result2 = compare(4.5,4.5);
	 System.out.println(result2);
	 System.out.println();
	 
	}

}		
	
compare(Integer,Integer)
false

compare(Double,Double)
true	
	

Wildcard type Parameter(非境界ワイルドカード型)

ジェネリックタイプのタイプパラメータは、Objectもしくは、extendsを通して、特定クラスのサブクラスまで自由に入ることができる。

しかし、ジェネリックは致命的な短所がある。

「共変性がない。」

我々が、オブジェクト指向ができる理由の一つ(LSPが守れる理由にも繋がる)はポリモーフィズムであり、その中でもアップキャストとダウンキャストで様々なものを具象できるためだ。
しかし、ジェネリックタイプは一度タイプが決れば、他のジェネリックのオブジェクトとは
形変換ができない。

ArrayList<Object> parent = new ArrayList<>();
ArrayList<Integer> child = new ArrayList<>();

parent = child; // error
child = parent; // error
	

その問題を改善するため、使うことがWildcard type parameterである。
wildcard typeは三つがある。

<?> : どのタイプでも利用できる。
<? extends B>: BとBを継承したサブクラスのみ入れる。
<? super B> :BとBのスパークラスのみ入れる。

Person

public class Person {
}

class Worker extends Person{
}

class Student extends Person{
}

class HighStudent extends Student{
}

class MiddleStudent extends Student{
}

Wildcard parameterを勉強するため、コードを作成してみた。
PersonというスーパクラスとPersonを継承したWorker,Studentクラス、Studentクラスを継承したHighStudent、MiddleStudentを宣言した。

Applicant
public class Applicant<T> {
	public T kind;
	
public Applicant(T kind) {
	this.kind = kind;
   }

}

Course
public class Course {
	//Personであれば、皆登録可能
	public static void registerCourse1(Applicant<?> applicant) {
		System.out.println(applicant.kind.getClass().getSimpleName());
	}
	
	//Studentであれば、登録可能
	public static void registerCourse2(Applicant<? extends Student> applicant) {
		System.out.println(applicant.kind.getClass().getSimpleName());
	}
	
	
	public static void registerCourse3(Applicant<? super Worker> applicant) {
		System.out.println(applicant.kind.getClass().getSimpleName());
	}
	
	

}

ジェネリックタイプのApplicantクラスとWild card typeをパラメータにもっているCourseを
宣言し、テストする。

GenericExample
public class GenericExample {

	public static void main(String[] args) {
		//全ての人が登録可能
		Course.registerCourse1(new Applicant<Person>(new Person()));
		Course.registerCourse1(new Applicant<Worker>(new Worker()));
		Course.registerCourse1(new Applicant<Student>(new Student()));
		Course.registerCourse1(new Applicant<HighStudent>(new HighStudent()));
		Course.registerCourse1(new Applicant<MiddleStudent>(new MiddleStudent()));
		System.out.println();
		
		//全ての学生が登録可能
		//Course.registerCourse2(new Applicant<Person>(new Person()));
		//Course.registerCourse2(new Applicant<Worker>(new Worker()));
		Course.registerCourse2(new Applicant<Student>(new Student()));
		Course.registerCourse2(new Applicant<HighStudent>(new HighStudent()));
		Course.registerCourse2(new Applicant<MiddleStudent>(new MiddleStudent()));
		System.out.println();
		
		//全ての社会人と一般字のみ
		Course.registerCourse3(new Applicant<Person>(new Person()));
		Course.registerCourse3(new Applicant<Worker>(new Worker()));
		//Course.registerCourse3(new Applicant<Student>(new Student()));
		//Course.registerCourse3(new Applicant<HighStudent>(new HighStudent()));
		//Course.registerCourse3(new Applicant<MiddleStudent>(new MiddleStudent()));
		System.out.println();

	}

}

Person
Worker
Student
HighStudent
MiddleStudent

Student
HighStudent
MiddleStudent

Person
Worker

このように、Wildcard typeを遠して、より様々なパータンでパラメータを設定することができる。

Discussion