JavaRush /จาวาบล็อก /Random-TH /คลาสภายในในวิธีท้องถิ่น

คลาสภายในในวิธีท้องถิ่น

เผยแพร่ในกลุ่ม
สวัสดี! เรามาพูดถึงคลาสที่ซ้อนกันประเภทอื่นกันดีกว่า กล่าวคือเกี่ยวกับคลาสท้องถิ่น (วิธีคลาสภายในท้องถิ่น) สิ่งแรกที่คุณต้องจำก่อนเรียนคือสถานที่ในโครงสร้างของชั้นเรียนที่ซ้อนกัน คลาสภายในในวิธีโลคัล - 2จากแผนภาพของเรา เราสามารถเข้าใจได้ว่าคลาสท้องถิ่นเป็นประเภทย่อยของคลาสภายใน ซึ่งเราได้พูดถึงโดยละเอียดในหนึ่งในเนื้อหาก่อนหน้านี้ อย่างไรก็ตาม คลาสท้องถิ่นมีคุณสมบัติที่สำคัญจำนวนหนึ่งและมีความแตกต่างจากคลาสภายใน กุญแจสำคัญอยู่ในการประกาศ: คลาสท้องถิ่นถูกประกาศในบล็อคโค้ดเท่านั้น บ่อยที่สุด - ภายในวิธีการบางอย่างของคลาสภายนอก ตัวอย่างเช่น อาจมีลักษณะดังนี้:
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) นั่นคือสิ่งที่พวกเขาเป็น ชั้นเรียนท้องถิ่น! อย่างที่คุณเห็น พวกมันมีความแตกต่างจากคลาสภายในมากมาย เรายังต้องเจาะลึกคุณสมบัติของเวอร์ชันภาษาเพื่อทำความเข้าใจวิธีการทำงาน :) ในการบรรยายครั้งถัดไป เราจะพูดถึงคลาสภายในที่ไม่ระบุชื่อ - กลุ่มสุดท้ายของคลาสที่ซ้อนกัน ขอให้โชคดีกับการเรียนของคุณ! :)
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION