Често се случва, когато пишем класове, да осъзнаем, че ако комбинираме определени член данни, то те съвсем спокойно могат да се обособят в отделен клас, който да репрезентира тези данни. Или пък по време на писане се сещаме, че ни трябва нов клас, който обаче е много здраво обвързан с вече съществуващ клас. Друг сценарий е, когато ни трябва някакъв helper клас, който да спомага за работата на някой наш основен клас. Езикът Java ни дава възможността да декларираме класове в рамките на даден клас.
Вложени класове ( Nested classes)
Всеки клас, деклариран в рамките на даден клас се нарича вложен. Вложените класове биват два вида – статични и нестатични.
class OuterClass { ... static class StaticNestedClass { ... } class InnerClass { ... } }
Всеки вложен клас е член на класа, в който е деклариран. Ако е нестатичен, то той има достъп до всички останали член променливи и методи на основния клас, но само ако са с модификатор за достъп, различен от private. Статичните вложени член класове, обаче, нямат достъп до никои от даннните или методите на външния клас, независимо от модификатора им на достъп. Обърнете внимание, че вложените класове могат да бъдат дефинирани с всеки модификатор за достъп, за разлика от нормалните, които могат да бъдат с модификатор public или default. Тоест, вложеният клас може да има същите модификатори, които могат да имат и член променливите на класа, тъй като той се явява нещо като „член клас“ на класа, ако е нестатичен, или „статичен член“ на класа, в обратния случай.
public class Test { private class FirstExample { } protected static class SecondExample { } }
Ще разгледаме последователно различните вариации на вложените класове:
Вложени нестатични член класове (Non- static Inner classes):
Всеки вложен член клас може да бъде деклариран с всички модификатори за достъп, които са валидни за член променливите на класа. За да създадете инстанция на вложен член клас (само ако е с подходящ модификатор), ще трябва да го направите през съответния му външен клас.
public class Mobile { public int numberOfDoors; public String brand; public String model; public Mobile(int numberOfDoors, String brand, String model) { this.numberOfDoors = numberOfDoors; this.brand = brand; this.model = model; } public class MobileEngine { private float power; private String brand; public MobileEngine(float power, String brand) { this.power = power; this.brand = brand; } public float getPower() { return power; } public void setPower(float power) { this.power = power; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } } } public static void main(String[] args) { Mobile mob = new Mobile(5, "Audi", "A1"); Mobile.MobileEngine engine = mob.new MobileEngine(2500, "Audi"); }
Обърнете внимание как се инстанцира този клас. Трябва да имаме създаден обект от тип външния клас Mobile и после през него да създадем обект от вложения клас чрез следния синтаксис:
Mobile.MobileEngine engine = mob.new MobileEngine(2500, "Audi");
Оставили сме вложения клас с модификатор public, тъй като така ще можем да създаваме негови инстанции и навън. Ако го направим private, което по-често се случва, ще можем да си го ползваме само в рамките на класа. В този случай инстанция на друго място е невъзможно да бъде направена. Възможността да крием или не вложения клас допълнително подсилва идеята за капсулация на данните.
Статични вложени класове(Static inner classes):
Статичния вложен клас не се различава особено от нормалния клас. Той може да достъпва статични методи и статични променливи на външния клас така, сякаш и самият той е външен клас. Държи се като типичен статичен метод – не е нужно да създаваме инстанция на външния клас, ако искаме да го инстанцираме или да извикаме някой негов метод, общовалиден е за всички инстанции на външния клас.
Нека дадем пример:
public abstract class Question { private String text; public Question(String text) { this.text = text; } public void askQuestion() { System.out.println(this.text); } public abstract boolean checkAnswer(Object userAnswer); } public class OpenQuestion extends Question { private String answer; public OpenQuestion(String text, String answer) { super(text); this.answer = answer; } @Override public boolean checkAnswer(Object userAnswer) { if (userAnswer instanceof String) { String userAnsw = (String) userAnswer; if (this.answer.equals(userAnsw)) return true; } return false; } } public class IntegerQuestion extends Question { private Integer answer; public IntegerQuestion(String text, Integer answer) { super(text); this.answer = answer; } @Override public boolean checkAnswer(Object userAnswer) { if (userAnswer instanceof Integer) { Integer userAnsw = (Integer) userAnswer; if (this.answer.equals(userAnsw)) return true; } return false; } } public class Example { public enum Level { MEDIUM, MASTER } public Example(Level l) { if (l == Level.MEDIUM) { System.out .println("The test will be loaded with default questions for MEDIUM level."); Example.FirstExample example = new Example.FirstExample(); example.addIntegerQuestion(new IntegerQuestion("Kolko e 2x2?", 4)); example.addIntegerQuestion(new IntegerQuestion("Kolko koli imash?", 3)); example.displayFirstExample(); } else if (l == Level.MASTER) { System.out .println("The test will be loaded with default question for MASTER level."); Example.SecondExample example = new Example.SecondExample(); example.addQuestion(new OpenQuestion("Kak se kazva kotkata vi?", "Tinna")); example.addQuestion(new IntegerQuestion( "Kolko e 2 na stepen 3-ta?", 8)); example.displaySecondExample(); } else { System.out.println("No questions."); } } protected static class FirstExample { private List listOfquestions; private FirstExample() { this.listOfquestions = new ArrayList(); } public FirstExample(List listOfquestions) { this.listOfquestions = listOfquestions; } public void displayFirstExample() { if (this.listOfquestions == null || this.listOfquestions.size() == 0) { System.out.println("No question for First Example."); } else { for (IntegerQuestion i : listOfquestions) { i.askQuestion(); } } } private void addIntegerQuestion(IntegerQuestion quest) { this.listOfquestions.add(quest); } } protected static class SecondExample { private List listOfquestions; private SecondExample() { this.listOfquestions = new ArrayList(); } public SecondExample(List listOfquestions) { this.listOfquestions = listOfquestions; } public void displaySecondExample() { if (this.listOfquestions == null || this.listOfquestions.size() == 0) { System.out.println("No question for Second Example."); } else { for (Question i : listOfquestions) { i.askQuestion(); } } } private void addQuestion(Question quest) { this.listOfquestions.add(quest); } } } public class Test { public static void main(String[] args) { List list = new ArrayList(); list.add(new IntegerQuestion("Kolko e visok Ivan?", 185)); list.add(new IntegerQuestion("Kolko teji Petq?", 60)); Example.FirstExample ex = new Example.FirstExample(list); ex.displayFirstExample(); } }
Имаме статичните класове FirstExample и SecondExample, които са вложени в класа Example. Обърнете внимание, че можем да ги достъпваме без проблем , но през външния клас Example.
Example.FirstExample ex = new Example.FirstExample(list);
Локални класове(Local classes):
Нека да припомним какво представлява програмният блок – това е код, заключен между две къдрави скоби –
{ //code block }
Ако дефинираме дадена променлива в рамките на програмен блок, то тя се нарича локална и обхватът и е само в рамките на блока. Същото нещо се отнася и за локалните класове – това са класове, които са дефинирани в програмен блок, обикновено в тялото на даден метод. Подобно на локалните променливи и те имат ограничен обхват – важат само в рамките на блока.
public class OuterClass { public Person pers; public OuterClass(Person pers) { this.pers = pers; } public void printSomethingForPerson() { class PersonalInformationPrinter { private Person p; public PersonalInformationPrinter(Person p) { this.p = p; } public void printPersonalInformation() { if (this.p != null) { System.out.println("Personal information:"); System.out.println("Name: " + p.getName().getFirstName()); System.out.println("Years: " + p.getYears()); } } } PersonalInformationPrinter p = new PersonalInformationPrinter(pers); p.printPersonalInformation(); } public static void main(String[] args){ OuterClass out = new OuterClass(new Person(new Name("Ivan", "Ivanov", "Petrov"), 25)); out.printSomethingForPerson(); } }
В случая създадохме един клас OuterClass, в който имаме метод printSomethingForPerson. В него вградихме локален клас PersonalInformationPrinter, който си има метод за принтиране на основна информация за човек. Локалният клас може да достъпва член променливи и методи на външния клас, също както и обикновения inner нестатичен клас.
Локалните класове могат да достъпват и локални променливи на метода, в който са декларирани, а също и параметри на метода, стига да са декларирани като final или да са effectually final – това означава, да не са декларирани изрично като final, но да не се променлят в рамките на метода, след като веднъж бъдат инициализирани.
Ако локален клас се декларира в рамките на статичен метод, то тогава важат всички ограничения, които има статични метод за достъп до член данни и метода на класа – това ознчава, че локалният клас ще има достъп само до статични данни и методи на класа. Също така локалният клас не може да съдържа декларация на статични член данни, освен ако самият той не е деклариран в рамките на статичен метод, но пък може да съдържа константи – final static.
Анонимни класове(Anonymous classes):
Анонмни класове в Java, са такива вложени класове, които си нямат име. Обикновено се дефинират като подкласове на дадени класове – просто предефинират определен метод на основния клас, за да се използват в специфична ситуция, или пък за временна имплементация на интерфейс.
public interface Printable { public abstract void printSomeInfo(); } public static void main(String[] args) { Printable printInstance = new Printable() { @Override public void printSomeInfo() { System.out.println("something"); } }; printInstance.printSomeInfo(); }
Резултат: something
В този случай все едно сме създали някакъв клас, който имплементира интерфейса Printable и сме предефинирали метода му printSomeInfo() и после сме създали инстанция от тази имплементацията(чието име не знаем). Предимството е, че ако ни трябва на това място лесна и бърза имплементация на метода printSomeInfo, не е нужно да създаваме истински клас, да предефинираме метода му, после да създаваме инстанция от този клас и да извикаме метода. Може никога повече да не се наложи да извикаме този метод с точно тази имплементация- затова го правим на място и създаваме анонимна имплементация на интерфейса Printable и я ползваме.
Нека имаме клас
public class CustomerInformation { public void printCustomerInformation(Person p) { System.out.println("Customer information: "); System.out.println("Name: " + p.getName().getFirstName()); System.out.println("Family Name: " + p.getName().getLastName()); System.out.println("Years: " + p.getYears()); } }
Можем съвсем спокойно да създаваме обекти от тип CustomerInformation и да викаме метода за принтиране на информацията. Обаче, ако се наложи в някой особен случай да не желаем да иползваме тази имплементация на метода printCustomerInformation, а някаква друга – например да принтира само информация за имената на човек, то ще трябва да предефинираме метода в някакъв друг клас, който да наследи текущия, или пък да overload-нем метода в рамките на същия клас. Да, обаче този частен случай ни трябва еднократно- тогава можем да създадем един анонимен наследник, в който да си предефинираме метода така, както желаем в момента. Например:
public static void main(String[] args) { CustomerInformation inst = new CustomerInformation() { public void printCustomerInformation(Person p) { System.out.println("Nothing to show, because I am anonymous class with overriten method!"); } }; inst.printCustomerInformation(new Person(new Name("Ivan", "Ivanov", "Petrov"), 24)); }
Анонимният клас може да достъпва проненливите на основния си клас, както и статичните му променливи или тези, които са final или effectually final. Може да добавя свои член променливи или константи, сякаш е истински налследник. Не може, обаче, да си декларира констуктор, тъй като от създената анонимна имплементация, можем да създаден един единствен обект – текущия. Ако все пак се налага дадена променлива да бъде инициализирана при създаване на инстанцията на анонимния клас, то можем да го направим в рамките на статичен инициализатор- static block.
Нека обобщим – предимствата на вложените класове са, че можем да групираме близки по предназначение класове в рамките на един – обобщаващ ги. Можем да напраивм същото нещо и ако ги сложим в пакети, но влагайки един клас в друг ще постигнем по-добър интегритет и по-висока функционална кохезия (Functional cohesion)на кода. Така например, често използваните вътрешни класове са скрити за вънпшния свят, чрез модификатор private, тъй като те едиствено служат за вътрешно ползване от даден клас.
Явор Томов, Даниел Джолев
Много полезна статия с нагледна информация. Помогна ми много, благодаря!