Kotlin

Bridge to Kotlin Part 9 (Generics)

အရင်အပိုင်းတွေ လေ့လာချင်တယ်ဆိုရင်

Generics တွေကတော့ Java သမားတွေအတွက် အေးဆေး နားလည်နိုင်ပါတယ်။ တစ်ခြား Programming ဖက်ကလူတွေအတွက် နားလည်ဖို့ အခက်အခဲရှိနိုင်ပါတယ်။ အဲဒီအတွက် အသေးစိတ် ပြန်ပြီး ရှင်းပြပါမယ်။ Generic တွေကို Java 5 မှာ စပြီး မိတ်ဆက်ပေးလာတာ ဖြစ်ပါတယ်။

အဓိကအားဖြင့် Generic တွေကို compile-time type checking အတွက် အသုံးပြုတာ ဖြစ်ပါတယ်။ ဆိုလိုတာက compile time မှာ ထည့်လိုက်တဲ့ value တွေရဲ့ Type တွေကို မှန်ကန်မှုကို စစ်ဆေးဖို့အတွက် သတ်မှတ်ပေးလိုက်တဲ့ သဘော ဖြစ်ပါတယ်။ PHP ဖက်မှာဆိုရင်တော့ Type Casting ပုံစံနဲ့ ကြည့်လို့ရပါတယ်။ ကျွန်တော်တို့ လက်တွေ့ လေ့လာကြည့်ရအောင်။ အဲဒီအတွက် ကျွန်တော်တို့ Java နည်းနည်းပါးပါး ရေးဖို့ လိုအပ်ပါတယ်။

List list = new ArrayList();
list.add("abc");
list.add(new Integer(5));
for(Object obj: list) {
    String str = (String) obj;
}

list ဆိုတဲ့ ArrayList Blank တစ်ခု တည်ဆောက်ပြီး Array ထဲကို String နဲ့ Integer ပေါင်းထည့် လိုက်ပါတယ်။ အဲဒီအထိ အဆင်ပြေပါတယ်။ တကယ်တန်း ရိုက်ထုတ်တဲ့အခါ String တွေပဲ လိုချင်တယ်ဆိုပြီး (String) နဲ့ Type Casting လုပ်လိုက်တဲ့အခါ ClassCastException ဆိုပြီး Error တက်လာပါလိမ့်မယ်။

အဲဒီလို အခြေအနေတွေအတွက် compile-time type checking လုပ်နိုင်ဖို့အတွက် Generic ကို သုံးပါတယ်။ ကျွန်တော်တို့ ဒီလိုပြင်ပြီးရေးကြည့်ရအောင်

List<String> list = new ArrayList<String>();

list.add("Hello World");
list.add(new Integer(5));

List အတွင်းမှာ ပါဝင်တဲ့ Element တွေက <String> တွေပဲ ဖြစ်ရမယ်ဆိုပြီး သတ်မှတ်ပေးလိုက်တဲ့ အတွက် အခုလို compile time မှာကတည်းက error ပြနိုင်တာ ဖြစ်ပါတယ်။

Java Generic Class

Java Generic Class တွေကို နားလည်ဖို့ နောက်တစ်ဆင့် ဆက်ပြီး လေ့လာကြည့်ရအောင်

import java.util.ArrayList;
import java.util.List;

public class mainTwo {

    public static void main(String[] args) {
        GenericsTypeOld type = new GenericsTypeOld();
        type.set(45);
        String str = (String) type.get();
    }
}

class GenericsTypeOld {
    private Object t;

    public Object get() {
        return t;
    }

    public void set(Object t) {
        this.t = t;
    }
}

type ကို integer value နဲ့ set လုပ်လိုက်တဲ့အခါ ဘာ Error မှ မပြပေမယ့် runtime မှာတော့ ClassCastException ဆိုပြီး တက်လာပါလိမ့်မယ်။ ကျွန်တော်တို့ Generic Class ကို အသုံးပြုပြီး ရေးကြည့်ပါမယ်။

public class mainTwo {

    public static void main(String[] args) {
        GenericsType<String> type = new GenericsType();
        type.set(new Integer(12));
        
    }
}

class GenericsType<T> {
    private T t;

    public T get() {
        return t;
    }

    public void set(T t) {
        this.t = t;
    }
}

