<T> และ <E> หรือ Generic ใน Java คืออะไร และใช้ยังไงกัน

ไม่ได้เขียนบล๊อกเกี่ยวกับ Programming มาพักใหญ่ๆ และ วันนี้ขอจัดหน่อยละกัน
Java และภาษาตระกูล C ส่วนใหญ่จะมีลักษณะการประกาศตัวแปรแบบ Type-sensitive หรือต้องฟิกไปเลยตั้งแต่สร้างตัวแปรว่ามันจะเก็บข้อมูลแบบไหน เช่น

int x, y;
double d;
String str;

แต่ถ้าเป็นพวกภาษา Dynamic-type การสร้างตัวแปรพวกนี้จะง่ายกว่า เช่น var x ใน JavaScript หรือ $x ใน PHP ซึ่งสร้างอะไรขึ้นมาก็เก็บได้ตั้งแต่ตัวเลขยัน object

ซึ่งบางครั้งเราใช้ก็อาจจะเกิดเหตุขัดใจนิดหน่อยกับภาษาพวก Type-sensitive เพราะ

บางครั้ง เราก็ไม่รู้ Type ของตัวแปรนะ

มาถึงตรงนี้อาจจะมีคนสงสัย ว่าเราเขียนโปรแกรมเอง แล้วเราจะไม่รู้ Type ของโค้ดเราได้ยังไง?

เหตุการณ์นี้มักจะเกิดขึ้นเมื่อเราเขียนโค้ดพวก Library เช่น ถ้าเราจะสร้าง LinkedList ซึ่งมี Node เป็นตัวเก็บข้อมูล (ใครยังไม่รู้จัก LinkedList ลองไปอ่านเรื่อง Data Structure ดูนะ)

เราก็สร้าง Node ขึ้นมาแบบนี้ละกัน

class Node{
	int data;
	Node next
}

สมมุติว่าเราอยากได้ LinkedList ที่เก็บ integer เราก็เลยสร้าง Node ที่เก็บ data เป็น int ... ก็ได้นี่นา แล้วปัญหาคืออะไรงั้นเหรอ

คำถาม: แล้วถ้าเราต้องการ LinkedList ที่เก็บ String ได้ล่ะ

อ้อ ก็สร้าง Node ตัวใหม่มาไงล่ะ

class NodeString{
	String data;
	Node next;
}

แบบนี้ไง

อืม... ลองมาคิดกันเล่นๆ นะ ถ้าทำแบบนี้ แล้วในอนาคตต้องการ LinkedList ที่เก็บข้อมูลชนิดอื่นได้จะทำไงล่ะ จะสร้างคลาส NodeXXX เพิ่มขึ้นมาแบบนี้ไปทุกครั้งเลยเหรอ ไม่ดีมั้ง?

ทางแก้ก็คือเอา Generic มาช่วยล่ะ

Generic / มองtypeเป็นตัวแปรซะ

มาดูวิธีแก้ปัญหาการเขียนโค้ดเมื่อกี้แบบใหม่นะ

ในเมื่อเอาจริงๆ แล้ว คลาส Node ของเรามันก็ไม่จำเป็นต้องมีหลายตัวนี่นา แต่แค่ตรง data เราไม่รู้จะใช่typeอะไรเข้าไปเท่านั้นเอง

ถ้าไม่รู้ type ก็ใช้ Object แทนก็ได้นี่?

class Node{
	Object data;
	Node next
}

ในเมื่อคลาส Object เป็นพ่อทุกสถาบันของคลาสในโลก Java (ทุกคลาส extends มาจากมันหมด) ดังนั้นมันจะเก็บอะไรก็ได้

แต่การใช้ Object ก็มีปัญหาเล็กน้อยนั่นคือเวลาเอาข้อมูลออกมาใช้ จำเป็นต้อง Casting ชนิดข้อมูลก่อน ซึ่งมันมีโอกาสพังสูง

Node n1 = new Node();
Node n2 = new Node();

n1.data = 1;
n2.data = "stringยังไงล่ะ";

int x = (int) n1.data;
int y = (int) n2.data;

เมื่อ Object เก็บได้ทุกอย่าง เวลาใส่เราก็สบายใจได้ว่าจะtypeอะไรก็โยนให้มันได้

