Review Basic Java for Beginner (1/3)

Phai Panda
6 min readFeb 24, 2023

--

มาทบทวนกันครับว่าพื้นฐานภาษา Java เรื่องไหนบ้างที่ยังขาดไป อ้างอิงเวอร์ชัน 1.8

JShell

โปรแกรมนี้มีมากับ JDK เวอร์ชัน 9 (1.9) อยู่ใน folder ชื่อ bin สามารถใช้เรียนเขียนจาวาพื้นฐานได้ อ้างอิง

มัน imports ไลบรารีเบื้องต้นมาให้ ตรวจสอบ /imports

Class

คลาสคือไทป์ (type) ประกอบด้วย state กับ action

  • ตัว state คือข้อมูล (data)
  • ส่วน action คือเมธอด (method)

ตัวอย่างคลาส Passenger

  • มี state เช่น name, address
  • มี action เช่น walk, smile

คลาสสร้างด้วย new keyword เช่น Passenger p1 = new Passenger();

กำหนดค่าให้กับ state ผ่าน instance ของคลาส เช่น p1.name =​ “John”;

เรียกใช้ action ของคลาส เช่น p1.walk();

default constructor ของคลาสจะต้องถูกเรียกให้ทำงานก่อนเสมอ เช่น new Passenger();

Encapsulation

คือการออกแบบ (design) เพื่อซ่อน state หรือ action ที่ต้องการไว้กับคลาสนั้นๆเอง ด้วย access modifiers เช่น

public class Passenger { // public คลาสนี้ถูกเข้าถึงได้จากทุก packages
private String name; // private เฉพาะคลาสนี้เท่านั้นเข้าถึง name ได้
void walk() { } // default เฉพาะ package เดียวกันเข้าถึง walk ได้
}

Abstraction

การซ่อนความซับซ้อนไว้ภายในเมธอด

เช่น กำหนดให้คลาส MotorBike มี speed กำหนดสูงสุดไว้ที่ 120 และต่ำสุดที่ 0

class MotorBike {
int speed = 0;

void setSpeed(int newSpeed) {
if(newSpeed >= 0 && newSpeed <= 120) {
this.speed = newSpeed;
}
}
}

จากตัวอย่าง setSpeed จะกำหนดค่า speed ใหม่ได้ก็ต่อเมื่อค่านั้นมากกว่าหรือเท่ากับ 0 และไม่เกิน 120 เหล่านี้คือความซับซ้อนที่ผู้เรียกใช้เมธอดนี้ไม่จำเป็นต้องทราบ

Primitive Data Types

คือไทป์พื้นฐานของจาวา ได้แก่ byte, short, int, long, float, double, boolean และ char อ้างอิง

สามารถจัดกลุ่มได้

Integer: byte, short, int, long

Floating Point: float, double

Boolean: boolean

Character: char

ตัวอย่าง

byte speed = 120;
short maxItems = 32767;
int count = -99;
long id = 100;
float rate = 3.5f;
double maxRate = 3.5;
boolean isActive = true;
char ch = 'A';

Wrapper Classes

คือไทป์ที่ห่อหุ้ม primitive data types เพื่อใช้งานพวกมันดั่ง Object สิ่งที่ได้คือเมธอดที่อำนวยความสะดวกในการใช้งาน อ้างอิง

ตัวอย่าง

Byte speed = new Byte((byte) 120);
Short maxItem = new Short((short) 32767);
Integer count = new Integer(-99);
Long id = new Long(100);
Float rate = new Float(3.5);
Double maxRate = new Double(3.5);
Boolean isActive = new Boolean(true);
Character ch = new Character('A');

อำนวยความสะดวก เช่น ถามค่าต่ำสุด-สูงสุดที่รับได้

  System.out.println(Short.MIN_VALUE); // -32768
System.out.println(Short.MAX_VALUE); // 32767

Floating Point Data

คือเลขทศนิยม การกำหนดด้วย literal หรือค่าใดๆ เช่น 3.15 จาวาให้ไทป์ ​double เป็น default ค่านี้ยังสามารถเขียนต่อท้ายด้วย d หรือ D เพื่อบอกความเป็น double เช่น 3.15d หรือ 3.15D

ตัวอย่าง

  double x = 3.15;
double y = 3.15d;
double z = 3.15D;
System.out.println(Double.SIZE); // 64
System.out.println(Double.BYTES); // 8
System.out.println(Double.MIN_VALUE); // 4.9E-324
System.out.println(Double.MAX_VALUE); // 1.7976931348623157E308

ต่างจาก literal ของ float ที่ต้องกำกับด้วย f หรือ F ต่อท้ายค่าเสมอ

ตัวอย่าง

  float x = 3.15f;
