Содержание | Рекомендации по изучению | Задания

Занятие 1 | Занятие 2 | Занятие 3 | Занятие 4 | Занятие 6 | Занятие 7 | Занятие 8 | Занятие 9 | Занятие 10 | Занятие 11 | Занятие 12 | Занятие 13

Занятие 7

Ассоциативные массивы

Понятие ассоциативного массива

Ассоциативный массив — это набор пар элементов. Каждая пара содержит ключ и значение, причем ключи не повторяются. В любой момент можно получить элемент-значение, ассоциированный (находящийся в паре) с заданным ключом. Ассоциативный массив также называют словарем или отображением.

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

Можно воспринимать ассоциативный массив как разновидность обычного массива, в котором индексы могут быть не только целыми числами, а произвольными объектами.

Интерфейс Map

Интерфейс Map содержит методы для работы с ассоциативным массивом:

size() — возвращает количество элементов (пар) в массиве;

containsKey(Object key) — проверяет, существует ли в массиве элемент с ключом key;

containsValue(Object value) — проверяет, существует ли в массиве элемент со значением value;

get(Object key) — возвращает значение, соответствующее ключу key;

put(Object key, Object value) — добавляет в массив элемент с ключом key и значением value. Если элемент с таким ключом уже существует в массиве, то его значение просто изменяется;

values() — возвращает значения всех элементов массива в виде коллекции (т.е. возвращаемый результат имеет тип Collection);

remove(Object key) — удаляет элемент с ключом key, возвращая значение этого элемента (если он есть) и null, если такого элемента не было;

об элементах ассоциативного массива Map (интерфейс Map.Entry)

clear() — очищает массив;

isEmpty() — проверяет, не пуст ли массив.

Каждый элемент ассоциативного массива, описываемого интерфейсом Map, имеет интерфейсный тип Map.Entry, который предоставляет три основных метода:

getKey() — возвращает ключ элемента;

getValue() — возвращает значение элемента;

setValue(Object value) — меняет значение элемента.

Метод entrySet(), определенный в интерфейсе Map, позволят получить все элементы ассоциативного массива в виде множества объектов типа Map.Entry.

Класс Hashtable

Класс Hashtable — одна из реализаций интерфейса Map. *

о внутренней организации и конструировании класса Hashtable

Hashtable, как и Vector, кроме размера имеет емкость (размер буфера, выделенного под элементы массива). Помимо этого он характеризуется показателем загруженности — долей буфера, после заполнения которой емкость автоматически увеличивается. Конструктор Hashtable() без параметров создает пустой объект с емкостью в 101 элемент и показателем загруженности 0.75. Чтобы задать начальную емкость и показатель загруженности, следует воспользоваться конструктором с двумя параметрами Hashtable(int capacity, float loadFactor).

Hashtable имеет наследника — класс Properties, который вместо пар произвольных объектов хранит пары строк. Если в конкретной задаче и ключи и значения элементов ассоциативного массива должны иметь тип String (а это бывает довольно часто), удобнее воспользоваться классом Properties — не надо будет делать постоянное приведение типов.

В классе Properties определены методы getProperty(String key) и setProperty(String key, String value), работающие аналогично методам get() и put(), но принимающие параметры типа String.

об упорядоченных ассоциативных массивах (интерфейс SortedSet)

Интерфейс SortedMap

SortedMap — наследник интерфейса Map, описывает ассоциативный массив, элементы которого упорядочены по ключам. Методы, предоставляемые этим интерфейсом: firstKey(), lastKey(), subMap(Object fromKey, Object toKey), headMap(Object toKey), tailMap(Object fromKey) аналогичны методам интерфейса SortedSet. Данный интерфейс реализуется, например, в классе TreeMap. Один из конструкторов этого класса принимает объект типа Comparator, посредством которого можно задать свой собственный порядок сортировки.

Пример работы с ассоциативными массивами

Нужно разработать метод findUser(String login, String password), находящий в базе данных зарегистрированных пользователей пользователя с логином login и паролем password и возвращающий объект класса User, созданный на основе информации из этой БД. Метод должен возвращать null, если пользователя с требуемыми логином и паролем не существует.

Пусть сведения о зарегистрированных пользователях хранятся в таблице USERS, имеющей следующую структуру:

Пусть уже имеется вспомогательный метод getNextUser(), который возвращает очередную строку этой таблицы, а если строк больше нет, возвращает null. Заголовок этого метода может выглядеть следующим образом:

Map getNextUser();

