JavaRush /Blog Java /Random-VI /Các lớp bên trong trong phương thức cục bộ

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

Xuất bản trong nhóm
Xin chào! Hãy nói về một loại lớp lồng nhau khác. Cụ thể là về các lớp cục bộ (Phương thức các lớp bên trong cục bộ). Điều đầu tiên bạn cần nhớ trước khi học là 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 trong phương thức cục bộ - 2Dựa trên sơ đồ của chúng tôi, chúng tôi có thể hiểu rằng các lớp cục bộ là một kiểu con của các lớp nội bộ, mà chúng tôi đã nói chi tiết trong mộ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à khác biệt so với các lớp nội bộ. Chìa khóa nằm ở phần 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 nhất - 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;
           }
       }

       //...code валидации номера
   }
}
QUAN TRỌNG!Mã này sẽ không biên dịch khi dán vào IDEA nếu bạn đã cài đặt Java 7. Chúng ta sẽ nói về lý do của việc này ở cuối bài giảng. Nói một cách ngắn gọn, công việc của các lớp địa phương phụ thuộc rất nhiều vào phiên bản ngôn ngữ. Nếu mã này không được 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 một từ finalvào tham số phương thức để nó trông như thế này: validatePhoneNumber(final String number). Sau này mọi thứ sẽ hoạt động. Đây là một chương trình nhỏ - trình xác thực 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 lớp cục bộ của mình PhoneNumber. Bạn có thể có một câu hỏi hợp lý: tại sao? Tại sao phả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 lớp bên trong thông thường? Thật vậy, người ta có thể làm điều này: biến lớp PhoneNumberthành nội bộ. Một điều nữa là quyết định 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 trong bài giảng về các lớp bên trong:
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!");
       }
   }
}
Trong đó, chúng tôi đã tạo ra HandleBar(tay lái) một lớp bên trong của xe đạp. Có gì khác biệt? Trước hết, trong việc sử dụng lớp. Lớp HandleBartrong ví dụ thứ hai là một thực thể phức tạp hơn so với PhoneNumberlớp đầu tiên. Đầu tiên, y HandleBarcó các phương thức công khai rightleft(không phải là setter và getter). Thứ hai, chúng tôi không thể dự đoán trước nơi Bicyclechúng tôi có thể cần nó và lớp bên ngoài của nó - đây có thể là hàng tá vị trí và phương thức khác nhau, ngay cả trong cùng một chương trình. Nhưng với một lớp học, PhoneNumbermọ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 chức năng - kiểm tra xem số đó có phải là số điện thoại hay không. Trong hầu hết các trường hợp, chương trình của chúng tôi PhoneNumberValidatorthậm chí sẽ không phải là một chương trình độc lập mà chỉ đơn giản là một phần trong logic ủy quyền cho chương trình chính. Ví dụ: trên nhiều trang web khác nhau, khi đăng ký, bạn thường được yêu cầu nhập số điện thoại. Và nếu bạn gõ một số điều vô nghĩa thay vì số, trang web sẽ hiển thị lỗi: “Đây không phải là số điện thoại!” Để vận hành một trang web như vậy (hay đúng hơn là cơ chế ủy quyền người dùng), các nhà phát triển của nó có thể đưa một mã tương tự của chúng tôi vào mã PhoneNumberValidator. 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 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 đúng như vậy thì sẽ không có gì thay đổi: chỉ có một phương pháp thực hiện công việc của nó - chỉ vậy thôi. Trong trường hợp này, vì tất cả logic công việc được thu thập trong một phương thức, nên việc đóng gói một lớp bổ sung ở đó sẽ thuận tiện và chính xác hơn nhiều. Nó không có phương thức riêng nào ngoài getter và setter. Về cơ bản chúng tôi chỉ cần dữ liệu hàm tạo từ nó. Nó không được sử dụng trong các phương pháp khác. Vì vậy, không có lý do gì để mở rộng thông tin về nó ngoài phương pháp duy nhất mà nó được sử dụng. Chúng tôi đã đưa ra một ví dụ về việc khai báo một lớp cục bộ trong một phương thức, nhưng đây không phải là khả năng 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) {


       //...code валидации номера
   }
}
Hoặc thậm chí trong một vòng lặp 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 валидации номера
   }
}
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 vẫn sẽ diễn ra bên trong phương thức. Vì vậy, chúng tôi đã giải quyết thông báo, chúng tôi cũng nói về “triết học” :) Các lớp địa phương có những đặc điểm và sự khác biệt nào so với các lớp nội bộ? Một đối tượng 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 chúng ta cần một phương thức generatePhoneNumber()tạo ra một số điện thoại ngẫu nhiên và trả về một tệp PhoneNumber. Chúng tôi sẽ 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 trong tình huống hiện tại:
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() {

   }

}
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à tham số phương thức. Trong trường hợp bạn quên, “local” là một biến được khai báo bên trong một phương thức. String russianCountryCodeNghĩa là, nếu chúng ta tạo một biến cục bộ bên trong một phương thức cho một số mục đích của mình validatePhoneNumber()thì chúng ta có thể truy cập biến đó từ lớp cục bộ PhoneNumber. Tuy nhiên, có rất nhiều điều tinh tế ở đây phụ thuộc vào phiên bản ngôn ngữ được sử dụng trong chương trình. Khi bắt đầu bài giảng, chúng tôi đã lưu ý rằng mã trong một trong các ví dụ có thể không biên dịch được trong Java 7, bạn nhớ không? Bây giờ chúng ta hãy xem 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 trong phương thức là 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 валидации номера
}
Ở đây trình biên dịch đã đưa ra hai lỗi. Nhưng ở đây mọi thứ đều theo thứ tự:
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 валидации номера
}
Bây giờ bạn đã biết lý do tại sao mã ở đầu bài giảng không được biên dịch: một lớp cục bộ trong Java 7 chỉ có quyền truy cập vào finalcác tham số -method và finalcác biến -local. 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, lớp cục bộ không chỉ có quyền truy cập vào finalcác biến và tham số -local mà còn truy cập vào các tệp 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ị một biến ra bàn điều khiển russianCountryCode, ngay cả khi nó không phải như vậy final. Điều chính là nó không thay đổi ý nghĩa của nó. Trong ví dụ này, mọi thứ hoạt động như bình thường:
public void validatePhoneNumber(String number) {

  String russianCountryCode = "+7";

    class PhoneNumber {

       public void printRussianCountryCode() {

           //в Java 7 здесь была бы ошибка
           System.out.println(russianCountryCode);
       }

    }

   //...code валидации номера
}
Nhưng nếu chúng ta thay đổi giá trị của biến ngay sau khi khởi tạo thì mã sẽ không biên dịch được.
public void validatePhoneNumber(String number) {

  String russianCountryCode = "+7";
  russianCountryCode = "+8";

    class PhoneNumber {

       public void printRussianCountryCode() {

           //error!
           System.out.println(russianCountryCode);
       }

    }

   //...code валидации номера
}
Nhưng không phải ngẫu nhiên mà lớp cục bộ là một kiểu con của lớp nội bộ! Họ cũ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í 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 trường tĩnh vào lớp trình xác nhận của chúng tôi String phoneNumberRegex:
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 biến tĩnh này. Phương thức này kiểm tra xem chuỗi được truyền tới 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à ký tự không phải là số từ 0 đến 9). Chúng ta có thể dễ dàng truy cập biến này từ lớp cục bộ PhoneNumber. 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 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 xác định 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: “Không thể tham chiếu một biến không tĩnh từ ngữ cảnh tĩnh”. Các lớp cục bộ không tĩnh vì chúng có quyền truy cập vào các thành viên thể hiện của khối chứa. Vì vậy, 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; các giao diện có tính chất tĩnh. Mã này sẽ 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;
           }
       }

       //...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ì lớp đó PhoneNumbercó thể triển khai nó:
public class PhoneNumberValidator {
   interface I {}

   public static void validatePhoneNumber(String number) {

       class PhoneNumber implements I{
           private String phoneNumber;

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

       //...code валидации номера
   }
}
Các lớp cục bộ không thể khai báo các bộ khởi tạo tĩnh (khối khởi tạo) hoặc giao diện. 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). Đó là những gì họ đang có, các lớp học địa phương! 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 nội bộ. Chúng ta thậm chí còn phải đi sâu vào các tính năng của phiên bản ngôn ngữ để hiểu cách chúng hoạt động :) Trong bài giảng 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 với 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