17. Внутренние и анонимные классы.

 

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

Внутренние классы действительно проявляются, когда Вы пытаетесь привести к базовому типу и в частности к interface. (Эффект возникновения ссылки на интерфейс от объекта, который реализует его же при попытке апкастинга к базовому классу.) Это происходит потому, что внутренний класс, реализованный на интерфейсе, может быть потом полностью, но невидимо и недоступно для всех приведен к базовому классу, что обычно называется скрытой реализацией. Все что Вы получите назад это просто ссылка на базовый класс или интерфейс.

Внутренний класс может быть создан внутри метода или даже в случайном контексте.

Если вам не нужно соединение между внутренним классом и объектом внешнего класса, тогда Вы можете сделать этот внутренний класс static. Для того, что бы понять значение static примененного к внутреннему классу, Вы должны вспомнить то, что этот объект обычного внутреннего класса неявным образом ссылается на объект создавшего его окружающего класса. А если Вы объявите его как static, то это уже не будет правдой. Static внутренний класс означает:

1. Вам не нужен объект внешнего класса для создания объекта static внутреннего класса;

2. Вы не можете получить доступ к внешнему объекту из static внутреннего класса.

Анонимный класс – класс, который не имеет своего имени. Поскольку он анонимный, то у него нет имени, которое можно передать конструктору, поэтому у него не может быть конструктора. При помощи инициализации экземпляра, Вы можете, в действительности, создать конструткор для анонимного внутреннего класса. Вы не можете перегрузить инциализатор экземпляра, поэтому у вас есть только один такой "конструктор".

 

18 Состав пакета java.util.* (Collection, List, Map и др.)

 

Категория Collection (коллекция) хранит только один элемент в каждой ячейке (имя немного вводит в заблуждение, так как любая библиотека контейнеров часто называется “collections”). Сюда включается List (список), который хранит группы элементов в указанном порядке, и Set (набор), который позволяет добавление одного элемента каждого типа. ArrayList - это тип List, а HashSet - это тип Set. Для добавление элементов в любой из Collection существует метод add( ).

Map (карта) хранит пары ключ-значение, что похоже на мини базу данных. Приведенная выше программа использует одну часть букета Map - HashMap. Если вы имеете Map, ассоциированную со штатами, и вы хотите узнать столицу Огайо, вы ищите его так, как будто вы просматриваете индексированный массив. (Карты также называются ассоциативным массивом.) Для добавления элемента в Map существует метод put( ), который принимает ключ и значение в качестве аргументов.

Можно увидеть различия в поведении разных контейнеров. List хранит объекты точно так, как они были введены, без изменения порядка или редактирования. Однако Set принял по одному экземпляру каждого объекта и использовал свой внутренний метод упорядочивания (в общем, обычно заботитесь только о том, является ли что-то или нет членом Set, а не порядок, в котором оно появится — для этого вы используете List). Map тоже принимает только по одному значению для каждого элемента, основываясь на ключе, и он также имеет свое собственное внутреннее упорядочивание и не заботится о том порядке, в котором вы вводили элементы.

“Неудобство” при использовании контейнеров Java в том, что вы теряете информацию о типе, когда помещаете объект в контейнер. Это случается потому, что программист такого контейнерного класса не имел идей о том, как указать тип того, что вы хотите поместить в контейнер, а создание контейнеров для хранения только вашего типа не допустит создания инструментов общего назначения. Так что вместо этого контейнер содержит ссылки на Object, который является корнем всех классов, так что он содержит любой тип. (Конечно, сюда не включаются примитивные типы, так как они не наследуются ни от чего.) Это лучшее решение, за исключением:

Так как информация о типе отбрасывается, когда вы помещаете ссылку на объект в контейнер, нет ограничений на тип объекта, который может быть помещен в контейнер, даже если вы предназначаете его только для хранения, скажем, котов. Кто-либо может так же легко поместить в контейнер собаку.

Так как информация о типе теряется, есть только одна вещь, которую контейнер знает, он хранит ссылки на объекты. Вы должны выполнить приведение к правильному типу перед использованием объекта.

С другой стороны, Java не позволит вам неправильно использовать объект, который вы поместили в контейнер. Если вы поместили собаку в контейнер кошек, а затем пробуете трактовать все что есть в контейнере как кошек, вы получите исключение во время выполнения, когда вытянете ссылку на собаку из контейнера кошек и попробуете ее привести к кошке.