Заметьте, возвращаемый результат имеет интерфейсный тип Map, а не какого-то конкретного класса (например, Hashtable). Это профессиональный подход к разработке метода. Конечно, внутри метода происходит работа с объектом конкретного класса (возможно, с тем же Hashtable — ведь метод должен создать возвращаемый объект, а создать объект абстрактного класс или интерфейса нельзя). Но, скрывая детали своей реализации и сообщая остальной программе лишь минимально необходимую информацию (что возвращаемый результат поддерживает интерфейс Map), метод добивается большей гибкости. Впоследствии может быть принято решение заменить Hashtable на HashMap, и при этом все изменения нужно будет произвести только в теле метода getNextUser(), а не во всех местах программы, где он вызывался.

Ключами элементов ассоциативного массива, возвращаемого методом getNextUser(), являются строки, совпадающие с названиями столбцов таблицы, а значениями — строки, содержащие значения из соответствующей ячейки таблицы. Предположим, что этот метод к тому же облегчает нам работу: заменяет значение 0 столбца category на строку "user", а значение 1 на строку "admin". Дело в том, что в зависимости от категории пользователя (которая в БД закодирована числом), мы должны создать либо объект класса User, либо объект производного от него класса Admin.

Наш метод будет выглядеть следующим образом. (Набирать его нет смысла, просто постарайтесь разобраться. И следите за скобками!)

private User findUser(String login, string password) { Map userData = getNextUser(); while (userData != null) { if (((String)UserData.get("login")).equals(login) && ((String)UserData.get("password")).equals(password)) { if (((String)UserData.get("category")).equals("user")) return new User((String)UserData.get("name")); if (((String)UserData.get("category")).equals("admin")) return new Admin((String)UserData.get("name")); } } userData = getNextUser(); } return null; }

Обратите внимание как каждый раз, обращаясь к ассоциативному массиву userData методом get() и получая значение, ассоциированное с нужным нам ключом, мы приводим его к типу String.

Обратите внимание на организацию цикла while: мы вызываем метод getNextUser(), пока он не возвратит нам значение null. Если в процессе обработки очередного набора данных из таблицы мы обнаружим, что логин и пароль совпадают с параметрами метода, мы завершаем работу метода командой return, возвращая требуемый объект. Если же команда return ни разу не вызовется в цикле, это будет означать, что такого пользователя в базе нет и метод должен вернуть null, что он и делает в последней строке.

Классы-оболочки

Объектно-ориентированный подход к описанию коллекций дает нам ряд преимуществ. В частности, возможность помещать в коллекции объекты любых классов. Но при этом простые типы данных оказываются обделенными. Мы не можем создать коллекцию целых чисел int или символов char*.

Для того, чтобы работать с простыми типами данных как с объектами (и, в частности, применять их в коллекциях), используются так называемые классы-оболочки (wrappers). Класс-оболочка построен по очень простому принципу: он хранит внутри себя поле простого типа и предоставляет несколько операций для доступа к этому полю.

В стандартной библиотеке Java определены восемь классов-оболочек — по одному на каждый простой тип. Их названия совпадают с названиями этих типов, но начинаются с заглавной буквы. То есть, это Byte, Short, Long, Float, Double, Char, Boolean. Исключение — класс-оболочка для целых чисел, который называется Integer. Каждый класс имеет простой конструктор, принимающий величину соответствующего типа.

Integer i = new Integer(15); Boolean b = new Boolean(false);

При необходимости легко можно получить «содержимое» класса с помощью одного из его методов. Автоматического приведения к простому типу не происходит. Так что, если нам надо передать в какой-то метод параметр типа int, а нужное нам число представляет собой объект класса Integer, надо осуществить явное преобразование:

String s; сhar ch = s.charAt(i.intValue);

Зато легко теперь добавлять объекты простых типов в любую коллекцию. Например:

Vector vect = new Vector(); vect.add(b); vect.add(new Double(3.14));

Класс Vector в последних версиях Java позволяет передавать в метод и примитивные типы данных, автоматически создавая для них оболочку.

Работа с датой и временем

Данные типа дата/время приходится обрабатывать довольно часто, поэтому все современные языки программирования предлагают удобный механизм для работы с ними в одной из своих библиотек.

В Java для этих целей разработаны классы Date и Calendar, собранные в пакете java.util.

Класс Date хранит число миллисекунд, прошедших с 1 января 1970 года (во внутреннем поле, имеющем тип long). Конструктор без параметров этого класса создает объект, содержащий текущее время (по системным часам машины, на которой выполняется программа). Другой конструктор, с параметром типа long, создает объект Date на основе заданного числа.

