здрасти Нека поговорим за друг вид вложени класове. Говоря за локални класове (метод-локални вътрешни класове). Преди да се потопим, първо трябва да запомним тяхното място в структурата на вложените класове.
От нашата диаграма можем да видим, че локалните класове са подвид на вътрешните класове, за които говорихме подробно в предишни материали . Въпреки това, локалните класове имат редица важни характеристики и разлики от обикновените вътрешни класове. Основното е в тяхната декларация: Локален клас се декларира само в блок code. Най-често тази декларация е вътре в няHowъв метод на външния клас. Например може да изглежда така:

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, този code няма да се компorра, когато бъде поставен в IDEA. Ще говорим за причините за това в края на урока. Накратко, How работят локалните класове зависи силно от versionта на езика. Ако този code не се компorра instead of вас, можете or да превключите езиковата version в IDEA на Java 8, or да добавите думата final
към параметъра на метода, така че да изглежда така: validatePhoneNumber(final String number)
. След това всичко ще работи. Това е малка програма, която проверява телефонни номера. Неговият validatePhoneNumber()
метод приема низ като вход и определя дали е телефонен номер. И вътре в този метод декларирахме нашия локален PhoneNumber
клас. Може разумно да попитате защо. Защо точно трябва да декларираме клас вътре в метод? Защо не използвате обикновен вътрешен клас? Вярно, можехме да направимPhoneNumber
клас вътрешен клас. Но окончателното решение зависи от структурата и преднаmeaningто на вашата програма. Нека си припомним нашия пример от урок за вътрешни класове:
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
има public right
и left
методи (това не са сетери/гетери). Второ, невъзможно е да се предвиди предварително къде може да ни потрябва той и външният му Bicycle
клас. Може да има десетки различни места и методи, дори в една програма. Но с PhoneNumber
класа всичко е много по-просто. Нашата програма е много проста. Има само една цел: да провери дали даден номер е валиден телефонен номер. В повечето случаи нашитеPhoneNumberValidator
дори няма да бъде самостоятелна програма, а по-скоро част от логиката за оторизация за по-голяма програма. Например различни уебсайтове често искат телефонен номер, когато потребителите се регистрират. Ако въведете няHowва глупост instead of цифри, уебсайтът ще отчете грешка: "Това не е телефонен номер!" Разработчиците на такъв уебсайт (or по-скоро неговият механизъм за оторизация на потребителите) могат да включат нещо подобно на нашияPhoneNumberValidator
в техния code. С други думи, имаме един външен клас с един метод, който ще се използва на едно място в програмата и никъде другаде. И ако се използва, тогава нищо няма да се промени в него: един метод върши своята работа - и това е всичко. В този случай, тъй като цялата логика е събрана в един метод, ще бъде много по-удобно и правилно да капсулирате допълнителен клас там. Той няма собствени методи освен геттер и сетер. Всъщност имаме нужда само от данни от конструктора. Не се включва в други методи. Съответно, няма причина да се взема информация за него извън единствения метод, където се използва. Дадохме и пример, в който локален клас е деклариран в метод, но това не е единствената опция. Може да се декларира просто в codeов блок:
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
}
}
Но такива случаи са изключително редки. В повечето случаи декларацията ще се случи вътре в метода. И така, измислихме декларации и също говорихме за "философията" :) Какви допълнителни функции и разлики имат локалните класове в сравнение с вътрешните класове? Обект от локален клас не може да бъде създаден извън метода or блока, в който е деклариран. Представете си, че имаме нужда от 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() {
}
}
Друга важна характеристика на локалните класове е възможността за достъп до локални променливи и параметри на метода. В случай, че сте забравor, променлива, декларирана вътре в метод, е известна като "локална" променлива. Тоест, ако създадем локална String usCountryCode
променлива вътре в validatePhoneNumber()
метода по няHowва причина, можем да получим достъп до нея от локалния PhoneNumber
клас. Има обаче много тънкости, които зависят от versionта на езика, използван в програмата. В началото на урока отбелязахме, че codeът за един от примерите може да не се компorра в Java 7, помните ли? Сега нека разгледаме причините за това :) В Java 7 локален клас може да има достъп до локална променлива or параметър на метод само ако те са декларирани като 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
}
Сега знаете защо codeът от началото на урока не се компorра: в Java 7 локалният клас има достъп само до final
параметрите на метода и final
локалните променливи. В Java 8 поведението на локалните класове се промени. В тази version на езика локалният клас има достъп не само до final
локални променливи и параметри, но и до тези, които са effective-final
. Effective-final
е променлива, чиято стойност не се е променила от инициализацията. Например в Java 8 можем лесно да покажем usCountryCode
променливата на конзолата, дори и да не е final
. Важното е стойността му да не се променя. В следния пример всичко работи Howто трябва:
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
}
Но ако променим стойността на променливата веднага след инициализацията, codeът няма да се компorра.
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;
}
Локалните класове са подобни на вътрешните класове, защото не могат да дефинират or декларират ниHowви статични членове. Локалните класове в статичните методи могат да препращат само към статични членове на обхващащия клас. Например, ако не дефинирате променлива (поле) на обхващащия клас като статична, тогава компилаторът на Java генерира грешка: „Нестатичната променлива не може да бъде реферирана от статичен контекст.“ Локалните класове не са статични, защото имат достъп до членовете на екземпляра в обграждащия блок. В резултат на това те не могат да съдържат повечето типове статични декларации. Не можете да декларирате интерфейс вътре в блок: интерфейсите по своята същност са статични. Този code не се компorра:
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
}
}
Статичните инициализатори (блокове за инициализация) or интерфейси не могат да бъдат декларирани в локални класове. Но локалните класове могат да имат статични членове, при condition че са постоянни променливи ( static final
). И сега знаете за местните класове, хора! Както можете да видите, те имат много разлики от обикновените вътрешни класове. Дори трябваше да се задълбочим в характеристиките на конкретни версии на езика, за да разберем How работят :) В следващия урок ще говорим за анонимни вътрешни класове — последната група от вложени класове. Успех в учението! :)
GO TO FULL VERSION