float y = 3.15F;
System.out.println(Float.SIZE); // 32
System.out.println(Float.BYTES); // 4
System.out.println(Float.MIN_VALUE); // 1.4E-45
System.out.println(Float.MAX_VALUE); // 3.4028235E38

Explicit Casting

การแปลงค่าไทป์จากเล็กไปใหญ่ พื้นที่ของค่านั้นในหน่วยความจำจะไม่สูญเสียจำนวนบิต (เพราะขยายใหญ่ขึ้น) เรียกว่า implicit casting กระทำโดย Compiler เช่น

double x = 3;

โดยที่ 3 ถูกมองว่าเป็น integer ถูก implicit casting ไปเป็น double ผลลัพธ์คือ 3.0

ในขณะที่ explicit casting กระทำโดยโปรแกรมเมอร์ พื้นที่ของค่านั้นในหน่วยความจำจะสูญเสียจำนวนบิต (เพราะลดขนาดลง) เป็นการแปลงค่าไทป์จากใหญ่ไปเล็ก

int x = 3.15; // รันไม่ได้

int x = (int)3.15; // รันได้

โดยที่ (int) คือ explicit casting ไทป์จาก double ไปเป็น int ผลลัพธ์คือ 3 เฉยๆ

คลาส BigDecimal

สำหรับไทป์ที่มีขนาดใหญ่เกินกว่า 100 บิต ซึ่งเกินเกินกว่า long และ double เพื่อการคำนวณที่ต้องการความแม่นยำและเข้มงวดมาก จาวานำเสนอ

  • BigInteger สำหรับ integer data
  • BigDecimal สำหรับ floating point data

BigInteger และ BigDecimal ไม่สามารถใช้เครื่องหมาย เช่น +-*/% กับพวกมันได้ อ่านต่อ

ตัวอย่าง

  BigDecimal x = new BigDecimal(5);
BigDecimal y = new BigDecimal(5.1);
BigDecimal z = new BigDecimal("5.1");
System.out.println(x); // 5
System.out.println(y); // 5.0999999999999996447286321199499070644378662109375
System.out.println(z); // 5.1

จากตัวอย่าง ตัวแปร x, y และ z มีไทป์เป็น BigDecimal เพื่อให้ผลลัพธ์ไม่ผิดพลาดควรใช้สตริงกำหนดค่าให้กับมัน (สตริงมีสมบัติ immuable)

คลาส String

อาร์เรย์ของ char (หรือ char[]) เรียกว่าสตริง เราสามารถสร้างสตริงจากคลาส String ซึ่งสตริงมีสมมบัติเป็น immutability หมายถึงการไม่เปลี่ยนแปลง

การเปรียบเทียบค่าของสตริงของคลาส String ใช้ใช้เมธอด equals

String name1 = new String("John");
String name2 = new String("John");
System.out.println("John == John is " + (name1 == name2)); // false
System.out.println("John equals John " + name1.equals(name2)); // true

String Literal

สตริงที่สร้างโดย literal มีสมมบัติเป็น immutability ไม่ต่างจากคลาส String สามารถกำหนดค่าสตริง (literal) ให้กับตัวแปรได้โดยตรง

การเปรียบเทียบสตริงใช้ได้ทั้งเครื่องหมาย == และเมธอด equals แต่การเปรียบเทียบค่าของสตริงแนะนำให้ใช้เมธอด equals เท่านั้น

String name1 = "John";
String name2 = "John";
System.out.println("John == John is " + (name1 == name2)); // true
System.out.println("John equals John " + name1.equals(name2)); // ture

Reference Types

การอ้างอิงไปยัง memory address หรือการคัดลอกค่า memory address เรียกว่า reference

ส่วนตัวเห็นว่า reference ไม่มีอยู่จริง ทุกสิ่งที่เกิดคือการ copy by value ทั้งสิ้น ถ้า value นั้นหมายถึง memory address ด้วย

int x = 100;

สิ่งที่เกิดขึ้นคือ การจอง memory สำหรับตัวแปร x ดังรูป

x variable memory allocation

โดยที่ 0xF01 เป็น memory address สมมติ

และหากประกาศตัวแปร x ไว้ในเมธอด ผลคือพื้นที่ของรูปข้างต้นนี้ก็จะเกิดใน stack ของเมธอดนั้นด้วย

ตัวอย่างถัดมา

String name1 = new String(“John”);

สิ่งที่เกิดขึ้นคือ การจอง memory สำหรับตัวแปร name1 ดังรูป

name1 variable memory allocation

โดยที่ 0xA01 เป็น memory address สมมติที่เกิดขึ้น ณ Heap memory (Global Shared) และถูกคัดลอกเพื่อใช้ในการอ้างอิง (reference type) ต่อไป

หากมองว่า 100 กับ 0xA01 เป็นเพียง value หรือค่าใดๆ นั่นแสดงว่า reference type ไม่มีอยู่จริง

