วันศุกร์ที่ 17 ตุลาคม พ.ศ. 2551

ปริศนา Java 2



หลังจากเขียนภาคหนึ่งไว้ตั้งแต่สองสามเดือนก่อน คราวนี้ก็มาต่อภาคสองสักทีนะครับ สำหรับภาคนี้ คำถามก็ยังเหมือนเดิมครับ คือผลลัพธ์จากโปรแกรม Java นี้คืออะไร



import java.net.*;
import java.util.*;
public class URLSet {
private static final String[] URL_NAMES = {
"http://www.google.com",
"http://www.ajsarun.blogspot.com",
"http://www.ajsarun.bloggang.com",
"http://ajsarun.blogspot.com",
"http://kisarun.multiply.com",
"http://www.google.com"
};
public static void main(String[] args) throws MalformedURLException {
Set favorites = new HashSet();
for(String urlName: URL_NAMES) {
favorites.add(new URL(urlName));
}
System.out.println(favorites.size());
}
}



  1. 4

  2. 5

  3. 6

  4. ขึ้นอยู่กับการรันแต่ละครั้ง





เราลองมาวิเคราะห์โปรแกรมนี้กันดูนะครับ จะเห็นว่าเราประกาศอะเรย์ของ String ชื่อว่า URL_NAMES โดยสมาชิกแต่ละตัวของอะเรย์ก็คือ URL ของเว็บไซต์ต่าง ๆ แต่จะเห็นว่ามี http://www.google.com ซ้ำกันอยู่ 2 ที่ จากนั้นที่เมท็อด main เราสร้างออบเจ็กของคลาส HashSet (ซึ่งจัดเป็นข้อมูลแบบเซ็ต) ใน for loop ก็มีการสร้างออบเจ็กต์ของ class URL โดยใช้สมาชิกแต่ละตัวจากอะเรย์ URI_NAMES และเก็บอ็อบเจกต์ของคลาส URL แต่ละตัวลงในเซ็ต พวกเราคงทราบนะครับว่าข้อมูลในเซ็ตจะไม่ซ้ำกัน แต่เรามีwww.google.com ซ้ำกันอยู่ 2 ตัวดังนั้น ดังนั้นข้อมูลในเซ็ตก็ควรจะมี 5 ตัว เมื่อเราสั่งพิมพ์ขนาดของเซ็ตออกมาในบรรทัดสุดท้าย ผลลัพธ์ก็ควรจะเป็น 5 ใช่ไหมครับ คำตอบก็ควรจะเป็นข้อ B

แต่ไม่ใช่ครับ คำตอบคือข้อ D. ประหลาดใจไหมครับ คือถ้าคุณต่ออินเทอร์เน็ตอยู่ผลลัพธ์จะเป็นข้อ A. คือ 4 แต่ถ้าไม่ผลลัพธ์จะเป็น 5 ทำไมจึงเป็นเช่นนั้น เรามาดูกันครับ ความลับอยู่ที่เมท็อด equals ของคลาส URL ครับ วิธีการเปรียบเทียบว่า URL จะเท่ากันหรือไม่ดูดังนี้ครับ กล่าวคือถ้าเราต่ออินเทอร์เน็ตอยู่ มันจะแปล URL ไปเป็นหมายเลข IP ถ้าหมายเลข IP เท่ากันก็ถือว่าเป็น URL ตัวเดียวกัน แต่ถ้าเราไม่ต่ออินเทอร์เน็ต มันก็จะเอาค่าสตริงของ URL มาเปรียบเทียบ โดยไม่คำนึงถึงตัวอักษรตัวเล็กหรือตัวใหญ่


