สวัสดี! เรามาพูดถึงคลาสซ้อนประเภทอื่นกัน ฉันกำลังพูดถึงคลาสท้องถิ่น (เมธอด-คลาสภายในท้องถิ่น) ก่อนดำดิ่ง เราต้องจำตำแหน่งของพวกมันในโครงสร้างของคลาสที่ซ้อนกันเสียก่อน จากแผนภาพของเรา เราจะเห็นว่าคลาสท้องถิ่นเป็น สาย
พันธุ์ย่อยของคลาสภายใน ซึ่งเราได้พูดถึงรายละเอียดในเอกสารก่อนหน้านี้ อย่างไรก็ตาม คลาสท้องถิ่นมีลักษณะสำคัญและความแตกต่างจากคลาสภายในทั่วไปหลายประการ สิ่งสำคัญคือการประกาศ: คลาสโลคัลถูกประกาศในบล็อกของรหัสเท่านั้น บ่อยครั้งที่การประกาศนี้อยู่ในเมธอดของคลาสภายนอก ตัวอย่างเช่น อาจมีลักษณะดังนี้:

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;
}
}
// ...number validation code
}
}
สำคัญ!หากคุณติดตั้ง Java 7 ไว้ โค้ดนี้จะไม่คอมไพล์เมื่อวางลงใน IDEA เราจะพูดถึงเหตุผลนี้ในตอนท้ายของบทเรียน กล่าวโดยย่อ วิธีการทำงานของคลาสท้องถิ่นนั้นขึ้นอยู่กับเวอร์ชันของภาษาเป็นอย่างมาก หากโค้ดนี้ไม่ได้คอมไพล์ให้คุณ คุณสามารถสลับเวอร์ชันภาษาใน IDEA เป็น Java 8 หรือเพิ่มคำfinal
ในพารามิเตอร์ method เพื่อให้มีลักษณะดังนี้validatePhoneNumber(final String number)
: หลังจากนั้นทุกอย่างจะทำงาน นี่เป็นโปรแกรมขนาดเล็กที่ใช้ตรวจสอบหมายเลขโทรศัพท์ วิธี การvalidatePhoneNumber()
ใช้สตริงเป็นอินพุตและกำหนดว่าเป็นหมายเลขโทรศัพท์หรือไม่ และในวิธีนี้ เราประกาศPhoneNumber
คลาส ท้องถิ่นของเรา คุณอาจถามอย่างสมเหตุสมผลว่าทำไม เหตุใดเราจึงต้องประกาศคลาสภายในเมธอด ทำไมไม่ใช้ชั้นในธรรมดา? จริงอยู่ เราสามารถสร้างPhoneNumber
ชั้นเรียนชั้นใน แต่ทางออกสุดท้ายขึ้นอยู่กับโครงสร้างและวัตถุประสงค์ของโปรแกรมของคุณ ลองนึกถึงตัวอย่างของเราจากบทเรียนเกี่ยวกับชั้นเรียนภายใน:
public class Bicycle {
private String model;
private int maxWeight;
public Bicycle(String model, int maxWeight) {
this.model = model;
this.maxWeight = maxWeight;
}
public void start() {
System.out.println("Let's go!");
}
public class HandleBar {
public void right() {
System.out.println("Steer right!");
}
public void left() {
System.out.println("Steer left!");
}
}
}
ในนั้นเราได้สร้างHandleBar
ชั้นในของจักรยาน ความแตกต่างคืออะไร? ประการแรก วิธีการใช้คลาสนั้นแตกต่างกัน คลาสHandleBar
ในตัวอย่างที่สองเป็นเอนทิตีที่ซับซ้อนกว่าPhoneNumber
คลาสในตัวอย่างแรก อันดับแรกHandleBar
มีสาธารณะright
และleft
เมธอด (ไม่ใช่ setters/getters) ประการที่สอง เป็นไปไม่ได้ที่จะคาดการณ์ล่วงหน้าว่าเราอาจต้องการมันที่ไหนและBicycle
ชั้น นอกของมัน อาจมีสถานที่และวิธีการต่างๆ มากมาย แม้แต่ในโปรแกรมเดียว แต่ด้วยPhoneNumber
ชั้นเรียนแล้วทุกอย่างจะง่ายขึ้นมาก โปรแกรมของเราง่ายมาก มีวัตถุประสงค์เพียงอย่างเดียว: เพื่อตรวจสอบว่าหมายเลขใดเป็นหมายเลขโทรศัพท์ที่ถูกต้องหรือไม่ ในกรณีส่วนใหญ่ของเราPhoneNumberValidator
จะไม่ได้เป็นโปรแกรมแบบสแตนด์อโลนด้วยซ้ำ แต่เป็นส่วนหนึ่งของตรรกะการให้สิทธิ์สำหรับโปรแกรมขนาดใหญ่กว่า ตัวอย่างเช่น เว็บไซต์ต่างๆ มักจะขอหมายเลขโทรศัพท์เมื่อผู้ใช้สมัครใช้งาน หากคุณป้อนเรื่องไร้สาระแทนตัวเลข เว็บไซต์จะรายงานข้อผิดพลาด: "นี่ไม่ใช่หมายเลขโทรศัพท์!" ผู้พัฒนาเว็บไซต์ดังกล่าว (หรือมากกว่านั้นคือกลไกการอนุญาตของผู้ใช้) สามารถรวมสิ่งที่คล้ายกับของเราได้PhoneNumberValidator
ในรหัสของพวกเขา กล่าวอีกนัยหนึ่ง เรามีคลาสภายนอกหนึ่งคลาสที่มีเมธอดเดียว ซึ่งจะใช้ในที่เดียวในโปรแกรมและจะไม่มีที่อื่นอีก และหากมีการใช้งานก็จะไม่มีอะไรเปลี่ยนแปลง: วิธีหนึ่งทำหน้าที่ของมัน - แค่นั้น ในกรณีนี้ เนื่องจากตรรกะทั้งหมดถูกรวบรวมไว้ในเมธอดเดียว การสรุปคลาสเพิ่มเติมจะสะดวกและเหมาะสมกว่ามาก ไม่มีวิธีการของตัวเองยกเว้นทะเยอทะยานและเซตเตอร์ ในความเป็นจริงเราต้องการข้อมูลจากตัวสร้างเท่านั้น ไม่เกี่ยวข้องกับวิธีการอื่น ดังนั้นจึงไม่มีเหตุผลที่จะนำข้อมูลเกี่ยวกับข้อมูลนี้ไปใช้นอกเหนือจากวิธีเดียวที่ใช้ เรายังให้ตัวอย่างที่มีการประกาศคลาสโลคัลในเมธอด แต่นี่ไม่ใช่ตัวเลือกเดียว สามารถประกาศได้ง่ายๆ ในบล็อครหัส:
public class PhoneNumberValidator {
{
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
}
public void validatePhoneNumber(String phoneNumber) {
// ...number validation 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;
}
}
// ...some logic
}
// ...number validation 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;
}
}
// ...number validation code
}
// Error! The compiler does not recognize the PhoneNumber class
public PhoneNumber generatePhoneNumber() {
}
}
คุณสมบัติที่สำคัญอีกประการของคลาสโลคัลคือความสามารถในการเข้าถึงตัวแปรโลคัลและพารามิเตอร์เมธอด ในกรณีที่คุณลืม ตัวแปรที่ประกาศภายในเมธอดเรียกว่าตัวแปร "โลคัล" นั่นคือถ้าเราสร้างString usCountryCode
ตัวแปรโลคัลภายในvalidatePhoneNumber()
เมธอดด้วยเหตุผลบางอย่าง เราสามารถเข้าถึงได้จากPhoneNumber
คลาส โลคัล อย่างไรก็ตาม มีรายละเอียดปลีกย่อยมากมายที่ขึ้นอยู่กับเวอร์ชันของภาษาที่ใช้ในโปรแกรม ในตอนต้นของบทเรียน เราสังเกตว่าโค้ดสำหรับหนึ่งในตัวอย่างอาจไม่ได้คอมไพล์ใน Java 7 จำได้ไหม ทีนี้ลองมาพิจารณาเหตุผลกัน: ใน Java 7 คลาสโลคัลสามารถเข้าถึงตัวแปรโลคัลหรือพารามิเตอร์เมธอดได้ก็ต่อเมื่อมีการประกาศfinal
ในเมธอด:
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
class PhoneNumber {
private String phoneNumber;
// Error! The method parameter must be declared as final!
public PhoneNumber() {
this.phoneNumber = number;
}
public void printUsCountryCode() {
// Error! The local variable must be declared as final!
System.out.println(usCountryCode);
}
}
// ...number validation code
}
ที่นี่คอมไพเลอร์สร้างข้อผิดพลาดสองข้อ และทุกอย่างเป็นไปตามลำดับที่นี่:
public void validatePhoneNumber(final String number) {
final String usCountryCode = "+1";
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public void printUsCountryCode() {
System.out.println(usCountryCode);
}
}
// ...number validation code
}
ตอนนี้คุณรู้แล้วว่าทำไมโค้ดตั้งแต่ต้นบทเรียนถึงไม่คอมไพล์: ใน Java 7 คลาสโลคัลมีสิทธิ์เข้าถึงเฉพาะfinal
พารามิเตอร์เมธอดและfinal
ตัวแปรโลคัล ใน Java 8 พฤติกรรมของคลาสโลคัลมีการเปลี่ยนแปลง ในภาษาเวอร์ชันนี้ คลาสโลคัลไม่สามารถเข้าถึงเฉพาะfinal
ตัวแปรและพารามิเตอร์โลคัลเท่านั้น แต่ยังสามารถเข้าถึงตัวแปรที่เป็นeffective-final
. Effective-final
เป็นตัวแปรที่มีค่าไม่เปลี่ยนแปลงตั้งแต่เริ่มต้น ตัวอย่างเช่น ใน Java 8 เราสามารถแสดงusCountryCode
ตัวแปรบนคอนโซลได้อย่างง่ายดาย แม้ว่าจะfinal
ไม่ใช่ สิ่งสำคัญคือค่าของมันไม่เปลี่ยนแปลง ในตัวอย่างต่อไปนี้ ทุกอย่างทำงานได้ตามปกติ:
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
class PhoneNumber {
public void printUsCountryCode() {
// Java 7 would produce an error here
System.out.println(usCountryCode);
}
}
// ...number validation code
}
แต่ถ้าเราเปลี่ยนค่าตัวแปรทันทีหลังจากเริ่มต้น โค้ดจะไม่คอมไพล์
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
usCountryCode = "+8";
class PhoneNumber {
public void printUsCountryCode() {
// Error!
System.out.println(usCountryCode);
}
}
// ...number validation 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;
}
}
// ...number validation 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;
}
}
// ...number validation code
}
}
initializers แบบคงที่ (บล็อกการเริ่มต้น) หรืออินเทอร์เฟซไม่สามารถประกาศในคลาสโลคัลได้ แต่คลาสโลคัลสามารถมีสมาชิกแบบสแตติกได้ โดยมีเงื่อนไขว่าคลาสเหล่านั้นเป็นตัวแปรคงที่ ( static final
) และตอนนี้คุณรู้เกี่ยวกับชั้นเรียนในท้องถิ่นแล้ว ทุกคน! อย่างที่คุณเห็น พวกเขามีความแตกต่างมากมายจากชนชั้นภายในทั่วไป เรายังต้องเจาะลึกคุณลักษณะของเวอร์ชันเฉพาะของภาษาเพื่อที่จะเข้าใจวิธีการทำงานของมัน :) ในบทเรียนถัดไป เราจะพูดถึงคลาสภายในที่ไม่ระบุตัวตน ซึ่งเป็นกลุ่มสุดท้ายของคลาสซ้อนกัน ขอให้โชคดีในการเรียน! :)
GO TO FULL VERSION