CodeGym /Blog Java /Ngẫu nhiên /Các lớp bên trong một phương thức cục bộ

Các lớp bên trong một phương thức cục bộ

Xuất bản trong nhóm
CHÀO! Hãy nói về một loại lớp lồng nhau khác. Tôi đang nói về các lớp cục bộ (method-local inner classes). Trước khi đi sâu vào, trước tiên chúng ta phải nhớ vị trí của chúng trong cấu trúc của các lớp lồng nhau. Các lớp bên trong một phương thức cục bộ - 2Từ sơ đồ của chúng ta, chúng ta có thể thấy rằng các lớp cục bộ là một phân loài của các lớp bên trong, mà chúng ta đã nói chi tiết trong các tài liệu trước . Tuy nhiên, các lớp cục bộ có một số tính năng quan trọng và sự khác biệt so với các lớp bên trong thông thường. Điều chính là trong khai báo của họ: Một lớp cục bộ chỉ được khai báo trong một khối mã. Thông thường, phần khai báo này nằm bên trong một số phương thức của lớp bên ngoài. Ví dụ, nó có thể trông như thế này:

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
   }
}
QUAN TRỌNG!Nếu bạn đã cài đặt Java 7, mã này sẽ không được biên dịch khi dán vào IDEA. Chúng ta sẽ nói về những lý do cho điều này ở phần cuối của bài học. Nói tóm lại, cách các lớp cục bộ hoạt động phụ thuộc nhiều vào phiên bản của ngôn ngữ. Nếu mã này không biên dịch cho bạn, bạn có thể chuyển phiên bản ngôn ngữ trong IDEA sang Java 8 hoặc thêm từ này finalvào tham số phương thức để nó trông giống như sau: validatePhoneNumber(final String number). Sau đó, mọi thứ sẽ hoạt động. Đây là một chương trình nhỏ xác nhận số điện thoại. Phương thức của nó validatePhoneNumber()lấy một chuỗi làm đầu vào và xác định xem đó có phải là số điện thoại hay không. Và bên trong phương thức này, chúng ta đã khai báo PhoneNumberlớp cục bộ của mình. Bạn có thể hợp lý hỏi tại sao. Chính xác thì tại sao chúng ta lại khai báo một lớp bên trong một phương thức? Tại sao không sử dụng một lớp bên trong bình thường? Đúng, chúng ta có thể đã thực hiệnPhoneNumberlớp một lớp bên trong. Nhưng giải pháp cuối cùng phụ thuộc vào cấu trúc và mục đích chương trình của bạn. Hãy nhớ lại ví dụ của chúng ta từ một bài học về các lớp bên trong:

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!");
       }
   }
}
Trong đó, chúng tôi đã tạo ra HandleBarmột lớp bên trong của chiếc xe đạp. Có gì khác biệt? Trước hết, cách sử dụng lớp là khác nhau. Lớp HandleBartrong ví dụ thứ hai là một thực thể phức tạp hơn so với PhoneNumberlớp trong ví dụ đầu tiên. Đầu tiên, HandleBarcó công khai rightleftphương thức (đây không phải là setters/getters). Thứ hai, không thể dự đoán trước nơi chúng ta có thể cần đến nó và Bicyclelớp bên ngoài của nó. Có thể có hàng chục địa điểm và phương pháp khác nhau, ngay cả trong một chương trình. Nhưng với PhoneNumberlớp học, mọi thứ đơn giản hơn nhiều. Chương trình của chúng tôi rất đơn giản. Nó chỉ có một mục đích: kiểm tra xem một số có phải là số điện thoại hợp lệ hay không. Trong hầu hết các trường hợp, chúng tôiPhoneNumberValidatorthậm chí sẽ không phải là một chương trình độc lập, mà là một phần của logic ủy quyền cho một chương trình lớn hơn. Ví dụ: các trang web khác nhau thường yêu cầu số điện thoại khi người dùng đăng ký. Nếu bạn nhập một số vô nghĩa thay vì số, trang web sẽ báo lỗi: "Đây không phải là số điện thoại!" Các nhà phát triển của một trang web như vậy (hay đúng hơn là cơ chế ủy quyền người dùng của nó) có thể bao gồm một cái gì đó tương tự như của chúng tôiPhoneNumberValidatortrong mã của họ. Nói cách khác, chúng ta có một lớp bên ngoài với một phương thức, phương thức này sẽ được sử dụng ở một nơi trong chương trình và không ở nơi nào khác. Và nếu nó được sử dụng, thì sẽ không có gì thay đổi trong đó: một phương thức thực hiện công việc của nó - và chỉ có thế. Trong trường hợp này, bởi vì tất cả logic được tập hợp vào một phương thức, sẽ thuận tiện và phù hợp hơn nhiều khi đóng gói một lớp bổ sung ở đó. Nó không có phương thức riêng ngoại trừ getter và setter. Trên thực tế, chúng ta chỉ cần dữ liệu từ hàm tạo. Nó không liên quan đến các phương pháp khác. Theo đó, không có lý do gì để lấy thông tin về nó bên ngoài phương pháp duy nhất mà nó được sử dụng. Chúng tôi cũng đã đưa ra một ví dụ trong đó một lớp cục bộ được khai báo trong một phương thức, nhưng đây không phải là lựa chọn duy nhất. Nó có thể được khai báo đơn giản trong một khối mã:

