プログラミング自主学習 DAY66 Generic type/wildcard type(共変性)
Generic
Generic Type
また、決定されていないタイプをパラメータをもっているクラスとインタフェースのことだ。
public class ClassName <A,B...> {....}
public interface InterfaceName <A,B...>{...}
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に様々な種類とモデルをセーブするためだ。
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
このように、キャストなしでも、参照アドレスを代入することができる。
次は、ジェネリックタイプのインターフェースを具象してみる。
public interface Rentable<P> {
P rent();
}
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つの生成し、メソッドを呼び出す。
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であるためだ。
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を呼び出す。
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
public class Box<T> {
private T t;
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
}
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のメソッドだ。
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のスパークラスのみ入れる。
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を宣言した。
public class Applicant<T> {
public T kind;
public Applicant(T kind) {
this.kind = kind;
}
}
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を
宣言し、テストする。
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