ข้าม​ไป​ยัง​เนื้อหา

Java ตัดสิน​ใจ​ตรง​ข้าม​กับ Go เกือบ​ทุก​ข้อ และ​เครื่อง​มือ​ชุด​แรก

คอร์ส​นี้​ไม่​ได้​เริ่ม​ที่​ว่า​ตัวแปร​คือ​อะไร

มัน​เริ่ม​ที่​คำถาม​ที่​มี​ประโยชน์​กว่า — Java ตัดสิน​ใจ​ต่าง​จาก​ภาษา​ที่​คุณ​ใช้​อยู่​ตรง​ไหน และ​มัน​จ่าย​อะไร​ไป​กับ​การ​ตัดสิน​ใจ​นั้น

📦 orderctl — ของ​ชิ้น​เดียวกัน​กับ​คอร์ส Go

คอร์ส​นี้​สร้าง orderctl เครื่อง​มือ​บรรทัด​คำ​สั่ง​ที่​ประมวล​ผล​ออ​เด​อร์​อาหาร ตัว​เดิม​กับ​ที่ คอร์ส Go จาก​ศูนย์ สร้าง​ไว้

การ​เขียน​ของ​ชิ้น​เดียวกัน​สอง​ภาษา ทำให้​คำถาม “ภาษา​ไหน​ดี​กว่า” กลาย​เป็น​คำถาม​ที่​ตอบ​ได้ ด้วย​การ​ชี้​ไป​ที่ code สอง​ชุด​ที่​ทำงาน​เดียวกัน แทนที่​จะ​เถียง​กัน​ด้วย​ความ​รู้สึก

บท​นี้​เป็น​บท​แนวคิด​กับ​การ​ติด​ตั้ง code ที่​รัน​ได้​จริง​มี​แค่ hello world · ของ​จริง​เริ่ม​บท​ที่ 2

คอร์ส​นี้​จะ​กลับ​มา​ที่​ทุก​แถว​ที​ละ​บท และ​ทุก​แถว​มี​ราคา​ทั้ง​สอง​ฝั่ง

เรื่องGo เลือกJava เลือกบท​ที่​กลับ​มา
ความ​ผิด​พลาดเป็น​ค่าที่​ฟังก์ชัน​คืน​กลับ​มาเป็น exception ที่​กระโดด​ขึ้น​ไป​เอง6
การ​ใช้​ซ้ำประกอบ​อย่าง​เดียว ไม่มี​การ​สืบทอดมี​การ​สืบทอด​และ abstract class5
การ​เข้า​กับ interfaceดู​จาก​รูป​ร่าง ไม่​ต้อง​ประกาศต้อง​เขียน implements ออก​มา5
การ​จัด​รูป​แบบgofmt ที่​ไม่มี​ตัว​เลือกไม่มี​ตัว​ไหน​ใน​กล่อง ต้อง​เลือก​เองบท​นี้
ผล​ของ​การ buildโปรแกรม​เดียว​ที่​รัน​บน​เครื่อง​เปล่า​ได้ต้อง​มี JVM รอ​อยู่​ปลาย​ทาง3

สี่​แถว​แรก​ไม่มี​ฝ่าย​ชนะ · แถว​ที่​ห้า​มี​ฝ่าย​ชนะ​ชัด​ใน​บาง​บริบท และ​คอร์ส​นี้​จะ​ไม่​แกล้ง​ทำ​เป็น​ไม่​เห็น

Java ออก​รุ่น​ใหม่​ทุก​หก​เดือน แต่​มี​แค่​บาง​รุ่น​ที่​ได้​รับ​การ​ดูแล​ยาว

รุ่น​ที่​ได้​รับ​การ​ดูแล​ยาว​เรียก​ว่า LTS · ตั้งแต่ Java 17 เป็นต้น​มา LTS ออก​ทุก​สอง​ปี จึง​เป็น 17, 21, 25 แล้ว​จะ​เป็น 29

คอร์ส​นี้​ใช้ Java 25 และ​ทุก​ตัวอย่าง​รัน​ได้​โดยไม่​ต้อง​เปิด​โหมด​ทดลอง