ตัวอย่างถัดมา

String name2 = “John”

String name3 = “John”

สิ่งที่เกิดขึ้นคือ การจอง memory สำหรับตัวแปร name2 และ name3 ดังรูป

name2 and name3 variables memory allocation

โดยที่ 0xA02 เป็น memory address สมมติที่เกิดขึ้น ณ Heap memory ซึ่งอยู่ในโครงสร้างที่เรียกว่า String Pool เก็บเหล่าสตริง literal ไว้ภายใน

นั่นเป็นที่มาว่า = new String(“John”); เหตุใดใช้เครื่องหมาย == เปรียบเทียบแล้วได้ผลเป็น false คำตอบคือ memory address ต่างกัน

และเป็นที่มาว่า = “John”; เหตุใดใช้เครื่องหมาย == เปรียบเทียบแล้วได้ผลเป็น true คำตอบคือ memory address เหมือนกัน

String Immutable

สตริง immutable คือค่าของสตริงไม่สามารถแก้ไขได้ การแก้ไขใดๆก็คือการสร้างสตริงใหม่เท่านั้น

ประโยชน์

  • สตริงสามารถ reuse หรือนำกลับมาใช้ซ้ำได้ดังที่ปรากฏใน String Pool
  • username และ password ที่ใช้เชื่อมต่อไปยังฐานข้อมูล, socket โดยวิธีการโปรแกรมมิ่งต้องแก้ไขไม่ได้เพื่อป้องกันการ hack
  • multithreading จะไม่สามารถเปลี่ยนแปลงค่าของสตริงได้ นั่นเท่ากับ thread-safe

คลาส StringBuffer

ทุกครั้งที่มีการสร้างสตริงที่แตกกต่างกันจะจองหน่วยความจำใหม่เสมอ เพื่อลดการใช้หน่วยความจำลักษณะนี้ ควรใช้คลาส StringBuffer

StringBuffer ใช้ synchronized method นั่นเท่ากับ thread-safe หมายความว่ามีเพียงเธรดเดียวเท่านั้นที่เข้าถึงออบเจกต์ของ StringBuffer ได้ในแต่ละครั้งที่ทำ multithreading

ตัวอย่าง

StringBuffer buffer = new StringBuffer("Jon");
buffer.append(" went to school");
buffer.append(" every day.");
String msg = buffer.toString();
System.out.println(msg);

คลาส StringBuilder

โปรแกรมนั้นทำงานแบบเธรดเดียว (single thread) และต้องการประสิทธิภาพการใช้สตริงที่ดีกว่า StringBuffer แนะนำให้ใช้ StringBuilder เพราะมัน Non-thread-safe

ตัวอย่าง

StringBuilder buffer = new StringBuilder("Jon");
buffer.append(" went to school");
buffer.append(" every day.");
String msg = buffer.toString();
System.out.println(msg);

Auto Boxing

คือการสร้าง Wrapper Class ให้อัตโนมัติ

เช่น

Integer x = 100;

แท้จริงคือ

Integer x = Integer.valueOf(100);

โดยที่กลไกภายในคือ

new Integer(100);

Variable Arguments

ระบุให้ parameter สามารถรับค่าเป็นจำนวน arguments เข้ามา (number of objects) อยู่ในรูปแบบ syntax เครื่องหมาย ellipsis หรือก็คือ … และต้องอยู่ลำดับท้ายสุดของ parameter ทั้งหมด

public static void main(String[] args) {
int x = 1, y = 2;
fun1("test", x, y, 3, 4, 5);
}

static void fun1(String str, int... a) { }

จากตัวอย่าง x, y, 3, 4, 5 คือ variable arguments ส่วน a คือตัวแปรที่รับค่า variable arguments ไปใช้และตัวมันเองมีสมบัติเป็นอาร์เรย์

Object composition

คือการห่อหุ้มคลาสไว้ในคลาส เช่น คลาส Customer มีคลาส Address

Inheritance

คือการถ่ายทอดสมบัติ (state และ action) จากคลาสแม่ (parent class) ไปยังคลาสสืบทอดหรือคลาสลูก (child class) โดยใช้ extends keyword

คลาสสืบทอดมีคลาสแม่ได้เพียงคลาสเดียวเท่านั้น

สมบัติของคลาสแม่ในคลาสสืบทอดจะถูกเรียกได้จาก super keywork

constructor ของคลาสแม่ในคลาสสืบทอดจะถูกเรียกอัตโนมัติโดย Java Compiler

constructor ของคลาสแม่ในคลาสสืบทอดจะถูกเรียกเพื่อนำไปสร้างเป็นออบเจกต์ของคลาสแม่ก่อนคลาสสืบทอดเสมอ