แต่ตอนเรียกใช้เท่านั้นแหละ เราต้องทำการ Cast มันกลับเป็น type เดิมของมันซึ่งตรงนี้ไม่ว่าคุณจะแคสแบบไหนมันคอมไพล์ผ่านหมดเลยนะ!! ความซวยจึงบังเกิดถ้าคุณดันแคสมันผิดtype เช่นในตัวอย่าง n2.data นั้นเก็บค่า String อยู่ พอสั่งมันแคสเป็น int ก็เตรียมตัวเจอ ClassCastException เด้งขึ้นมาได้เลยนะ

ในเมื่อมันไม่เวิร์ค งั้นเอาใหม่!

class Node{
	X data;
	Node next;
}

ในเมื่อเราไม่รู้ว่าตกลงแล้ว data จะเป็นtypeอะไร ก็ใช้เป็นตัวแปรแทนละกัน (ฮา)

โอเค ยังไม่ต้องแย้งว่า Java จะมอง X เป็นชื่อคลาสซึ่งมันยังไม่มีและจะขึ้นตัวแดง เราแค่บอกเพิ่มว่า X ตัวนี้น่ะมันเป็นtypeนะ แบบนี้

class Node<X>{
	X data;
	Node next;
}

เขียนแบบนี้แปลว่า "เวลาเรา new Node ขึ้นมาน่ะ อยากให้คนใช้ส่งคลาสเข้ามาให้ด้วย" ใครเคยใช้คลาสพวก ArrayList<คลาส> น่าจะนึกภาพออก

//จากเดิม
Node n = new Node();

//กลายเป็น
Node<String> n1 = new Node<String>();
Node<Integer> n2 = new Node<Integer>();
Node<MyOwnClass> n3 = new Node<MyOwnClass>();

ในกรณีนี้ ทั้ง n1, n2, n3 จะสร้างขึ้นมาจากคลาส Node ตัวเดียวกันเลย แต่ .data ของพวกมันจะต่างกันแล้ว เพราะคลาสที่เราส่งเข้าไปตอนสร้างเป็นคนละตัวกัน

แล้ว <E> หรือ <T> มันคืออะไร

ไม่มีอะไรหรอก คุณสามารถใช้ตัวแปรอะไรแทนtypeของคุณก็ได้ จะใช้ X แบบตัวอย่างเมื่อกี้ก็ได้ แค่ส่วนใหญ่ในโลกโปรแกรมมิ่งเขามักจะมีเทรนด์การตั้งชื่ออยู่ แบบเวลาเราวนลูป ก็มักจะใช้ i=0 แบบนั้นใช่มั้ยล่ะ

โดยหลักๆ เขาจะใช้พวกนี้กัน

  • E - Element (ตัวมาตราฐานที่ใช้ใน Java Collections Framework)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - ชนิดข้อมูลตัวที่ 2, 3, 4, ...

รับมากกว่า 1 Type ก็ได้

หากโค้ดของเราต้องการ Generic Type มากกว่า1ตัวก็แค่ใส่ , คั่นไปแบบนี้

class Node<E, T, S>{
	E data1;
	T data2;
	S data3;
}

แต่คำเตือนคืออย่าใช้เยอะโดนไม่จำเป็น บางเคสเลี่ยงไปใช้ interface หรือ abstract class แทนก็ได้นะ มันอ่านเข้าใจง่ายกว่า

ปะ Generic ที่ method ก็ได้นะ

ถ้าการปะ Generic ที่ตัวคลาสยังไม่ตอบโจทย์พอ (เพราะมีบางเคสที่เราต้อง new Object ก่อน แล้วมารู้typeตอนกำลังจะเรียก method) ก็ไปปะที่ method แทนได้นะ

class Node<E>{
	E data;
	<T> void method1(T t){
		//TODO
	}
	<T> T method2(T t){
		//TODO
		return t;
	}
}

//แล้วก็เรียกใช้
Node n = new Node();
n.method1("อะไรก็ได้เลย");

int x = n.method2(123);
String str = n.method2("ใส่สตริงลงไปก็จะรีเทิร์นสตริงไงล่ะ");