ของ​ที่​คอร์ส​ใช้ต้อง​มี​อย่าง​น้อย
record · sealed · text block17
pattern matching ใน switch21
void main() · IO.println25

สาม​อย่าง​ใน​แถว​สุดท้าย​คือ​เหตุผล​ที่​คอร์ส​นี้​ไม่​รองรับ 21 · มัน​ทำให้​บท​ที่ 1 กับ 2 เขียน​ได้​โดย​ไม่​ต้อง​มี build tool เลย ซึ่ง​เป็น​เรื่อง​ที่​เปลี่ยน​รูปการ​สอน​ทั้ง​คอร์ส

เลือก​ทาง​ไหน​ก็ได้ แต่​ให้​ดู​แค่​ว่า​ตัวเลข​ขึ้น​ต้น​ด้วย 25

$ java --version
openjdk 25.0.4.1 2026-08-18

ตัวเลข​หลัง​จุด​ต่าง​กัน​ตาม​ที่มา​ที่​คุณ​ติด​ตั้ง · คอร์ส​นี้​จึง​ผูก​กับ 25 เท่านั้น ไม่​ผูก​กับ string เต็ม

`--version` กับ `-version` ไป​คนละ​สาย

java --version เขียน​ออก stdout ส่วน java -version เขียน​ออก stderr

ข้อ​นี้​กัด​ตอน​คุณ​เขียน​สคริปต์​แล้ว​ดัก​ผล​ด้วย $(...) หรือ​ส่ง​ต่อ​ด้วย pipe · -version จะ​หาย​ไป​เงียบๆ เพราะ​สอง​ท่า​นั้น​รับ​เฉพาะ stdout

ผู้​เขียน​บท​นี้​ทดสอบ​ข้อ​นี้​ผิด​ใน​รอบ​แรก​ด้วย · ใช้ 2>&1 >/dev/null ซึ่ง​เรียง​ลำดับ ผิด​ทาง ทำให้ stdout ยัง​ไหล​เข้า pipe อยู่ · ลำดับ​ที่​ถูก​คือ 2>&1 1>/dev/null

java ไม่​ได้​มา​ตัว​เดียว มัน​มา​เป็น​ชุด และ​แต่ละ​ตัว​ทำ​คนละ​อย่าง

  • java รัน​โปรแกรม และ​ตั้งแต่ 11 มัน​รับ source โดยตรง​ได้​ด้วย
  • javac แปล source เป็น .class
  • jar ห่อ .class หลาย​ตัว​เป็น​แฟ้ม​เดียว
  • jshell ลอง​พิมพ์​ที​ละ​บรรทัด​แล้ว​ดู​ผล​ทันที
  • jlink ประกอบ runtime ที่​มี​เฉพาะ​ส่วน​ที่​โปรแกรม​ใช้
ไม่มี​ตัว​ไหน​ใน​ชุด​นี้​ดึง dependency ให้

ถ้า​คุณ​มา​จาก C# คุณ​จะ​มอง​หา CLI กลาง​แบบ dotnet ที่​คุม​ทั้ง build, test, run และ​การ ดึง package

ไม่มี · ทั้ง​ห้า​คำ​สั่ง​ข้าง​บน​ไม่มี​ตัว​ไหน​รู้จัก dependency เลย​สัก​ตัว · ตัว​ที่​ทำ​หน้าที่​นั้น​คือ Maven ซึ่ง​อยู่​นอก JDK และ​เรา​จะ​เจอ​มัน​ใน​บท​ที่ 3

ถ้า​คุณ​มา​จาก Python จุด​ที่​ต่าง​คือ​ไม่มี virtual environment ให้​ทำ · dependency ของ​ทุก​โปร​เจ​กต์​ใน​เครื่อง​กอง​รวม​กัน​ที่ ~/.m2 แล้ว​แยก​กัน​ด้วย​เลข​รุ่น

สาม​อย่าง​ที่ Java เคย​บังคับ​ให้​เขียน​รอบ hello world หาย​ไป​พร้อม​กัน​ทั้ง​สาม​อย่าง

สร้าง Hello.java แล้ว​ใส่​แค่​นี้

