หัวข้อ 1 · 18 นาที
Package และ Inheritance
นึกภาพก่อน
สมมติมีคลาส Ball ที่วาดลูกบอลสีดำเด้งไปมาได้แล้ว วันหนึ่งอยากได้ลูกบอลที่มีสี ทางหนึ่งคือคัดลอกโค้ดทั้งคลาสมาแก้
อีกทางคือบอกว่า "ColoredBall คือ Ball ที่เพิ่มเรื่องสี" แล้วเขียนเฉพาะส่วนที่ต่าง
ทางที่สองคือ การสืบทอด (inheritance): สร้างคลาสใหม่ (คลาสลูก) โดยใช้คลาสเดิม (คลาสแม่) เป็นต้นแบบ เพื่อเพิ่มความสามารถโดยไม่ต้องเขียนของเดิมซ้ำ
Package
เมื่อโปรแกรมมีหลายไฟล์ ควรจัดคลาสที่เกี่ยวข้องกันเป็นกลุ่ม เรียกว่า package
- ชื่อเต็มของคลาส = ชื่อ package + ชื่อคลาส เช่น
java.io.File,java.io.PrintStream,jlab.graphics.DWindow - ไฟล์ที่อยู่ใน package ขึ้นต้นด้วยคำสั่ง
package - ไฟล์ที่จะใช้คลาสจาก package อื่นต้อง
import
package com.lasakul;
public class EFG {
public void print_efg() {
System.out.println("Hello World...I am EFG class");
}
}
import com.attasit.ABC;
import com.lasakul.EFG;
public class App {
public static void main(String[] args) throws Exception {
ABC.print_String("Attasit Lasakul KMITL");
EFG.print_efg();
}
}
ชื่อ package ตรงกับโครงสร้างโฟลเดอร์: com.lasakul.EFG อยู่ในไฟล์ com/lasakul/EFG.java
Inheritance
public class B extends A {
...
}
A เป็น superclass (คลาสแม่) และ B เป็น subclass (คลาสลูก)
สิ่งที่เกิดขึ้นเมื่อ B รับทอดจาก A:
- อ็อบเจกต์ของ
Bมีตัวแปรประจำอ็อบเจกต์ทุกตัวของA Bใช้ตัวแปรและเมท็อดของAที่ไม่เป็นprivateได้Bไม่ได้ ตัวสร้าง (constructor) ของAแต่เรียกใช้ได้Bเพิ่มตัวแปรใหม่และเมท็อดใหม่ได้Bเขียนเมท็อดใหม่ แทน ของที่รับทอดมาได้ เรียกว่า override
สไลด์สรุปเป็นสองกลุ่ม:
| คลาสลูกได้มา | คลาสลูกไม่ได้มา |
|---|---|
| object variable | class variable (static) |
| object method | class method (static) |
| private variable (มีอยู่ในอ็อบเจกต์ แต่เข้าถึงไม่ได้) | |
| constructor |
ตัวอย่าง: Ball และ ColoredBall
public class ColoredBall extends Ball {
private int color;
public ColoredBall(double r, double x, double y) {
this(r, x, y, DWindow.BLACK);
}
public ColoredBall(double r, double x, double y, int color) {
super(r, x, y);
this.color = color;
}
public void setColor(int c) { color = c; }
public int getColor() { return color; }
public void draw(DWindow w) {
w.fillEllipse(color, x, y, 2 * r, 2 * r);
}
}
- เพิ่มตัวแปร
colorและเมท็อดsetColor,getColor - override
drawให้วาดด้วยสี - เมท็อด
moveไม่ได้เขียนใหม่ จึงใช้ของBall
this กับ super
| เขียน | ความหมาย |
|---|---|
this.var | ตัวแปร var ที่ประกาศในคลาสเรา |
super.var | ตัวแปร var ที่ประกาศในคลาสแม่ |
this.method() | เมท็อดที่เขียนในคลาสเรา |
super.method() | เมท็อดที่เขียนในคลาสแม่ |
this(...) | เรียกตัวสร้างในคลาสเรา |
super(...) | เรียกตัวสร้างในคลาสแม่ |
ถ้าชื่อไม่ซ้ำกับของคลาสลูก จะเขียน this.var หรือ var เฉย ๆ แทน super.var ก็ได้
public class Point3D extends Point2D {
public int z;
public Point3D(int x, int y, int z) {
super(x, y);
this.z = z;
}
public String toString() {
return "x=" + super.x + ",y=" + super.y + ",z=" + this.z;
}
}
การใช้ super.method() ที่พบบ่อยคือ "ทำของเดิมด้วย แล้วเพิ่มของใหม่":
public class BallG extends Ball {
private double gravity = 2;
public void move(DWindow w) {
dy += gravity;
super.move(w);
}
}
Constructor กับการสืบทอด
กฎสามข้อ:
- คลาสที่ไม่เขียนตัวสร้างเลย compiler จะเพิ่มตัวสร้างปริยาย (ไม่มีพารามิเตอร์) ให้
- ถ้าบรรทัดแรกของตัวสร้างไม่ใช่
this(...)หรือsuper(...)compiler จะเติมsuper();ให้เอง - ผลคือตัวสร้างของคลาสแม่ทำงาน ก่อน ของคลาสลูกเสมอ
ตัวอย่างไล่ทีละขั้น
public class A {
public A() { System.out.println("I'm in A"); }
public static void main(String[] args) { new B(); }
}
class B extends A {
public B() { System.out.println("I'm in B"); }
}
new B()เรียกตัวสร้างB()- บรรทัดแรกของ
B()ไม่ใช่this(...)หรือsuper(...)จึงเหมือนมีsuper();อยู่ super()เรียกA()พิมพ์I'm in A- กลับมาทำส่วนที่เหลือของ
B()พิมพ์I'm in B
ผลลัพธ์:
I'm in A
I'm in B
ถ้าเปลี่ยนคลาสแม่ให้มีแต่ A(int x):
public class A {
public A(int x) { System.out.println("I'm in A(" + x + ")"); }
}
class B extends A {
public B() { System.out.println("I'm in B"); }
}
จะ แปลไม่ผ่าน เพราะ super(); ที่ถูกเติมให้ต้องการตัวสร้าง A() ซึ่งไม่มี (เมื่อเขียนตัวสร้างเองแล้ว compiler ไม่เพิ่มตัวสร้างปริยายให้อีก)
ต้องแก้โดยเขียน super(ค่า); เองในบรรทัดแรกของ B()
การสร้างคลาสใหม่แบบรับทอด
- เขียน ตัวสร้างที่ต้องการให้บริการ
- เพิ่ม ตัวแปรประจำอ็อบเจกต์ใหม่ที่จำเป็น
- เพิ่ม เมท็อดประจำอ็อบเจกต์ใหม่ที่ superclass ไม่มี
- แทน เมท็อดประจำอ็อบเจกต์เดิมของ superclass ที่อยากเปลี่ยน
- Java ไม่อนุญาตให้ลบเมท็อดของ superclass ที่ไม่อยากได้
- หลีกเลี่ยงการตั้งชื่อตัวแปรประจำอ็อบเจกต์ซ้ำกับของคลาสแม่
จุดที่มักพลาด
1. คิดว่าคลาสลูกได้ constructor ของแม่มาด้วย
ไม่ได้ ถ้า Ball มี Ball(double r, double x, double y) ก็ไม่ได้แปลว่า new ColoredBall(1, 2, 3) ใช้ได้ ต้องเขียนตัวสร้างใน
ColoredBall เอง แล้วเรียก super(r, x, y)
2. วาง super(...) ไม่ใช่บรรทัดแรก
this(...) และ super(...) ต้องเป็นคำสั่งแรกของตัวสร้างเท่านั้น และใช้ได้อย่างใดอย่างหนึ่ง
3. คิดว่า private variable ของแม่ไม่มีอยู่ในอ็อบเจกต์ลูก
มีอยู่ แต่โค้ดในคลาสลูกเข้าถึงโดยตรงไม่ได้ ต้องผ่านเมท็อดของแม่ (เช่น getter/setter)
4. สับสน override กับการเพิ่มเมท็อดใหม่
override คือเขียนเมท็อดที่ ชื่อและพารามิเตอร์เหมือนของแม่ ถ้าพารามิเตอร์ต่าง จะกลายเป็นเมท็อดใหม่อีกตัว (overload) ของเดิมยังอยู่
5. ลืมว่า super(); ถูกเติมให้อัตโนมัติ
โจทย์ไล่ผลลัพธ์มักไม่เขียน super() ให้เห็น ต้องนึกเองว่าตัวสร้างของแม่ทำงานก่อน
ที่มา: package_Inheritance.pdf หน้า 1–19 (หน้า 20–62 เป็น Lab แสดงผล LED ซึ่งใช้หลักเดียวกัน: DecimalLED_5x7 extends DotMatrixLED_5x7)