โดยให้วาง Generic ไว้หน้า return_type แต่แต่หลัง access modifier และ static (แบบนี้ public static <T> T method(T t) นะ) วิธีใช้อาจจะเข้าใจยากกว่าการปะ Generic ที่ตัวคลาสไปสักหน่อย คือครั้งนี้เวลาเราเรียกใช้เราไม่ต้องส่งชื่อคลาสไปแล้ว แต่ตัว Generic จะดูtypeจากค่าที่เราส่งเข้าไปเอง เช่น

  • n.method2(123) - ก็จะมองว่า T เป็น int
  • n.method2("ใส่สตริงลงไปก็จะรีเทิร์นสตริงไงล่ะ") - ก็จะมองว่า T เป็นสตริงนะ

เรื่องสุดท้าย ขอปิดด้วย

การ filter ให้ใส่ type เฉพาะที่เราต้องการด้วยคำสั่ง "extends"

ปัญหาสุดท้ายของการใช้ Generic คือบางครั้งเราไม่ได้ต้องการให้คนที่เรียกใช้ใส่คลาสมั่วๆ อะไรมาก็ได้ เพราะมันจะเกิดเคสแบบนี้

class Node<E>{
	E data;
	Node next;
	int compateTo(Node other){
		return data ????? other.data;
	}
}

จะเขียน method: compateTo แต่ทีนี้ เนื่องจากเราไม่รู้ว่า E เป็นคลาสอะไรกันแน่ (อย่าเข้าใจผิดว่าทุกคลาสจะต้องมี compareTo ให้เรียกใช้นะ) แล้วเราจะเปรียบเทียบ data ทั้ง2ตัวยังไงล่ะ

ก็แปลว่าจริงๆ เราอยากจะให้เขาเรียกใช้โดยใส่คลาสอะไรก็ได้เหมือนเดิมนั่นแหละ ขอแค่คลาสนั้นจะต้องมี compareTo ให้เรียกใช้

Note: เมธอด int compareTo(Object other) เป็นเมธอดบังคับในอินเตอร์เฟซ Comparable ซึ่งมีมาให้ใน Java Libraryตัวหลักอยู่แล้วนะ บอกให้รู้ไว้ก่อน

class Node<E extends Comparable>{
	E data;
	Node next;
	int compateTo(Node other){
		return data.compareTo(other.data);
	}
}

แต่ถึงมันจะเป็น interface แต่พออยู่ใน Generic แล้วเราจะใช้ extends แทนนะ

โอเค เท่านี้ E ของเราก็จะเป็นคลาสอะไรก็ได้ แต่มี compateTo อย่างแน่นอน เรียกได้อย่างสบายใจและล่ะ

...

จากที่เขียนไป คงเห็นตัวอย่างการใช้ Generic ไปพอสมควรแล้วนะ จริงๆ ก็ยังมีอีกมาก เช่นการใช้ Nested-Generic หรือ Genericซ้อนGeneric เช่น <E<T>> แต่มันไม่ค่อยเจอ

สำหรับtipการใช้ Java วันนี้ก็จบแค่นี้ก่อนละกัน ^__^

4963 Total Views 3 Views Today
Ta

Ta

สิ่งมีชีวิตตัวอ้วนๆ กลมๆ เคลื่อนที่ไปไหนโดยการกลิ้ง .. ถนัดการดำรงชีวิตโดยไม่โดนแสงแดด
ปัจจุบันเป็น Senior Software Engineer อยู่ที่ Centrillion Technology
งานอดิเรกคือ เขียนโปรแกรม อ่านหนังสือ เขียนบทความ วาดรูป และ เล่นแบดมินตัน

You may also like...

1 Response

  1. 14 กันยายน 2018

    […] แต่เนื่องจาก List นั้นไม่ใช่ array แท้ๆ (มันเป็น class ที่สร้างขึ้นมาเอง) เราเลยต้องมีการบอกด้วยว่าข้อมูลในลิสต์นี้เป็นชนิดอะไรด้วยการใส่ generic ลงไป (generic คือไอ้ <…> ที่อยู่ข้างหลังนะ อ่านเรื่อง generic เพิ่มเติมได้ที่นี่) […]

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องที่ต้องการถูกทำเครื่องหมาย *