Метод getTime() позволяет получить это число, а метод setTime(long newTime) — изменить.

Для отображения даты на экране ее внутреннее представление необходимо перевести в строку, более удобную для восприятия. Для этого обычно используется класс SimpleDateFormat (являющийся наследником абстрактного класса DateFormat), который надо импортировать из пакета java.text. Прежде всего необходимо создать объект этого класса, указав в качестве параметра конструктора строку, определяющую способ форматирования. Например:

SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy hh:mm");

Теперь следует вызвать метод format() созданного форматирующего объекта, передав ему в качестве параметра тот объект Date, который мы хотим представить в виде строки. Чтобы вывести на экран текущую дату, используем команду:

System.out.println(formatter.format(new Date()));

В момент написания этих строк программа вывела в консоль

23.09.2007 05:51

В строке "dd.MM.yyyy hh:mm" dd означает цифры дня, MM — цифры месяца, yyyy — цифры года, hh — часы и mm — минуты. Комбинация MMMM дала бы название месяца (в именительном падеже), а yy — только две последние цифры года. Названные составляющие могут следовать в любом порядке (причем, все они присутствовать не обязаны), а разделители между ними (в примере это точка, пробел и двоеточие) могут быть и другими.

Класс Calendar позволяет работать с датой на более высоком уровне, отдельно рассматривая составляющие даты (число, месяц, год и т.д.). Он является абстрактным, поэтому необходимо пользоваться одним из его наследников. Таковым является GregorianCalendar (описывающий грегорианский календарь, по которому мы живем).

Создать объект этого класса, содержащий текущую дату/время можно конструктором без параметров. Всего же у класса GregorianCalendar семь конструкторов, наиболее мощный из них принимает шесть параметров:

GregorianCalendar(int year, int month, int day, int hour, int minutes, int seconds)

Получить любую часть даты можно с помощью универсального метода get(int field). Целочисленный параметр field определяет желаемую часть даты.

В классе Calendar определены константы, описывающие возможные варианты: ERA, YEAR, MONTH, WEEK_OF_YEAR, WEEK_OF_MONTH, DAY_OF_YEAR, DAY_OF_MONTH, DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND, MILLISECOND и несколько других.

Например, узнаем, какой сегодня день года:

GregorianCalendar today = new GregorianCalendar(); int result = today.get(Calendar.DAY_OF_YEAR); System.out.println(result);

Любую часть даты можно изменить методом set(int field, int value).

Упражнение

Спроектируйте и разработайте метод, определяющий, сколько времени прошло с заданной даты. С помощью этого методы выведите в консоль, сколько времи прошло с вашего дня рождения в удобном для восприятия виде, например: «Вам исполнилось 20 лет, 3 месяца, 18 дней, 4 часа, 5 минут и 10 секунд».

Генерация случайных чисел

В пакете java.util описан класс Random, являющийся генератором случайных чисел. На самом деле в силу своей природы ЭВМ не может генерировать истинно случайные числа. Числа генерируются определенным алгоритмом, причем каждое следующее число зависит от предыдущего, а самое первое — от некоторого числа, называемого инициализатором. Две последовательности «случайных» чисел, сгенерированных на основе одного инициализатора, будут одинаковы.

Класс Random имеет два конструктора:

Random() — создает генератор случайных чисел, использующий в качестве инициализатора текущую дату (число миллисекунд с 1 января 1970);

Random(long seed) — создает генератор случайных чисел, использующий в качестве инициализатора число seed.

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

От генератора можно получать случайные числа нужного типа с помощью методов nextBoolean(), nextInt(), nextLong(), nextFloat(), nextDouble(). Вещественные числа генерируются в диапазоне от 0 до 1 (не включая 1), а целые — из всего диапазона возможных значений. Можно сгенерировать целое число в нужном диапазоне (от 0 до max-1) методом nextInt(int max) или nextLong(long max).

Наконец, можно заполнить случайными числами целый массив (предварительно созданный), воспользовавшись методом nextBytes(byte[] arr). Элементы массива arr должны иметь тип byte.

Содержание | Рекомендации по изучению | Задания

Занятие 1 | Занятие 2 | Занятие 3 | Занятие 4 | Занятие 6 | Занятие 7 | Занятие 8 | Занятие 9 | Занятие 10 | Занятие 11 | Занятие 12 | Занятие 13