public class PhoneNumberValidator {
  
   {
       class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

   }

   public void validatePhoneNumber(String phoneNumber) {

      
       // ...number validation code
   }
}
Hoặc thậm chí trong forvòng lặp!

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
   }
}
Nhưng những trường hợp như vậy là cực kỳ hiếm. Trong hầu hết các trường hợp, việc khai báo sẽ diễn ra bên trong phương thức. Vì vậy, chúng tôi đã tìm ra các khai báo và chúng tôi cũng đã nói về "triết lý" :) Các lớp cục bộ có những tính năng và điểm khác biệt nào so với các lớp bên trong? Một đối tượng của lớp cục bộ không thể được tạo bên ngoài phương thức hoặc khối mà nó được khai báo. Hãy tưởng tượng rằng chúng ta cần một generatePhoneNumber()phương thức sẽ tạo ra một số điện thoại ngẫu nhiên và trả về một PhoneNumberđối tượng. Trong tình huống hiện tại của chúng tôi, chúng tôi không thể tạo một phương thức như vậy trong lớp trình xác thực của mình:

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() {

   }

}
Một tính năng quan trọng khác của các lớp cục bộ là khả năng truy cập các biến cục bộ và các tham số của phương thức. Trong trường hợp bạn quên, một biến được khai báo bên trong một phương thức được gọi là biến "cục bộ". Nghĩa là, nếu chúng ta tạo một String usCountryCodebiến cục bộ bên trong validatePhoneNumber()phương thức vì lý do nào đó, chúng ta có thể truy cập nó từ PhoneNumberlớp cục bộ. Tuy nhiên, có rất nhiều điều tinh tế phụ thuộc vào phiên bản ngôn ngữ được sử dụng trong chương trình. Ở phần đầu của bài học, chúng ta đã lưu ý rằng mã của một trong các ví dụ có thể không được biên dịch trong Java 7, nhớ không? Bây giờ hãy xem xét lý do cho điều này :) Trong Java 7, một lớp cục bộ chỉ có thể truy cập một biến cục bộ hoặc tham số phương thức nếu chúng được khai báo như finaltrong phương thức:

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
}
Ở đây trình biên dịch tạo ra hai lỗi. Và mọi thứ đều theo thứ tự ở đây:

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
}
Bây giờ bạn đã biết tại sao mã từ đầu bài học không biên dịch được: trong Java 7, một lớp cục bộ chỉ có quyền truy cập vào finalcác tham số của phương thức và finalcác biến cục bộ. Trong Java 8, hành vi của các lớp cục bộ đã thay đổi. Trong phiên bản ngôn ngữ này, một lớp cục bộ không chỉ có quyền truy cập vào finalcác biến và tham số cục bộ mà còn cả các tham số và biến cục bộ effective-final. Effective-finallà một biến có giá trị không thay đổi kể từ khi khởi tạo. Ví dụ, trong Java 8, chúng ta có thể dễ dàng hiển thị usCountryCodebiến trên bàn điều khiển, ngay cả khi nó không phải là final. Điều quan trọng là giá trị của nó không thay đổi. Trong ví dụ sau, mọi thứ hoạt động như bình thường:

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
}
Nhưng nếu chúng ta thay đổi giá trị của biến ngay sau khi khởi tạo, mã sẽ không biên dịch được.