void main() {
IO.println("orderctl พร้อมทำงาน");
}
$ java Hello.java
orderctl พร้อมทำงาน

สังเกต​ว่า​ไม่มี class Hello ครอบ ไม่มี public static void main(String[] args) และ​ไม่มี System.out.println

นี่​คือ compact source filecompact source fileรูป​ย่อ​ของ source ที่​ประกาศ class ครอบ​ไม่​ต้อง (JEP 512 final ใน Java 25) · ประกาศ `package` ไม่​ได้ และ​สมาชิก​ทั้งหมด​กลาย​เป็น​สมาชิก​ของ class โดย​ปริยาย ซึ่ง​เป็น​รูป​ที่​เพิ่ง​เสร็จ​สมบูรณ์​ที่ Java 25 · สมาชิก​ทุก​ตัว​ที่​คุณ​เขียน กลาย​เป็น​สมาชิก​ของ class ที่ compiler ตั้ง​ชื่อ​ให้​เอง

void main() ที่​ไม่ใช่ static และ​ไม่​รับ argument คือ instance main methodinstance main method`void main()` ที่​ไม่ใช่ static และ​ไม่​รับ argument · launcher ของ Java 25 เรียก constructor ไร้ argument ก่อน​แล้ว​จึง​เรียก main ตัว​นี้ · ตัว​รัน​ของ Java 25 สร้าง object ขึ้น​มา​ก่อน​ด้วย constructor ที่​ไม่​รับ​อะไร แล้ว​จึง​เรียก​มัน

สาม​อย่าง​นี้​มี​ประวัติ​คนละ​เส้น อย่า​จำ​รวม​กัน

void main() เป็น​ของ​ทดลอง​มา​ตั้งแต่ Java 21 แล้ว​ค่อยๆ เปลี่ยน​รูป​จน​เสร็จ​ที่ 25

ส่วน class IO ไม่มี​เลย​ใน 21 และ 22 · มัน​มา​ครั้ง​แรก​ที่ 23 ใน​ชื่อ java.io.IO โดย​ตอน​นั้น​เขียน println(...) เปล่าๆ ได้​เลย · พอ​ถึง 25 มัน​ย้าย​ไป java.lang.IO และ​เลิก​นำ​เข้า​ให้​อัตโนมัติ จึง​ต้อง​เขียน IO.println(...) เต็มๆ อย่าง​ที่​เห็น

ผล​คือ​ตัวอย่าง​จาก 23 หรือ 24 ที่​คุณ​ค้น​เจอ จะ​คอม​ไพล์​ไม่​ผ่าน​บน 25 · นี่​ไม่ใช่​ความ​ผิด​ของ​คุณ มัน​คือ​ฟีเจอร์​ที่​เพิ่ง​นิ่ง

java Hello.java ไม่ใช่​การ​ตีความ มัน​แปล​จริง แค่​ไม่​เก็บ​ผล​ไว้

file เดียวกัน​นั้น​เอา​ไป​แปล​ด้วย javac ก็ได้

$ javac -d out Hello.java
$ ls out
Hello.class
$ java -cp out Hello
orderctl พร้อมทำงาน

compact source file จึง​เป็น​ฟีเจอร์​ของภาษา ไม่ใช่​โหมด​พิเศษ​ของ​คำ​สั่ง java

สิ่ง​ที่​คำ​สั่ง java เพิ่ม​ให้​คือ source launchersource launcherโหมด​ที่​สั่ง `java Main.java` แล้ว​แปล​ใน​หน่วย​ความ​จำ · class ที่มา​จาก class path มอง​ไม่​เห็น class ที่​แปล​แบบ​นี้ จึง​รัน JUnit ร่วม​กับ​มัน​ไม่​ได้ — มัน​แปล​ไว้​ใน​หน่วย​ความ​จำ​แล้ว​รัน​ทันที โดย​ไม่​ทิ้ง .class ไว้

