2017年7月14日金曜日

Kotlin メモ : data class を継承できないので interface で実現した話

データクラス的な A と B があります。B は A を継承しています。
  1. public class A {  
  2.   
  3.     @NonNull  
  4.     public final String name;  
  5.   
  6.     public A(@NonNull String name) {  
  7.         this.name = name;  
  8.     }  
  9.   
  10.     @Override  
  11.     public boolean equals(Object o) {  
  12.         if (this == o) return true;  
  13.         if (o == null || getClass() != o.getClass()) return false;  
  14.   
  15.         A a = (A) o;  
  16.   
  17.         return name.equals(a.name);  
  18.     }  
  19.   
  20.     @Override  
  21.     public int hashCode() {  
  22.         return name.hashCode();  
  23.     }  
  24. }  
  1. public class B extends A {  
  2.   
  3.     public final int size;  
  4.   
  5.     public B(String name, int size) {  
  6.         super(name);  
  7.         this.size = size;  
  8.     }  
  9.   
  10.     @Override  
  11.     public boolean equals(Object o) {  
  12.         if (this == o) return true;  
  13.         if (o == null || getClass() != o.getClass()) return false;  
  14.         if (!super.equals(o)) return false;  
  15.   
  16.         B b = (B) o;  
  17.   
  18.         return size == b.size;  
  19.     }  
  20.   
  21.     @Override  
  22.     public int hashCode() {  
  23.         int result = super.hashCode();  
  24.         result = 31 * result + size;  
  25.         return result;  
  26.     }  
  27. }  
test はこんな感じです。B が A を継承しているのは、A のリストに B を入れたいからです。
  1. public class ABTest {  
  2.   
  3.     @Test  
  4.     public void test() {  
  5.         final B b = new B("hoge"10);  
  6.         assertEquals("hoge", b.name);  
  7.         assertEquals(10, b.size);  
  8.   
  9.         assertEquals(new B("hoge"10), new B("hoge"10));  
  10.         assertNotEquals(new B("hoge"10), new B("fuga"10));  
  11.         assertNotEquals(new B("hoge"10), new B("hoge"11));  
  12.   
  13.         List<A> aList = new ArrayList<>();  
  14.         aList.add(new B("hoge"10));  
  15.   
  16.         assertEquals(new B("hoge"10), aList.get(0));  
  17.     }  
  18. }  
こういう Java コードがあって、これを Kotlin 化するときに A, B それぞれを data class にしようとしたら、data class は親クラスになれない(final)ので困りました。
このテストが通るように、いい感じに Kotlin 化したいのです。

1. data class にするのを諦める

自分で equals と hasCode を override すれば data class にしなくてもやりたいことはできます。
以下は自動変換しただけのもの。
  1. open class A(val name: String) {  
  2.   
  3.     override fun equals(o: Any?): Boolean {  
  4.         if (this === o) return true  
  5.         if (o == null || javaClass != o.javaClass) return false  
  6.   
  7.         val a = o as A?  
  8.   
  9.         return name == a!!.name  
  10.     }  
  11.   
  12.     override fun hashCode(): Int {  
  13.         return name.hashCode()  
  14.     }  
  15. }  
  1. class B(name: String, val size: Int) : A(name) {  
  2.   
  3.     override fun equals(o: Any?): Boolean {  
  4.         if (this === o) return true  
  5.         if (o == null || javaClass != o.javaClass) return false  
  6.         if (!super.equals(o)) return false  
  7.   
  8.         val b = o as B?  
  9.   
  10.         return size == b!!.size  
  11.     }  
  12.   
  13.     override fun hashCode(): Int {  
  14.         var result = super.hashCode()  
  15.         result = 31 * result + size  
  16.         return result  
  17.     }  
  18. }  
もちろん上のテストは通ります。
しかし、いけてない。

2. A を interface にする

Kotlin では interface に抽象プロパティを持たせることができるので、A を interface に変えてみます。
  1. interface A {  
  2.     val name: String  
  3. }  
こうすると、B を data クラスにできます。
  1. data class B(override val name: String, val size: Int) : A  
めっちゃ短い!
テストもちゃんと通ります。



1 件のコメント:

  1. sealed classを使う、というのも手です。
    https://kotlinlang.org/docs/reference/sealed-classes.html

    返信削除