มาดูกรณีต่ออินเทอร์เน็ตอยู่ จะพบว่าในสตริงมี http://www.google.com สองครั้ง ซึ่งก็ต้องได้ หมายเลข IP เดียวกันแน่นอน คราวนี้มาพิจารณาดู URL สองตัวนี้ครับ http://www.ajsarun.blogspot.com กับ http://ajsarun.blogspot.com สองตัวนี้จะให้ผลลัพธ์เป็นหมายเลข IP เดียวกัน นั่นก็หมายความว่าจะมีหมายเลข IP ที่ไม่ซ้ำกันอยู่ทั้งหมด 4 ตัวเท่านั้น ดังนั้นผลลัพธ์จึงเป็น 4


สำหรับกรณีที่ไม่ต่ออินเทอร์เน็ต มันก็จะเปรียบเทียบค่าสตริง URL ว่ามี http://www.google.com อยู่ 2 ตัว ซึ่งซ้ำกัน ที่เหลือไม่ซ้ำดังนั้นผลลัพธ์จึงเป็น 5

เป็นยังไงครับรู้สึกอยากด่าหรืออยากเลิกใช้ Java ไปเลยไหมครับ คราวนี้ทำไมจึงเป็นเช่นนี้ พิธีกรเขาให้เหตุผลว่าในสมัยก่อนที่มีการสร้างคลาสนี้ยังไม่มีแนวคิดของการที่มีชื่อ URL ที่ต่างกัน แต่อ้างถึงหมายเลข IP เดียวกัน


วิธีการแก้ไขถ้าต้องการให้ได้ผลลัพธ์ที่เหมือนเดิมเสมอไม่ว่าจะต่ออินเทอร์เน็ตหรือไม่ ให้ใช้คลาสที่ชื่อ URI แทนครับ ดังนั้นโปรแกรมในตอนต้นจะต้องเปลี่ยนเป็นดังนี้ครับ




import java.net.*;
import java.util.*;
public class URISet {
private static final String[] URI_NAMES = {
"http://www.google.com",
"http://www.ajsarun.blogspot.com",
"http://www.ajsarun.bloggang.com",
"http://ajsarun.blogspot.com",
"http://kisarun.multiply.com",
"http://www.google.com"
};
public static void main(String[] args) {
Set favorites = new HashSet();
for(String uriName: URI_NAMES) {
favorites.add(URI.create(uriName));
}
System.out.println(favorites.size());
}
}





ซึ่งคลาส URI นี้จะพิจารณาโดยใช้สตริงเท่านั้น คือไม่มีการไปแปลงเป็นหมายเลข IP ใด ๆ ดังนั้นผลลัพธ์จะเป็น 5 เสมอ แต่ก็ยังเป็นการเปรียบเทียบแบบไม่สนใจตัวอักษรตัวเล็กหรือตัวใหญ่เช่นเดิมนะครับคือ http://www.google.com กับ http://www.GOOGLE.COM ก็ถือว่าเท่ากัน

สิ่งที่พิธีกรเขาสรุปจากปริศนานี้ก็คือ เขาบอกว่าเราไม่ควรใช้ URL กับ class SET หรือ class MAP เพราะว่ามันออกแบบมาไม่ดี ให้ใช้ URI แทน ซึ่งตรงนี้ผมว่าถ้าใครไม่ได้เขียนโปรแกรมที่เกี่ยวข้องกับเรื่องพวกนี้ก็อาจดูจะไกลตัวไปนะครับ

แต่สิ่งที่เขาเน้นและผมก็คิดว่าสำคัญก็คือ เวลาเราเขียนโปรแกรม Java เมท็อดตัวหนึ่งที่เรามักจะ override ก็คือเมท็อด equals ก็ขอให้คิดไว้เสมอนะครับว่า เมท็อด equals นี้ไม่ควรจะให้ผลลัพธ์ที่เปลี่ยนไปตามสภาพแวดล้อมของการทำงาน


ผู้เขียนยินดีที่จะให้นำบทความไปเผยแพร่ต่อไปได้ถ้าไม่ได้นำไปใช้เพื่อการค้า แต่ขอให้บอกที่มาว่ามาจากผู้เขียนด้วย