Java Package Tutorial

(English version here)
  โดย  ประดับเก่ง

 
  เกริ่นนำ
สำหรับคนที่ใช้จาว่าเขียนโปรเจคที่มีไฟล์มากกว่า 10 ไฟล์ขึ้นไป สิ่งที่ขาดไม่ได้อย่างหนึ่งคือการแบ่งไฟล์ไว้ใน subdirectory ต่าง ๆ โดยแบ่งตามหมวดหมู่และความสามารถที่อยู่ในแต่ละไฟล์นั้น ตัวอย่างที่เห็นได้ง่ายคือไฟล์ทั้งหลายที่อยู่ใน java package (java.xxx.yyy....)

รูปที่ 1 โครงสร้างโดยทั่วไปของ java package

ไฟล์ที่อยู่ในแต่ละ subdirectory จะมีหน้าที่แตกต่างกัน ยกตัวอย่างไฟล์ใน java.io.*; จะทำหน้าที่เกี่ยวกับ Input/Output ไฟล์ที่อยู่ใน java.net.*; ก็จะทำหน้าที่เกี่ยวกับ network เป็นต้น ถ้าใครเคยเข้าไปดู directory ใน java application ที่มี graphical user interface (GUI) รวมอยู่ด้วย ก็มักจะเห็น subdirectory ที่มีชื่อว่า ui (user interface) ซึ่งเป็นที่ ๆ เก็บโค๊ดเกี่ยวกับ GUI ทั้งหมด กับ engine ซึ่งเป็นส่วนที่เก็บโค๊ดที่เป็นตัวทำงานจริง ๆ เมื่อ user กดที่ปุ่มต่าง ๆ
ประโยชน์ที่เห็นได้ชัดในการใช้ package คือ การง่ายในการจัดเก็บ ค้นหา รวมไปถึงการแบ่งแยกไฟล์ในแต่ละโปรเจคให้แยกจากกันโดยสิ้นเชิง ความเข้าใจเกี่ยวกับ package จะช่วยให้สามารถใช้ไฟล์ที่เก็บอยู่ใน .jar (java archive) ไฟล์ ซึ่งนักพัฒนานำมาจากที่ต่าง ๆ ได้อย่างเหมาะสมมากขึ้น

การเขียน package
สมมุติว่ามีไฟล์หนึ่งชื่อ HelloWorld ซึ่งเราอยากให้อยู่ใน package ทีชื่อว่า world เราจะต้องใส่คำว่า package world ไว้บนสุดของไฟล์ HelloWorld

//================HelloWorld.java====================
package world;

 public class HelloWorld {
  public static void main(String[] args) {
     System.out.println("Hello World");
  }
}
//===================================================

สิ่งหนึ่งที่จะลืมไม่ได้หลังจากจัดให้ไฟล์ HelloWorld อยู่ใน package ที่ชื่อ world แล้ว คือการนำไฟล์นั้นไปใส่ใน directory ที่มีชื่อว่า world ง่ายที่สุดคือการใส่ไว้ที่ root directory ดังนั้นโครงสร้างของ package ก็จะเป็นดังรูป
-C:---world--HelloWorld.java

รูปที่ 2 class HelloWorld ซึ่งอยู่ใน "world" package

ถ้าจะให้ javac (java compiler) หรือ java (Java virtual machine) เห็น package นี้ เราจะต้องทำอีกอย่างคือการเซ็ต classpath
  set classpath = .;C:\;
ในตัวอย่างนี้ จาว่าจะหาไฟล์ HelloWorld ที่อยู่ใน package "world" = world.HelloWorld จาก directory ปัจจุบัน กับ C:\ drive
     . หมายถึง directory ปัจจุบัน
    C:\ คือ directory ที่เราเก็บ package ไว้ อาจมี ; กั้น ถ้ามีการเซ็ต classpath ให้หาหลาย ๆ ที่

ถ้าไม่อยากใส่ไว้ที่ root แต่อยากใส่ไว้ที่ directory ที่ชื่อ myclasses โครงสร้างของ package ก็จะเป็นดังรูป
-C:---myclasses--world--HelloWorld.java

รูปที่ 3 class HelloWorld ซึ่งอยู่ใน "world" package แต่เก็บไว้ใน myclasses directory

การเซ็ต classpath ก็จะเปลี่ยนเป็น
  set classpath = .;C:\myclasses;
จาว่าก็จะหาไฟล์ world.HelloWorld จาก directory ปัจจุบัน กับ C:\myclasses directory แทน