public void validatePhoneNumber(String number) {

  String usCountryCode = "+1";
  usCountryCode = "+8";

    class PhoneNumber {

       public void printUsCountryCode() {

           // Error!
           System.out.println(usCountryCode);
       }

    }

   // ...number validation code
}
Không có gì ngạc nhiên khi một lớp cục bộ là một phân loài của khái niệm lớp bên trong! Họ cũng có những đặc điểm chung. Một lớp cục bộ có quyền truy cập vào tất cả các trường và phương thức (thậm chí là riêng tư) của lớp bên ngoài: cả tĩnh và không tĩnh. Ví dụ: hãy thêm một String phoneNumberRegextrường tĩnh vào lớp trình xác thực của chúng tôi:

public class PhoneNumberValidator {

   private static String phoneNumberRegex = "[^0-9]";

   public void validatePhoneNumber(String phoneNumber) {
       class PhoneNumber {
          
           // ......
       }
   }
}
Việc xác thực sẽ được thực hiện bằng cách sử dụng biến tĩnh này. Phương thức kiểm tra xem chuỗi đã truyền có chứa các ký tự không khớp với biểu thức chính quy " [^0-9]" hay không (nghĩa là bất kỳ ký tự nào không phải là chữ số từ 0 đến 9). Chúng ta có thể dễ dàng truy cập biến này từ PhoneNumberlớp cục bộ. Ví dụ, viết một getter:

public String getPhoneNumberRegex() {
  
   return phoneNumberRegex;
}
Các lớp cục bộ tương tự như các lớp bên trong, bởi vì chúng không thể định nghĩa hoặc khai báo bất kỳ thành viên tĩnh nào. Các lớp cục bộ trong các phương thức tĩnh chỉ có thể tham chiếu các thành viên tĩnh của lớp kèm theo. Ví dụ: nếu bạn không định nghĩa một biến (trường) của lớp kèm theo là tĩnh, thì trình biên dịch Java sẽ tạo ra lỗi: "Biến không tĩnh không thể được tham chiếu từ ngữ cảnh tĩnh." Các lớp cục bộ không tĩnh, bởi vì chúng có quyền truy cập vào các thành viên thể hiện trong khối kèm theo. Kết quả là chúng không thể chứa hầu hết các loại khai báo tĩnh. Bạn không thể khai báo một giao diện bên trong một khối: giao diện vốn đã tĩnh. Mã này không biên dịch:

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
   }
}
Nhưng nếu một giao diện được khai báo bên trong một lớp bên ngoài, thì PhoneNumberlớp đó có thể thực hiện nó:

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
   }
}
Bộ khởi tạo tĩnh (khối khởi tạo) hoặc giao diện không thể được khai báo trong các lớp cục bộ. Nhưng các lớp cục bộ có thể có các thành viên tĩnh, miễn là chúng là các biến không đổi ( static final). Và bây giờ bạn biết về các lớp học địa phương, folks! Như bạn có thể thấy, chúng có nhiều điểm khác biệt so với các lớp bên trong thông thường. Chúng ta thậm chí phải đi sâu vào các tính năng của các phiên bản ngôn ngữ cụ thể để hiểu cách chúng hoạt động :) Trong bài học tiếp theo, chúng ta sẽ nói về các lớp bên trong ẩn danh — nhóm cuối cùng của các lớp lồng nhau. Chúc may mắn trong các nghiên cứu của bạn! :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION