Внутренние классы

Содержание

1 Введение
2 Выполняемые функции отдельных классов
3 Класс JPasswordField

Введение

В основе языка программирования Java лежит понятие классов. Все производимые программой операции должны быть заключены в классы. Классы можно определять несколькими возможными способами. Обычно, на самом верхнем уровне файла стоят определения классов:

class X {

}

Однако, это не единственный способ. Классы могут определяться также внутри других классов:

class X {
 
class Y {
  }
}

Класс, задаваемый внутри другого класса, принято называть внутренним. Существует четыре различных типа внутренних классов: класс верхнего уровня, член класса, локальный и анонимный классы.

Определение классов верхнего уровня почти ничем не отличается от определения обычных, внешних классов. Единственное различие состоит в том, что объявление данного класса находится в объявлении другого, поэтому перед ключевым словом class необходимо поставить ключевое слово static:

class OuterClass {
 
static class InnerClass {
  }
}

Во время компиляции класса X генерируется два файла .class, один – для InnerClass и другой – для OuterClass.

При этом возникает вопрос: как правильно ссылаться на внутренние классы? Из-за того, что внутренние классы определены внутри внешних, недостаточно просто использовать имя внутреннего класса (это возможно только, если обращение происходит из самого внешнего класса). Для создания ссылок на внутренние классы верхнего уровня требуется добавить префикс, указывающий на имя их внешнего класса. Например, ссылкой на InnerClass в предыдущем примере будет строка OuterClass.InnerClass.

Зачем вообще нужно подобное вложение классов друг в друга? Размещение класса верхнего уровня (или интерфейса) в другом классе дает наглядное представление о зависимости внешнего и внутреннего класса.

Например, интерфейс java.util.Map обеспечивает структуру данных для пар ключ/значение. У данного интерфейса есть внутренний интерфейс Entry, представляющий каждую запись пары ключ/значение на map. При определении Entry внутри интерфейса Map, он автоматически становится зависимым от родительского интерфейса. В результате, пользователи интерфейса Map получают ясное представление о роли Entry, кроме того, уменьшается вероятность возникновения конфликта имен для других классов с именем Entry общей структуры данных.

Определения для классов и интерфейсов верхнего уровня могут налагать на доступ определенные ограничения. Как и при работе с методами и переменными, здесь можно использовать любые модификаторы доступа: private, protected, public, а также безымянный доступ (по-умолчанию). Для внутренних классов данные модификаторы используются так же, как и для методов и переменных.

Второй формой внутреннего класса является член класса. При его создании не используется ключевое слово static. Различие между членом класса и классом верхнего уровня состоит в том, что в случае использования члена класса у вас имеется доступ к соответствующим полям и методам включающего класса. То есть каждый экземпляр внутреннего класса связан с одним и только одним экземпляром внешнего класса.

class OuterClass {
 
class InnerClass {
  }
}

Для получения доступа из внутреннего класса к экземпляру его внешнего класса необходимо в ссылке указать имя класса и ключевое слово this, поставив между ними точку (например, OuterClass.this). Ключевое слово this обеспечивает доступ к потенциально спрятанным методам и полям, в которых внутренние и внешние классы используют метод или переменную с одинаковыми именами.

Например, в следующем определении класса и у внешнего и у внутреннего классов присутствует переменная count. Для получения доступа к переменной внешнего класса, необходимо в ссылке на переменную перед ее именем приписать префикс this и имя внешнего класса.

class OuterClass {
 
int count = 0;

 
class InnerClass {
   
int count = 10000;

   
public void display() {
     
System.out.println("Outer: " + OuterClass.this.count);
      System.out.println
("Inner: " + count);
   
}
  }
}

Классы третьего типа определяются не на верхнем уровне внешнего класса, а внутри его методов. Их принято называть локальными классами, по аналогии с локальными переменными. Преимущество использования подобных классов заключается в том, что определять их можно непосредственно в области применения, кроме того, упрощается процесс ограничения доступа. К классу, определенному внутри метода нельзя получить доступ вне этого метода.

Не вызывает ли это у вас противоречий? Если класс не доступен, кроме как в самом методе, зачем вообще нужно определять класс в самом начале. Чаще всего это необходимо для тех случаев, когда локальный класс определяет реализацию интерфейса.

Далее представлен пример метода, содержащего несколько локальных внутренних классов:

public ActionListener getListener(int type) {
  
class TypeOneListener implements ActionListener {
    
public void actionPerformed(ActionEvent e) {
      
System.out.println("Got One");
    
}
   }
  
class TypeTwoListener implements ActionListener {
    
public void actionPerformed(ActionEvent e) {
      
System.out.println("Got Two");
    
}
   }

  
ActionListener returnValue = null;
  
switch(type) {
    
case 1:
       returnValue =
new TypeOneListener();
      
break;
    
case 2:
       returnValue =
new TypeTwoListener();
      
break;
  
}
  
return returnValue;
}

При использовании локальных внутренних классов все локальные переменные метода, к которым производится обращение из внутреннего класса, должны быть объявлены как final.

И, наконец, последний тип внутренних классов, которые называют анонимными. В примере локальных классов были определены классы TypeOneListener and TypeTwoListener. Тем не менее, их можно было оставить и без названий, так как вне определения внешнего класса имена данных классов становятся недоступными. Таким образом, локальные классы, если они расположены в области их применения, можно оставлять и без имен:

returnValue = new ActionListener() {
    
public void actionPerformed(ActionEvent e) {
      
System.out.println("Got Three");
    
}
}
;

В этом коде как бы говорится: "new InterfaceName()", что означает: «вызвать конструктор для интерфейса». Здесь не присутствует сам интерфейс, а только его реализация. Представьте себе безымянный класс, расширяющий Object и реализующий имя интерфейса. Так как у класса нет имени, вызвать конструктор невозможно. Вам остается только реализовывать каждый метод интерфейса в определении. С другой стороны, реализация будет неправильной, и код нельзя будет компилировать.

Обратите внимание на точку с запятой в конце. Так как определение внутреннего класса является частью приписанного выражения, точка с запятой требуется для его завершения.

Использование анонимных внутренних классов очень распространено в IDE утилитах. Такие инструментальные средства, как JBuilder, генерируют код, связывающий коды обработки событий компонентов. Когда вы создаете код вручную, внимательно проверяйте все круглые и фигурные скобки.

Проверьте свои знания о внутренних классах, полученные в данной статье, при помощи он-лайн теста.

Выполняемые функции отдельных классов

В предыдущем разделе мы рассказали вам о том, как правильно использовать внутренние классы для обеспечения выполнения функций компонента внутри того же класса. Однако в некоторых случаях вам может потребоваться использовать данные выполняемые функции совершенно в другом классе. Такая необходимость может возникнуть при большом количестве кода или для хранения UI отдельно от выполняемых функций (для обеспечения поиска неисправностей, а также модульности).

Процесс обеспечения выполняемых функций компонентов в отдельных классах очень прост и осуществляется в четыре этапа:

  1. При регистрировании компонента в качестве слушателя, создайте экземпляр класса, содержащего код выполняемых функций, при помощи ключевого слова new.

    Например:

    save.addActionListener(new Handler1());
  2. Удостоверьтесь в том, что класс, обеспечивающий выполнение функций, реализует нужный интерфейс.

    Такой как:

    public class Handler1 implements ActionListene
  3. Класс, обеспечивающий выполняемые функции должен быть объявлен как public, или являться частью подобного пакета.

    public class Handler1 implements ActionListener
  4. Реализуйте требуемые методы для данного интерфейса.

    // actionPerformed должен быть реализован для
    // интерфейса ActionListener.
    public void actionPerformed (ActionEvent ae)

В следующем приложении GUI-код, создающий фрейм и две кнопки, находится в классе TestButton. Выполняемые функции этих кнопок расположены в двух отдельных классах: Handler1 and Handler2.

Загрузите три данных класса и скомпилируйте внешний для них класс TestButton. В полученном процессе будут компилироваться все три класса.

При нажатии на одну из этих кнопок текст будет выводиться в консоль.

При запуске приложение должно выглядеть примерно так:

Класс JPasswordField

Компонент JPasswordField можно назвать облегченной версией текстового поля. Символы в таком поле не отображаются, а заменяются знаком *. При желании, звездочку можно заменить на любой другой символ.

Так же как и у текстового поля, у компонента JPasswordField имеется в распоряжении несколько конструкторов:

  • JPasswordField() – создает поле JPasswordField нулевой ширины, установив в нем первоначальное значение текстовой строки равным null.

  • JPasswordField(Document doc, String txt, int columns) – создает новое поле JPasswordField, использующее заданную модель хранения текста и ширину.

  • JPasswordField(int columns) – создает новое пустое поле JPasswordField с заданной шириной.

  • JPasswordField(String text) – создает новое поле JPasswordField, инициализированное с помощью текста в строке.

  • JPasswordField(String text, int columns) – создает новое поле JPasswordField, инициализированное с помощью текста и значения ширины.

При вводе текста в строке будут отображаться символы *. Заменить их на какие-либо другие можно при помощи метода setEchoChar(char c). Например:

JPasswordField jpf = new JPasswordField(15);
jpf.setEchoChar
('#');

В данном фрагменте создается объект JPasswordField шириной в 15 колонок. При чем, каждому набранному пользователем символу будет соответствовать не звездочка (*), а знак диеза (#).

Извлечь набранный в поле текст можно с помощью метода getPassword. В следующем примере метод getPassword вызывается в новой строке и присваивает полученное значение пароля переменной entrdpwd.

String entrdpwd = new String(jpf.getPassword());

Приложение PasswordTest создает фрейм с текстовым полем PasswordField. Введите пароль и в желтом текстовом поле внизу фрейма вы увидите надпись, указывающая на его правильность или ошибочность. Приложение допускает только три попытки ввода пароля, при исчерпании лимита оно закрывается.

После компиляции и запуска приложения вы увидите результат, представленный на рисунке:

Теги: java