สวัสดี! เรามาพูดถึงคลาสที่ซ้อนกันประเภทอื่นกันดีกว่า กล่าวคือเกี่ยวกับคลาสท้องถิ่น (วิธีคลาสภายในท้องถิ่น) สิ่งแรกที่คุณต้องจำก่อนเรียนคือสถานที่ในโครงสร้างของชั้นเรียนที่ซ้อนกัน
จากแผนภาพของเรา เราสามารถเข้าใจได้ว่าคลาสท้องถิ่นเป็นประเภทย่อยของคลาสภายใน ซึ่งเราได้พูดถึงโดยละเอียดในหนึ่งในเนื้อหาก่อนหน้านี้ อย่างไรก็ตาม คลาสท้องถิ่นมีคุณสมบัติที่สำคัญจำนวนหนึ่งและมีความแตกต่างจากคลาสภายใน กุญแจสำคัญอยู่ในการประกาศ: คลาสท้องถิ่นถูกประกาศในบล็อคโค้ดเท่านั้น บ่อยที่สุด - ภายในวิธีการบางอย่างของคลาสภายนอก ตัวอย่างเช่น อาจมีลักษณะดังนี้:
public class PhoneNumberValidator {
public void validatePhoneNumber(String number) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
//...code валидации номера
}
}
สำคัญ!โค้ดนี้จะไม่คอมไพล์เมื่อวางลงใน IDEA หากคุณติดตั้ง Java 7 ไว้ เราจะพูดถึงเหตุผลนี้ในตอนท้ายของการบรรยาย กล่าวอีกนัยหนึ่ง งานของชั้นเรียนในท้องถิ่นนั้นขึ้นอยู่กับเวอร์ชันของภาษาเป็นอย่างสูง หากโค้ดนี้ไม่คอมไพล์สำหรับคุณ คุณสามารถเปลี่ยนเวอร์ชันภาษาใน IDEA เป็น Java 8 หรือเพิ่มคำลงfinal
ในพารามิเตอร์ method เพื่อให้มีลักษณะดังนี้validatePhoneNumber(final String number)
: หลังจากนี้ทุกอย่างจะได้ผล นี่เป็นโปรแกรมขนาดเล็ก - เครื่องมือตรวจสอบหมายเลขโทรศัพท์ วิธีการของมันvalidatePhoneNumber()
ใช้สตริงเป็นอินพุตและพิจารณาว่าเป็นหมายเลขโทรศัพท์หรือไม่ และภายในวิธีนี้เราได้ประกาศคลาสท้องถิ่นของPhoneNumber
เรา คุณอาจมีคำถามเชิงตรรกะ: ทำไม? เหตุใดจึงประกาศคลาสภายในวิธีการ? ทำไมไม่ใช้คลาสภายในปกติล่ะ? อันที่จริงใคร ๆ ก็สามารถทำเช่นนี้ได้: ทำให้คลาสเป็นแบบPhoneNumber
ภายใน อีกประการหนึ่งคือการตัดสินใจขั้นสุดท้ายขึ้นอยู่กับโครงสร้างและวัตถุประสงค์ของโปรแกรมของคุณ จำตัวอย่างของเราจากการบรรยายเกี่ยวกับชั้นเรียนภายใน:
public class Bicycle {
private String model;
private int mawWeight;
public Bicycle(String model, int mawWeight) {
this.model = model;
this.mawWeight = mawWeight;
}
public void start() {
System.out.println("Go!");
}
public class HandleBar {
public void right() {
System.out.println("Steering wheel to the right!");
}
public void left() {
System.out.println("Steering wheel to the left!");
}
}
}
ในนั้นเราได้สร้างHandleBar
(แฮนด์รถ) ให้เป็นระดับภายในของจักรยาน ความแตกต่างคืออะไร? ก่อนอื่นในการใช้คลาส คลาสHandleBar
จากตัวอย่างที่สองเป็นเอนทิตีที่ซับซ้อนมากกว่าPhoneNumber
ตัวอย่างแรก อันดับแรก y HandleBar
มีวิธีการสาธารณะright
และleft
(ไม่ใช่ setter และ getter) ประการที่สอง เราไม่สามารถคาดการณ์ล่วงหน้าได้ว่าBicycle
เราต้องการมันที่ไหนและระดับภายนอกของมัน ซึ่งสิ่งเหล่านี้อาจเป็นสถานที่และวิธีการที่แตกต่างกันหลายสิบแห่ง แม้จะอยู่ในโปรแกรมเดียวกันก็ตาม แต่ด้วยชั้นเรียนPhoneNumber
ทุกอย่างจะง่ายกว่ามาก โปรแกรมของเรานั้นง่ายมาก มีเพียงฟังก์ชันเดียวเท่านั้น - เพื่อตรวจสอบว่าหมายเลขนั้นเป็นหมายเลขโทรศัพท์หรือไม่ ในกรณีส่วนใหญ่ โปรแกรมของเราPhoneNumberValidator
จะไม่ใช่โปรแกรมอิสระด้วยซ้ำ แต่เป็นเพียงส่วนหนึ่งของตรรกะการอนุญาตสำหรับโปรแกรมหลัก เช่น ในเว็บไซต์ต่างๆ เมื่อลงทะเบียน คุณมักจะถูกขอให้กรอกหมายเลขโทรศัพท์ และหากคุณพิมพ์เรื่องไร้สาระแทนตัวเลข ไซต์จะแสดงข้อผิดพลาด: “นี่ไม่ใช่หมายเลขโทรศัพท์!” สำหรับการดำเนินงานของไซต์ดังกล่าว (หรือกลไกการอนุญาตผู้ใช้) นักพัฒนาสามารถรวมอะนาล็อกของเราไว้ในโค้ดPhoneNumberValidator
ได้ กล่าวอีกนัยหนึ่ง เรามีคลาสภายนอกหนึ่งคลาสที่มีวิธีเดียวที่จะใช้ในที่เดียวในโปรแกรมและไม่มีที่อื่นเลย และถ้ามันเป็นเช่นนั้น ก็จะไม่มีอะไรเปลี่ยนแปลงไป: มีวิธีการหนึ่งที่ทำหน้าที่ของมันได้ นั่นคือทั้งหมด ในกรณีนี้ เนื่องจากตรรกะการทำงานทั้งหมดถูกรวบรวมไว้ในวิธีเดียว จะสะดวกกว่าและถูกต้องมากในการสรุปคลาสเพิ่มเติมที่นั่น ไม่มีวิธีการของตัวเองนอกจาก getter และ setter โดยพื้นฐานแล้วเราต้องการเพียงข้อมูลคอนสตรัคเตอร์เท่านั้น ไม่ได้ใช้ในวิธีอื่น ดังนั้นจึงไม่มีเหตุผลที่จะขยายข้อมูลเกี่ยวกับเรื่องนี้นอกเหนือจากวิธีการเดียวที่ใช้ เราได้ยกตัวอย่างการประกาศคลาสท้องถิ่นด้วยวิธีการ แต่นี่ไม่ใช่ความเป็นไปได้เพียงอย่างเดียว สามารถประกาศได้ง่ายๆ ในบล็อคโค้ด:
public class PhoneNumberValidator {
{
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
}
public void validatePhoneNumber(String phoneNumber) {
//...code валидации номера
}
}
หรือแม้กระทั่งอยู่ในวงfor
!
public class PhoneNumberValidator {
public void validatePhoneNumber(String phoneNumber) {
for (int i = 0; i < 10; i++) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
//...Howая-то логика
}
//...code валидации номера
}
}
แต่กรณีดังกล่าวพบได้น้อยมาก ในกรณีส่วนใหญ่ การประกาศจะยังคงเกิดขึ้นภายในวิธีการ ดังนั้นเราจึงจัดการกับการประกาศ เรายังพูดคุยเกี่ยวกับ "ปรัชญา" :) คลาสท้องถิ่นมีคุณสมบัติและความแตกต่างอะไรจากคลาสภายในอีกบ้าง? ไม่สามารถสร้างวัตถุคลาสท้องถิ่นภายนอกวิธีการหรือบล็อกที่มีการประกาศได้ ลองนึกภาพเราต้องการวิธีgeneratePhoneNumber()
การสร้างหมายเลขโทรศัพท์แบบสุ่มและส่งกลับไฟล์PhoneNumber
. เราจะไม่สามารถสร้างวิธีการดังกล่าวในคลาสเครื่องมือตรวจสอบของเราได้ในสถานการณ์ปัจจุบัน:
public class PhoneNumberValidator {
public void validatePhoneNumber(String number) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
//...code валидации номера
}
//ошибка! компилятор не понимает, что это за класс - PhoneNumber
public PhoneNumber generatePhoneNumber() {
}
}
คุณสมบัติที่สำคัญอีกประการหนึ่งของคลาสโลคัลคือความสามารถในการเข้าถึงตัวแปรโลคัลและพารามิเตอร์เมธอด ในกรณีที่คุณลืม “local” คือตัวแปรที่ประกาศภายในเมธอด String russianCountryCode
นั่นคือถ้าเราสร้างตัวแปรท้องถิ่น ภายในวิธีการเพื่อวัตถุประสงค์บางอย่างของเราvalidatePhoneNumber()
เราก็สามารถเข้าถึงได้จากคลาสPhoneNumber
ท้องถิ่น อย่างไรก็ตาม มีรายละเอียดปลีกย่อยมากมายที่นี่ซึ่งขึ้นอยู่กับเวอร์ชันของภาษาที่ใช้ในโปรแกรม ในตอนต้นของการบรรยาย เราได้สังเกตว่าโค้ดในตัวอย่างหนึ่งอาจไม่สามารถคอมไพล์ใน Java 7 ได้ จำได้ไหม? ตอนนี้เรามาดูเหตุผลของสิ่งนี้กัน :) ใน Java 7 คลาสโลคัลสามารถเข้าถึงตัวแปรโลคัลหรือพารามิเตอร์เมธอดได้ก็ต่อเมื่อมีการประกาศในเมธอดเป็นfinal
:
public void validatePhoneNumber(String number) {
String russianCountryCode = "+7";
class PhoneNumber {
private String phoneNumber;
//ошибка! параметр метода должен быть объявлен How final!
public PhoneNumber() {
this.phoneNumber = number;
}
public void printRussianCountryCode() {
//ошибка! локальная переменная должна быть объявлена How final!
System.out.println(russianCountryCode);
}
}
//...code валидации номера
}
ที่นี่คอมไพเลอร์แสดงข้อผิดพลาดสองประการ แต่ที่นี่ทุกอย่างเป็นไปตามลำดับ:
public void validatePhoneNumber(final String number) {
final String russianCountryCode = "+7";
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public void printRussianCountryCode() {
System.out.println(russianCountryCode);
}
}
//...code валидации номера
}
ตอนนี้คุณรู้เหตุผลว่าทำไมโค้ดตอนเริ่มต้นของการบรรยายจึงไม่คอมไพล์: คลาสท้องถิ่นใน Java 7 มีสิทธิ์เข้าถึงfinal
พารามิเตอร์ -method และfinal
ตัวแปร -local เท่านั้น ใน Java 8 พฤติกรรมของคลาสโลคัลเปลี่ยนไป ในภาษาเวอร์ชันนี้ คลาสโลคัลมีสิทธิ์เข้าถึงไม่เพียงแต่final
ตัวแปรและพารามิเตอร์ -โลคัล แต่ยังรวมถึงeffective-final
. Effective-final
เป็นตัวแปรที่มีค่าไม่เปลี่ยนแปลงตั้งแต่เริ่มต้น ตัวอย่างเช่น ใน Java 8 เราสามารถแสดงตัวแปรไปยังคอนโซลได้อย่างง่ายดายrussianCountryCode
แม้ว่าจะไม่ใช่final
ก็ตาม สิ่งสำคัญคือมันไม่เปลี่ยนความหมาย ในตัวอย่างนี้ ทุกอย่างทำงานได้ตามปกติ:
public void validatePhoneNumber(String number) {
String russianCountryCode = "+7";
class PhoneNumber {
public void printRussianCountryCode() {
//в Java 7 здесь была бы ошибка
System.out.println(russianCountryCode);
}
}
//...code валидации номера
}
แต่ถ้าเราเปลี่ยนค่าของตัวแปรทันทีหลังจากการกำหนดค่าเริ่มต้น โค้ดจะไม่คอมไพล์
public void validatePhoneNumber(String number) {
String russianCountryCode = "+7";
russianCountryCode = "+8";
class PhoneNumber {
public void printRussianCountryCode() {
//error!
System.out.println(russianCountryCode);
}
}
//...code валидации номера
}
แต่ไม่ใช่เพื่ออะไรที่คลาสท้องถิ่นจะเป็นประเภทย่อยของคลาสภายใน! พวกเขาก็มีจุดร่วมเช่นกัน คลาสท้องถิ่นสามารถเข้าถึงฟิลด์และวิธีการทั้งหมด (แม้แต่ส่วนตัว) ของคลาสภายนอก: ทั้งแบบคงที่และไม่คงที่ ตัวอย่างเช่น ลองเพิ่มฟิลด์คงที่ให้กับคลาสเครื่องมือตรวจสอบของเราString phoneNumberRegex
:
public class PhoneNumberValidator {
private static String phoneNumberRegex = "[^0-9]";
public void validatePhoneNumber(String phoneNumber) {
class PhoneNumber {
//......
}
}
}
การตรวจสอบจะดำเนินการโดยใช้ตัวแปรคงที่นี้ วิธีการตรวจสอบว่าสตริงที่ส่งไปนั้นมีอักขระที่ไม่ตรงกับนิพจน์ทั่วไป " [^0-9]
" หรือไม่ (นั่นคืออักขระไม่ใช่ตัวเลขตั้งแต่ 0 ถึง 9) PhoneNumber
เราสามารถเข้าถึงตัวแปรนี้ ได้อย่างง่ายดายจากคลาสท้องถิ่น ตัวอย่างเช่น เขียน getter:
public String getPhoneNumberRegex() {
return phoneNumberRegex;
}
คลาสท้องถิ่นจะคล้ายกับคลาสภายในเนื่องจากไม่สามารถกำหนดหรือประกาศสมาชิกแบบคงที่ได้ คลาสท้องถิ่นในวิธีการแบบคงที่สามารถอ้างอิงเฉพาะสมาชิกแบบคงที่ของคลาสที่ปิดล้อมเท่านั้น ตัวอย่างเช่น หากคุณไม่ได้กำหนดตัวแปร (ฟิลด์) ของคลาสที่ปิดล้อมเป็นแบบคงที่ คอมไพเลอร์ Java จะสร้างข้อผิดพลาด: “ตัวแปรที่ไม่คงที่ไม่สามารถอ้างอิงได้จากบริบทแบบคงที่” คลาสในเครื่องไม่คงที่เนื่องจากมีสิทธิ์เข้าถึงสมาชิกของอินสแตนซ์ของบล็อกที่มีอยู่ ดังนั้นจึงไม่สามารถมีการประกาศแบบคงที่ได้เกือบทุกประเภท คุณไม่สามารถประกาศอินเทอร์เฟซภายในบล็อกได้ อินเทอร์เฟซมีลักษณะคงที่ รหัสนี้จะไม่รวบรวม:
public class PhoneNumberValidator {
public static void validatePhoneNumber(String number) {
interface I {}
class PhoneNumber implements I{
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
}
//...code валидации номера
}
}
แต่หากมีการประกาศอินเทอร์เฟซภายในคลาสภายนอก คลาสPhoneNumber
ก็สามารถนำไปใช้ได้:
public class PhoneNumberValidator {
interface I {}
public static void validatePhoneNumber(String number) {
class PhoneNumber implements I{
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
}
//...code валидации номера
}
}
คลาสท้องถิ่นไม่สามารถประกาศตัวเริ่มต้นแบบคงที่ (บล็อกการเริ่มต้น) หรืออินเทอร์เฟซ แต่คลาสโลคัลสามารถมีสมาชิกแบบคงที่ได้ หากเป็นตัวแปรคงที่ ( static final
) นั่นคือสิ่งที่พวกเขาเป็น ชั้นเรียนท้องถิ่น! อย่างที่คุณเห็น พวกมันมีความแตกต่างจากคลาสภายในมากมาย เรายังต้องเจาะลึกคุณสมบัติของเวอร์ชันภาษาเพื่อทำความเข้าใจวิธีการทำงาน :) ในการบรรยายครั้งถัดไป เราจะพูดถึงคลาสภายในที่ไม่ระบุชื่อ - กลุ่มสุดท้ายของคลาสที่ซ้อนกัน ขอให้โชคดีกับการเรียนของคุณ! :)
GO TO FULL VERSION