CodeGym /จาวาบล็อก /สุ่ม /คลาสภายในในวิธีการท้องถิ่น
John Squirrels
ระดับ
San Francisco

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

เผยแพร่ในกลุ่ม
สวัสดี! เรามาพูดถึงคลาสซ้อนประเภทอื่นกัน ฉันกำลังพูดถึงคลาสท้องถิ่น (เมธอด-คลาสภายในท้องถิ่น) ก่อนดำดิ่ง เราต้องจำตำแหน่งของพวกมันในโครงสร้างของคลาสที่ซ้อนกันเสียก่อน จากแผนภาพของเรา เราจะเห็นว่าคลาสท้องถิ่นเป็น สายคลาสภายในในวิธีท้องถิ่น - 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;
           }
       }

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