ถ้าจะ compile ก็ให้ไปที่ directory C:\myclasses\world\ แล้วพิมพ์ javac HelloWorld.java
ถ้าจะ run ก็จะต้องใส่ชื่อแบบเต็มรูปแบบคือ java world.HelloWorld
บางทีใน package อาจมีการเก็บไฟล์ไว้ในหลาย level สมมุติว่าเราอยากสร้างไฟล์ชื่อ HelloMoon ไว้ใน subpackage ชื่อ moon ซึ่งอยู่ข้างใน package "world" อีกที ไฟล์ HelloMoon ก็จะเป็นดังนี้ 

//=================HelloMoon.java====================
package world.moon;

public class HelloMoon {
  protected String holeName = "rabbit hole";

  public String getHoleName() {
    return hole;
  }

  public void setHole(String holeName) {
    this.holeName = holeName;
  }
}
//===================================================

ถ้าเราเริ่มจาก root directory ไฟล์ HelloMoon ก็จะเป็น c:\world\moon\HelloMoon.java

รูปที่ 4 class HelloMoon ซึ่งอยู่ใน "world.coom" package 

การเซ็ต classpath ก็เหมือนเดิมคือ
  set classpath = .;C:\;
ที่เป็นดังนี้เพราะไฟล์ HelloMoon อยู่ใน package ชื่อ world.moon ถ้าเราย้าย classpath จาก .;C:\; เป็น .;C:\world; จาว่าจะเห็นว่าไฟล์ HelloMoon อยู่ใน package ชื่อ  moon แทน

การใช้ package
การเรียกใช้ class ที่เป็น public จาก package ต่าง ๆ (ถ้าดูจากตัวอย่างข้างต้น ก็จะเป็นไฟล์ HelloWorld กับ HelloMoon) อาจทำได้สองวิธีคือ

1) เรียกชื่อเต็มโดยตรง ยกตัวอย่างเช่น 
  world.HelloWorld helloWorld = new world.HelloWorld();
  world.moon.HelloMoon helloMoon = new world.moon.HelloMoon();
  String holeName = helloMoon.getHoleName();
  ...
2) โดยการใช้ import 
  import world.*; // สามารถเรียกทุก class ใน world package ที่เป็น public มาใช้ได้ (แต่เรามีไฟล์เดียว คือ HelloWorld.java)
  import world.moon.*; // สามารถนำทุกไฟล์ใน world.moon package ที่เป็น public มาใช้ได้ (มีไฟล์เดียว คือ HelloMoon)
ข้อสังเกตุที่เห็นคือ ถ้าเรา import แค่ package "world" เราจะไม่สามารถเรียกไฟล์ที่อยู่ใน subpackage moon มาใช้ได้
ถ้าเรา import แค่ package "world.moon" เราก็ไม่สามารถเรียก class HelloWorld มาใช้ได้เช่นกัน บางทีเราอาจจะชี้เฉพาะ class ที่เราอยากเรียกใช้โดยไม่ครอบคลุมทั้ง package.  import ก็อาจจะเป็น
  import java.net.URL; // class ที่เราสามารถใช้ได้จะมีเพียง class URL
ข้อดีของ java คือ ไม่ว่าเราจะ import ทั้ง package หรือเฉพาะไฟล์ เวลาในการ compile หรือ ขนาดของโปรแกรมก็จะไม่เปลี่ยนแปลง

การเรียกใช้ class ใน .jar ไฟล์
jar ไฟล์คือไฟล์ที่รวบรวมไฟล์หลาย ๆ ไฟล์เข้าด้วยกัน โดยโครงสร้างอาจจะประกอบด้วยหลาย ๆ  directory รวมไปถึง subdirectory ซึ่งจริง ๆ แล้วก็คือ package นั่นเอง
วิธีดูว่าใน .jar ไฟล์ มีอะไรอยู่บ้าง ให้ใช้ jar -tvf xxx.jar ยกตัวอย่างเช่น

รูปที่ 5 โครงสร้างของ jsdk package เมื่อใช้ jar -tvf jsdk.jar

จากรูป หลังจากที่เราดูโครงสร้างของไฟล์ jsdk.jar แล้ว สมมุติว่าเราจะเรียกใช้ class ที่ชื่อ Cookie (บรรทัดที่ 4) เราสามารถเรียกใช้ได้โดย
  import javax.servlet.http.Cookie; หรือ
  import javax.servlet.http.*;    // import ทั้ง package
ถ้า compile จาว่าอาจจะมองไม่เห็น package นี้ ดังนั้นเราต้อง
  set classpath = .;D:\JSDK2.0\lib\jsdk.jar;

เราจะสังเกตุได้ว่า ถ้า package ถูกแพ๊คอยู่ในรูปของ .jar ไฟล์ เวลา set classpath เราจะต้องใส่ชื่อ .jar ไฟล์นั้นลงไป แต่ถ้า package นั้นถูก unpack จาก .jar ไฟล์ หรือ อยู่ใน directory เฉย ๆ เวลาเรา set classpath เราจะใส่แค่ชื่อ directory ที่เก็บ package นั้น