หากคลาสแม่เปรียบเสมือนบรรพบุรุษ บรรพบุรุษของบรรพบุรุษจะต้องถูกเรียกเพื่อสร้างเป็นออบเจกต์ก่อนเสมอ

private กำหนดให้ state และ action ไม่ส่งต่อไปยังคลาสถ่ายทอด

Abstract Class

ความเป็นคลาสรูปธรรม (concrete class) สามารถนำไปสร้างออบเจกต์ได้เลย ส่วนความเป็นคลาสนามธรรม (abstract class) จะต้องถูก extends เท่านั้น

abstract class ไม่สามารถนำไปสร้างเป็นออบเจกต์ได้

ไม่มี abstract state ใน abstract class มีแค่ abstract action หรือ abstract method

ตัวอย่าง

public abstract class AbstractMotorBike {
protected abstract void setSpeed(int newSpeed);
}
public class MotorBike extends AbstractMotorBike {
int speed = 0;

@Override
public void setSpeed(int newSpeed) {
if(newSpeed >= 0 && newSpeed <= 120) {
this.speed = newSpeed;
}
}
}
public static void main(String[] args) {
AbstractMotorBike motorBike = new MotorBike();
motorBike.setSpeed(80);
}

คลาส AbstractMotorBike กำหนดให้ setSpeed เป็น abstract method ที่ยังไม่ถูก implement ด้วยเหตุนี้คลาสสืบทอด MotorBike จึงต้อง implement เพื่อกำหนดการทำงานแก่มัน

การสร้าง abstract class เป็นเพียงการ design ตาม design pattern แค่นั้น

Interface

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

หนึ่งคลาสสามารถ implement ได้หลาย interfaces

ตัวอย่าง

public interface Runnable {
public abstract void run();
}

interface นี้ชื่อว่า Runable มี abstract method (จะระบุคีย์เวิร์ด abstract หรือไม่ก็ได้) ชื่อ run

คลาสใดที่ implement interface นี้จะได้สมมบัติของ Runnable คือ run ไงล่ะ

ตัวอย่าง

class Task implements Runnable {
@Override
public void run() {
for(int i = 1; i <= 5; i++) {
System.out.println(i);
}
}
}

หากเรียก new Task().run(); ก็จะลูปตัวเลข 1 ถึง 5

JDK 1.8 ขึ้นไปสามารถทำ default implementation ได้

คือการกำหนดการทำงานเบื้องต้นให้กับ abstract method ใน interface

ตัวอย่าง

interface Nameable {
default String getName() {
return getClass().getSimpleName();
}
}
class Task implements Runnable, Nameable {
@Override
public void run() {
for(int i = 1; i <= 5; i++) {
System.out.println(getName() + " process " + i);
}
}
}
 public static void main(String[] args) {
Task task1 = new Task();
task1.run();
}

ผลลัพธ์

Task process 1
Task process 2
Task process 3
Task process 4
Task process 5

ประโยชน์ของ default implementation คือทำให้หลายคลาสที่ implement interface ตัวเดียวกันไม่ต้องถูกแก้ไขแม้ว่าในอนาคตจะมีการปรับปรุงเพิ่มเมธอดใหม่ๆเข้ามา กล่าวคือให้เมธอดเหล่านั้นมี default implementation นั่นเอง

LocalDate, LocalTime, LocalDateTime

package java.time.* ใน JDK 1.8 ได้อำนวยความสะดวกแก่การใช้งาน วัน-เดือน-ปี และ เวลา มาให้แทนการใช้งาน java.util.Date อันแสนเจ็บปวด อ่านต่อ

Polymorphism

คือการใช้ประโยชน์จาก different types of behaviours โดยใช้ Interface และแนวคิด Inheritance

ตัวอย่าง

class Task1 implements Runnable {
@Override
public void run() {
for(int i = 1; i <= 5; i++) {
System.out.println(i);
}
}
}
class Task2 implements Runnable {
@Override
public void run() {
for(int i = 65; i <= 70; i++) {
System.out.println((char)i);
}
}
}

คลาส Task1 และ Task2 ต่าง implement Runnable interface ดังนั้นมีเมธอด run

 public static void main(String[] args) {
Runnable[] runnables = {new Task1(), new Task2()};
process(runnables);
}

ทั้งคู่สร้างเป็นออบเจกต์เก็บไว้ในอาร์เรย์ของตัวแปร runnables

ทั้งคู่จะถูกเรียกให้ทำงานด้วยเมธอด process โดยการ polymorphism ในขณะที่การทำงานของทั้งคู่ให้ผลลัพธ์ต่างกัน

 static void process(Runnable[] runnables) {
for(Runnable runnable: runnables) {
runnable.run();
}
}

ผลลัพธ์

1
2
3
4
5
A
B
C
D
E
F

แล้วพบกันใหม่ใน 2/3 ครับ

--

--