Вот пример использование основной рабочей лошадки контейнеров -ArrayList. Для начала вы можете думать об ArrayList, как о “массиве, который автоматически растягивает себя”. Использование ArrayList достаточно простое: создание, помещение в него объектов с помощью add( ), и, позже, получение их с помощью get( ) и индекса, так же как будто вы имеете дело с массивом, но без квадратных скобок [49]. ArrayList также имеет метод size( ), который позволяет вам узнать сколько элементов было добавлено, так что вы по невнимательности не выйдете за пределы и не получите исключение.

Сначала создаются классы Cat и Dog:

//: c09:Cat.java

public class Cat {

  private int catNumber;

  Cat(int i) { catNumber = i; }

  void print() {

    System.out.println("Cat #" + catNumber);

  }

} ///:~

 

//: c09:Dog.java

public class Dog {

  private int dogNumber;

  Dog(int i) { dogNumber = i; }

  void print() {

    System.out.println("Dog #" + dogNumber);

  }

} ///:~

Кошки (Cat) и собаки (Dog) помещаются в контейнер, а затем вытягиваются оттуда:

//: c09:CatsAndDogs.java

// Пример простого контейнера.

import java.util.*;

 

public class CatsAndDogs {

  public static void main(String[] args) {

    ArrayList cats = new ArrayList();

    for(int i = 0; i < 7; i++)

      cats.add(new Cat(i));

    // Не проблема добавить к кошкам собаку:

    cats.add(new Dog(7));

    for(int i = 0; i < cats.size(); i++)

      ((Cat)cats.get(i)).print();

    // Собака обнаружится только во время выполнения

  }

} ///:~

Классы Cat и Dog отличаются — они не имеют ничего общего, за исключением того, что они оба - Object. (Если вы не можете точно сказать от чего унаследован класс, вы автоматически наследуете от Object.) Так как ArrayList содержит Object, вы можете поместить в контейнер не только объекты Cat с помощью метода add( ) контейнера ArrayList, но вы также можете добавить объекты Dog и при этом не получите ошибок времени компиляции, либо времени выполнения. Когда вы достаете то, о чем выдумаете как об объекте Cat с помощью метода get( ) контейнера ArrayList, вы получаете назад ссылку на объект, который вы должны привести к Cat. Затем вам необходимо взять в круглые скобки все выражение, чтобы навязать вычисление приведения до вызова метода print( ) для Cat, в противном случае вы получите синтаксическую ошибку. Затем, во время выполнения, когда вы попробуете привести объект Dog к типу Cat, вы получите исключение.

Это больше, чем просто неприятность. Это то, что может создать трудные в обнаружении ошибки. Если одна часть (или несколько частей) программы вставляют объекты в контейнер, и вы обнаруживаете только в отдельной части программы исключение, говорящее о том, что в контейнер был помещен неправильный объект, то вы должны найти, где производится неправильное вставление. С другой стороны, программирование удобно начать с каких-то стандартных контейнерных классов, несмотря на недостатки и неуклюжесть.

 

19. Потеря исключения. Привести пример (TIJ).

 

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

 

class VeryImportantException extends Exception {

  public String toString() {

    return "A very important exception!";

  }

}

 

class HoHumException extends Exception {

  public String toString() {

    return "A trivial exception";

  }

}

 

public class LostMessage {

  void f() throws VeryImportantException {

    throw new VeryImportantException();

  }

  void dispose() throws HoHumException {

    throw new HoHumException();

  }

  public static void main(String[] args)

      throws Exception {

    LostMessage lm = new LostMessage();

    try {

      lm.f();

    } finally {

      lm.dispose();

    }

  }

} ///:~

Вот что получаем на выходе:

Exception in thread "main" A trivial exception

    at LostMessage.dispose(LostMessage.java:21)

    at LostMessage.main(LostMessage.java:29)

Вы можете видеть, что нет свидетельств о VeryImportantException, которое просто заменилось HoHumException в предложении finally. Это достаточно серьезная ловушка, так как это означает, что исключения могут быть просто потеряны и далее в более узких и трудно определимых ситуациях, чем показано выше.

 

Hosted by uCoz