flowchart LR
  A["Hello.java"] -->|"java Hello.java"| B["แปลในหน่วยความจำ<br/>แล้วรันทันที"]
  A -->|"javac -d out"| C["out/Hello.class"]
  C -->|"java -cp out Hello"| D["รันจากที่แปลไว้แล้ว"]
  B -.->|"ไม่เหลืออะไรไว้"| E["ต้องแปลใหม่ทุกครั้ง"]

คำ​บรรยาย​ภาพ: สอง​เส้น​ทาง​จาก source เดียวกัน เส้น​บน​แปล​ใหม่​ทุก​ครั้ง​แล้ว​ไม่​เก็บ​ผล เส้น​ล่าง​แปล​ครั้ง​เดียว​แล้ว​รัน​ซ้ำ​ได้

ราคา​ของ​ความ​สะดวก​คือ​การ​แปล​ใหม่​ทุก​ครั้ง วัด​บน​เครื่อง​ที่​เขียน​บท​นี้ (OpenJDK 25.0.4.1 · macOS arm64) เจ็ด​รอบ​ต่อ​แบบ วัด​ซ้ำ​สี่​ชุด เส้น​บน​ช้า​กว่า​เส้น​ล่าง​ราว​สิบ​เท่า

ตัวเลข​วินาที​จริง​ไม่​ถูก​ยก​มา​ที่​นี่ เพราะ​มัน​ไม่​ข้าม​เครื่อง · สิ่ง​ที่​ข้าม​ได้​คือ​ทิศทาง และ​ทิศทาง​นี้​คือ​เหตุผล​ที่​บท​ที่ 3 ต้อง​เลิก​ใช้ source launcher

สี่​อย่าง​ที่​ยัง​ไม่มี และ​แต่ละ​อย่าง​มา​ถึง​เมื่อ​มี​เหตุผล​ให้​ต้อง​มี

  • ไม่มี package จึง​ยัง​ไม่มี​โครง​ไดเรกทอรี​ให้​จำ
  • ไม่มี build tool จึง​ยัง​ไม่มี XML ให้​อ่าน
  • ไม่มี​เทสต์ เพราะ source launcher รัน JUnit ร่วม​ด้วย​ไม่​ได้
  • ไม่มี dependency ซึ่ง​จะ​กลาย​เป็น​ปัญหา​ทันที​ใน​บท​ที่ 3 เพราะ Java ไม่มี JSON มา​ให้

ลอง​สั่ง java --list-modules ดู​ก่อน​ไป​ต่อ · standard library ของ Java ถูก​แบ่ง​เป็น​ก้อน และ​บน​เครื่อง​ที่​เขียน​บท​นี้​มี 69 ก้อน · ไม่มี​ก้อน​ไหน​ชื่อ​เกี่ยว​กับ JSON เลย​สัก​ก้อน ซึ่ง​เป็น​เรื่อง​ที่​บท​ที่ 3 ต้อง​จัดการ

บท​นี้​วาง​กรอบ​และ​ติด​ตั้ง ยัง​ไม่มี feature ของ orderctl เกิด​ขึ้น​จริง

คุณ​รู้​แล้ว​ว่า Java เลือก​ทาง​ตรง​ข้าม​กับ Go ตรง​ไหน​บ้าง เลือก​รุ่น​เป็น ติด​ตั้ง​แล้ว และ​รัน​โปรแกรม​แรก​ได้​โดย​ยัง​ไม่​ต้อง​รู้จัก build tool เลย

บท​หน้า​เรา​ลง​ของ​จริง — record และ sealed interface · Java ไม่มี zero value ให้​ตัวแปร​เฉพาะ​ที่ ซึ่ง​เป็น​เรื่อง​ที่​คน​มา​จาก Go ต้อง​ปรับ​ความ​คิดมาก​ที่สุด แต่​มัน​แลก​มา​ด้วย​กลไก​ที่​ทำให้​สถานะ​ที่​ผิด​สร้าง​ไม่​ขึ้น​ตั้งแต่​แรก


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้

บท​นี้​อิง​ต้นทาง​ที่​ลง​วัน​ที่​กำกับ อ่าน​ต่อ​ได้​โดยตรง:

เช็กความเข้าใจ — บทที่ 1

ข้อ 1 / 3

`java Hello.java` ทำงานอย่างไร