အဲဒီလိုရေးလိုက်တယ်ဆိုရင် ClassCastException Error မတက်တော့ပါဘူး။ အဲဒီအပြင် String Type Cast လုပ်စရာလဲ မလိုတော့ပါဘူး။ အဲဒီနေရာမှာ ကျွန်တော်တို့ သုံးသွားတဲ့ T ဆိုတာ Type ကို ညွှန်းပါတယ်။ အဲဒီလို အသုံးပြုလို့ရနိုင်တဲ့ Java Generics Type တွေကတော့

  • E – Element (Collection တွေမှာ အသုံးပြုပါတယ်, ဥပမာ – ArrayList, Set, etc.)
  • K – Key (Map ရဲ့ Key – Value Pair မှာ သုံးပါတယ်)
  • N – Number
  • T – Type
  • V – Value (Map ရဲ့ Key – Value Pair မှာ သုံးပါတယ်)
  • S,U,V etc. – 2nd, 3rd, 4th types (Type တစ်ခုထက် ပိုလာရင် T, S, U, V ဆိုပြီး အစဉ်လိုက် သုံးနိုင်ပါတယ်။

Generics အကြောင်းကို ဒီထက်ပိုပြီး နားလည်နိုင်ဖို့ ကျွန်တော်တို့ ရှေ့ဆက် လေ့လာစရာတွေ ရှိပါသေးတယ်။ ဒီတစ်ခါတော့ ကျွန်တော်တို့ Kotlin ဖက်ကို ပြန်သွားလို့ရပါပြီ။

ဒီနေရာမှာ တစ်ခု သတိထားဖို့ လိုတာက Java မှာ စမ်းလို့ရတာတွေကို Kotlin မှာ စမ်းလို့ မရနိုင်ဘူးဆိုတာပါ။ အဓိကအားဖြင့် Java မှာ ဖြစ်ခဲ့တဲ့ ပြဿနာပေါင်းစုံကို Kotlin မှာ ဖြေရှင်းပြီးသား ဖြစ်နေုလို့ပါ။  နောက်တစ်ချက် သတိထားဖို့ လိုတာက Generic နဲ့ Type Casting နဲ့ မတူပါဘူး။ Generic က သတ်မှတ်ပေးတဲ့ဖက်က နေတာဖြစ်ပြီး Type Casting ကတော့ ရယူတဲ့ဖက်က နေတာ ဖြစ်ပါတယ်။

Class vs Type

ကျွန်တော်တို့ Generics Class တွေကို လေ့လာနေရင်း ရောထွေးသွားနိုင်တာတွေ ရှိပါတယ်။ အဲဒါတွေကတော့ Class ဆိုတာဘာလဲ Type ဆိုတာ ဘာလဲ စသည်ဖြင့်ပေါ့။

Java မှာရော Kotlin မှာပါ class တွေ အနေနဲ့ အနည်းဆုံး type တစ်ခု သတ်မှတ်ပေးဖို့ ရှိဖို့ လိုပါတယ်။ အဲဒီတော့ class တိုင်းဟာ Type ဖြစ်ပြီး Type တိုင်းကတော့ class မဟုတ်ပါဘူး။ ဥပမာ အနေနဲ့ ပြောရရင် String ကတော့ class ရော type ရော ဖြစ်ပါတယ်။ String? ကတော့ Type ဖြစ်ပါတယ်။ Nullable Type ဖြစ်ပါတယ်။

အဲဒီလိုပဲ List က Class ဖြစ်ပြီး List<String> ကတော့ class မဟုတ်ပဲ type ဖြစ်ပါတယ်။

Subclass vs subtype

နောက်ထပ် နားလည်ဖို့ လိုတာ တစ်ခုက sublclass ဆိုတာ ဘာလဲ၊ subtype ဆိုတာဘလဲ။ အဲဒီနှစ်ခု ဘာကွာလဲဆိုတာပါ။ ကျွန်တော်တို့ နမူနာလေး ရေးကြည့်ရအောင်

val integer: Int = 1
val number: Number = integer

val testInt: Int = 45
val testAnotherInt: Int? = testInt

Int ဆိုတာ Number ရဲ့ Subclass ဖြစ်ပါတယ်။ အဲဒါကြောင့် number ထဲကို integer value ထည့်လို့ရတာပါ။ အဲဒီအပြင် Integer က Number ရဲ့ Sub Type လဲ ဖြစ်ပါတယ်။ နောက်တစ်ခုက Nullable Int က Int ရဲ့ Subtype တစ်ခု ဖြစ်နေတဲ့အတွက် assign လုပ်လို့ရပါတယ်။

Variance

ကျွန်တော်တို့ Program လေးတစ်ခု ရေးကြည့်ရအောင်

fun main(args: Array<String>) {
    val dog: Dog = Dog("Aung Net", "Puppy")
    val cat: Cat = Cat("Shwe War", "red")
    var animal: Animal = dog
    animal = cat
    println(animal.name)
}

abstract class Animal(val name: String)
class Dog(name: String, val breed: String) : Animal(name)
class Cat(name: String, val color: String) : Animal(name)

Variance ကို မြန်မာလို ပြန်ရမယ်ဆိုရင် မူကွဲတွေလို့ ပြန်ရမယ် ထင်ပါတယ်။ Dog တို့ Cat တို့က Animal Class ရဲ့ မူကွဲတွေ ဖြစ်ပါတယ်။ အဲဒီအတွက် Super Class ဖြစ်တဲ့ Animal ကို ပြန်ပြီး Assign လုပ်တော့ လုပ်လို့ရနေတာပါ။  Dog နဲ့ Cat က Animal ရဲ့ မူကွဲ Variance တွေ ဖြစ်ပါတယ်။

ဒီနေရာမှာ ကျွန်တော်က Variance ကို အမျိုးအရင်းလို့ ဘာသာပြန်ချင်ပါတယ်။

Covariance

Coveriance ဆိုတာကို မြန်မာလို ပြန်ရမယ်ဆိုရင်တော့ တစ်ဝမ်းကွဲတွေပေါ့။ 😛 ဥပမာ ရေးကြည့်ရအောင်။ အပေါ်မှာ ရေးခဲ့တဲ့ class တွေကိုပဲ ပြန်သုံးမှာပါ။

val dogList: List<Dog> = listOf(Dog("Aung Net", "Puppy"),
        Dog("Moe Lone", "Great Dane"))
val animalList: List<Animal> = dogList

Dog က Animal ရဲ့ အမျိုးအရင်းတွေပါ။ အဲဒါကို ပြန်သုံးတဲ့ List<Dog> ဟာလဲ List<Animal> ရဲ့ တစ်ဝမ်းကွဲအမျိုးတွေ Coveriance တွေ ဖြစ်လာပါတယ်။

Invariance

Inveriance ကိုတော့ ဘယ်လို မြန်မာပြန်ရမှန်း မသိတော့ပါဘူး။ အမွေပြတ်စွန့်လွှတ်လိုက်တဲ့ သားသမီးလို့ ပြောရင် ရမယ်ထင်ပါတယ်။ ကျွန်တော်တို့ နမူနာလေး ရေးကြည့်ရအောင်။ ဒါပေမယ့် အဲဒါက Java မှာပဲ ဖြစ်တာပါ။ ကျွန်တော်တို့ Kotlin မှာတော့ အဲဒီလို မိသားစု ပြဿနာတွေ မရှိပါဘူး။ ကျွန်တော်တို့ Java မှာ ဒီလို ရေးကြည့်မယ်ဆိုရင်

List<Dog> dogs = new ArrayList();
List<Animal> animals = dogs;

အဲဒီလို Error တက်နေပါလိမ့်မယ်။ အဲဒီလို ဖြစ်တာကို Inveriance လို့ ခေါ်ပါတယ်။

Contravariance

Contravariance ကို မြန်မာလို ဘယ်လိုပြန်ရမယ် ကျွန်တော် မသိတော့ပါဘူး။ အဖေထက်သား တစ်လကြီး ဆက်စပ်မှုလို့ ပြောရင်ရမယ် ထင်ပါတယ်။ ကျွန်တော်တို့ ကုဒ်ရေးကြည့်မယ် ဆိုရင်တော့ နားလည် သွားပါလိမ့်မယ်။

fun main(args: Array<String>) {
    val animalCompare: Compare<Animal> = object : Compare<Animal> {
        override fun compare(first: Animal, second: Animal): Boolean {
            return first.name == second.name
        }
    }
    val dogCompare: Compare<Dog> = animalCompare
    val catCompare: Compare<Cat> = animalCompare
}
interface Compare<in T> {
    fun compare(first: T, second: T): Boolean
}
abstract class Animal(val name: String)
class Dog(name: String, val breed: String) : Animal(name)
class Cat(name: String, val color: String) : Animal(name)

ကျွန်တော်တို့ ရေးထားတဲ့ ကုဒ်ကို လေ့လာကြည့်မယ်ဆိုရင် animalCompare ကို dogCompare နဲ့ catCompare က ပြန်သုံးလို့ရပါတယ်။ အပေါ်မှာတုန်းကတော့ အောက်ကနေ အပေါ်ကို ဘယ်လို ဆက်စပ်နေသလဲအပေါ်မှာ ပြောခဲ့ပေမယ့် ဒီတစ်ခါတော့ အပေါ်ကနေ အောက် ဆက်စပ်မှုပုံစံကို ပြပါတယ်။ ကျွန်တော်တို့ ဒီလိုရေးလိုက်မယ်ဆိုရင်တော့ သုံးလို့ရမှာ မဟုတ်ပါဘူး။ အဖေထက်သားက တစ်လကြီးနေတော့ ယှဉ်လို့ မရတော့တဲ့ သဘောဆိုပါတော့။

val myDogCompare: Compare<Dog> = object : Compare<Dog> {
    override fun compare(first: Dog, second: Dog): Boolean {
        return first.name == second.name
    }
}

val myAnimalCompare: Compare<Animal> = myDogCompare

အဲဒီပုံစံမျိုး Error တက်နေပါလိမ့်မယ်။ ဆိုလိုတာက myDogCompare ဆိုတဲ့ သားက myAnimalCompare ထက် တစ်လကြီးနေလို့ပါ။

In and Out in Generics

ကျွန်တော်တို့ အပေါ်မှာ ရေးတုန်းက in ကို သုံးခဲ့ပြီးပါပြီ။

interface Compare<in T> {
    fun compare(first: T, second: T): Boolean
}

in လို့ သတ်မှတ်ပေးလိုက်မယ်ဆိုရင် Compare အနေနဲ့ T ကို arguments အနေနဲ့ လက်ခံလို့ရတယ်ဆိုတဲ့ အဓိပ္ပါယ်ပါ။  ဒါပေမယ့် return မှာတော့ T ကို ပြန်ပေးလို့မရပါဘူး။ out နဲ့ ပတ်သက်ပြီး ကျွန်တော်တို့ ဒီလို စမ်းကြည့်ရအောင်

fun main(args: Array<String>) {
    val days = listOf("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")
    val getList: List<String> = object : List<String> {
        override fun get(index: Int): String {
            return days[index]
        }
    }
    println(getList.get(1))
    println(getList.get(4))
}

interface List<out E> {
    fun get(index: Int): E
}

ဒီနေရာမှာတော့ Generics Type ကို E ဆိုပြီး သုံးထားပါတယ်။ အဓိကက List<> အတွင်းမှာ ရှိတဲ့ Generics Type ကို E အနေနဲ့ သုံးလို့ပါ။ ဒီနေရာမှာ ထူးခြားတာက List အတွင်းက Element E ကို ပြန်ပေးရမယ်လို့ out နဲ့ သတ်မှတ်ပေးလိုက်တာပါ။ အပေါ်မှာ ရေးသွားတဲ့ ပုံစံကိုတော့ ကျွန်တော် ရှေ့အခန်းမှာ ပြောခဲ့ပြီး ဖြစ်ပါတယ်။ object expression ပုံစံနဲ့ ရေးတာပါ။ Generics အကြောင်းက ဒီလောက်ဆိုရင် ကျွန်တော်တို့အတွက် လုံလောက်ပါပြီ။ ဒါပေမယ့် နောက်တစ်ချိန်မှာ ဒီထက်ပိုပြီး သိဖို့ လိုဦးမယ်ဆိုတာ သတိထားဖို့လိုပါတယ်။ အဲဒီအတွက် Kotlin in Action ဆိုတဲ့ စာအုပ်ကို ဖတ်ဖို့ ညွှန်းချင်ပါတယ်။

ဆက်ပါဦးမယ်