Вопросы по Java на собеседовании (2)

1. Сравнение объектов методом equals и оператором '=='.
2. Коллекция объектов.
3. Основные интерфейсы коллекций и их реализации.
4. Отличие коллекций ArrayList и LinkedList.
5. Отличия коллекций ArrayList и Vector.
6. Отличия коллекций HashMap и Hashtable.
7. Почему интерфейс Map не относят к коллекции.
8. Перевод набора данных из ArrayList в HashSet и обратно.
9. Использование Iterator'а коллекции для перебора элементов.
10. Использование цикла for-each.
11. Использование метода итератора remove().
12. Итераторы Iterator и ListIterator.
13. Интерфейс Enumeration <E>.
14. Метод коллекции toArray() для получения массива элементов.
15. Использование утилиты Arrays.
16. Использование метода коллекции contains().
17. Методы коллекции Map.

Вопросы и ответы для собеседование по Java, Содержание.
Вопросы и ответы для собеседование по Java, часть 1.
Вопросы и ответы для собеседование по Java, часть 3.
Вопросы и ответы для собеседование по Java, часть 4.
Вопросы и ответы для собеседование по Java, часть 5.
Вопросы и ответы для собеседование по Java, часть 6.

1. Сравнение объектов методом equals и оператором '=='

Метод equas определен в Object и используется для сравнения объектов по значениям полей. При сравнении объектов при помощи оператора '==' выполняется сравнение по ссылкам.

При использовании метода equals() определяется отношение эквивалентности объектов. Эквивалентным называется отношение, которое является симметричным, транзитивным и рефлексивным :

  • Симметричность: для любых ненулевых x и y выражение x.equals(y) должно вернуть истину (true) тогда, и только тогда, когда y.equals(x) также вернет true;
  • Транзитивность: если для любых ненулевых x, y и z выражения x.equals(y) и y.equals(z) вернут true, то и x.equals(z) также вернет true;
  • Рефлексивность: для любого ненулевого x выражение x.equals(x) вернет true.

В методе equals() выполняется сравнение полей двух объектов. Ответственность за реализацию метода equals ложится на разработчика. При переопределении equals() обязательно нужно переопределить метод hashCode(), поскольку равные объекты должны возвращать одинаковые hash-коды. Следует помнить, что множество возможных hash-кодов ограничено примитивным типом int, а множество объектов – только фантазией программистов. Отсюда следует утверждение : «Множество объектов мощнее множества хеш-кодов». Из-за этого ограничения, вполне возможна ситуация, что хеш-коды разных объектов могут совпасть. Здесь надо понять, что если хеш-коды разные, то и входные объекты гарантированно разные. Но если хеш-коды равны, то входные объекты не всегда равны.

Ситуация, когда разные объекты имеют одинаковые хеш-коды, называется — коллизией. Вероятность возникновения коллизии зависит от используемого алгоритма генерации hash-кода. Для определения hash-кода объекта следует использовать те поля, которые используются при сравнении в методе equals().

Подробное описание с примерами использования методов equals() и hashCode() представлено здесь.

2. Коллекции объектов

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

  • добавление элемента в коллекцию;
  • удаление элемента из коллекции;
  • изменение элемента в коллекции.

3. Основные интерфейсы коллекций и их реализации

Наборы данных (Сollection) реализуют три интерфейса: List, Set, Queue.

List определяет упорядоченное хранение элементов, которые могут быть «одинаковыми». Основными реализациями интерфейса List являются ArrayList, LinkedList и Vector :

ArrayList – несвязанный набор элементов, имеет преимущество в навигации по коллекции;
LinkedList – связанный набор элментов, имеет преимущество во вставке и удалении элементов;
Vector – «синхронизированный» (synchronized) несвязанный набор элементов.

Vector по сравнению с ArrayList и LinkedList в однопоточном приложении работает медленнее, поэтому его целесообразнее использовать в многопоточном приложении.

Set – это коллекции, которые не содержат повторяющихся элементов. Основными реализациями интерфейса Set являются HashSet, TreeSet, LinkedHashSet :

TreeSet – упорядочивает элементы по их значениям;
HashSet – упорядочивает элементы по их hash ключу;
LinkedHashSet – хранит элементы в порядке их добавления в набор.

Подробное описание с примерами реализации набора данных интерфейса Set представлено здесь.

Queue – это интерфейс для реализации однонаправленной очереди. Основными реализациями интерфейса Queue являются LinkedList, PriorityQueue. Однонаправленная очередь работает по принципу FIFO (first-in-first-out), согласно которому новые элементы размещаются в хвосте очереди, а операции извлечения получают элементы из головы очереди. Имеется интерфейс для двустороннего доступа к элементам очереди Deque. Подробное описание с примерами реализации неблокирующих очередей представлено здесь.

Интерфейс Map также используется для формирования набора данных, но в формате «карты», где элементы хранятся в виде пары «ключ – значение». Основными реализациями интерфейса Map являются HashMap, TreeMap и LinkedHashMap :

HashMapпорядок элементов рассчитывается по hash ключу;
TreeMapэлементы хранятся в отсортированном порядке;
LinkedHashMapэлементы хранятся в порядке вставки.

Подробное описание с примерами реализации интерфейса Map представлено здесь.

4. Отличие коллекций ArrayList и LinkedList

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

5. Отличия коллекций ArrayList и Vector

Отличия двух коллекций ArrayList и Vector связаны с принципом изменения размера массива при добавлении элементов в набор и с синхронизацией элементов набора данных. Класс Vector был введен в JDK 1.0 и он не является частью JCF (Java Collection Framework).

Изменение размера массива
Обе коллекции ArrayList и Vector хранят их содержимое в виде массива. Но, когда элемент вставляется в ArrayList или Vector, объект должен будет расширить свой внутренний массив, если он исчерпан. Vector по умолчанию удваивает размер своего массива, а ArrayList увеличивает размер массива на 50 процентов.

Синхронизация данных
Синхронизация набора данных используется в многопоточном приложении, когда несколько потоков пытаются внести изменения в наборы данных. Vector изначально определен для использования в многопоточных приложениях, его методы синхронизированы, что обеспечивает потокобезопасность, но это приводит к снижению производительности. А при использовании ArrayList в многопоточном приложении необходимо синхронизировать блок кода, использующего данную коллекцию.

6. Отличия коллекций HashMap и Hashtable

HashMap и Hashtable являются реализациями одного интерфейса Map. Главное отличие данных классов связано с тем, что методы класса Hashtable синхронизированы, а HashMap - нет. Кроме этого класс Hashtable в отличии от HashMap не разрешает использование null в качестве ключей и значений. HashMap допускает хранение null ключей и значений, но не допускает дублирования ключей. Следует помнить, что hashCode() для null-ключа всегда равен нулю.

Наличие синхронизации в Hashtable снижает производительность кода, использующего данный класс. Для синхронизации можно использовать методы класса Collections: Collections.synchronizedMap(map), Collections.synchronizedList(list) или Collections.synchronizedSet(set). Но необходимо помнить, что методы синхронизации класса Collections возвращают синхронизированный декоратор переданной коллекции, не обеспечивающий синхронизацию элементов коллекции в случае итерации элементов.

Полную потокобезопасную синхронизацию набора данных обеспечивает класс ConcurrentHashMap из пакета concurrent. Описание ConcurrentHashMap с примером представлено здесь.

7. Почему интерфейс Map не относят к коллекции

Реализации интерфейса Map также, как и реализации коллекций (List, Set), используются для формирования набора данных. Однако, если в коллекции элемент данных представляет определенный Object, то в реализации Map элемент является совокупностью пары "ключ-значение". Соответственно, некоторые методы Collection нельзя использовать в Map. Например, метод remove(Object o) в коллекции предназначен для удаления элемента, в то время, как такой же метод remove(Object key) в интерфейсе Map предназначен для удаления элемента по заданному ключу.

8. Перевод набора данных из ArrayList в HashSet и обратно

Набор данных ArrayList можно преобразовать в коллекцию HashSet и обратно. Примеры преобразования :

List<String> list = new ArrayList <String>();
list.add ("Summer");
list.add ("Winter");
// Преобразование в Set
Set<String> saison = new HashSet<String> (list);

------------------------------------------------------------
// Обратное преобразование
Set<String> saison = new HashSet<String> ();
set.add ("Summer");
set.add ("Winter");
// Преобразование в ArrayList
List<String> list = new ArrayList <String>(saison);

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

9. Использование Iterator'а коллекции для перебора элементов

Интерфейс Collection имеет метод iterator(), возвращающий объект-итератор, который используется для последовательного обращения к элементам набора данных. Этот итератор реализует интерфейс Iterator, который включает методы next(), hasNext() и remove().

C помощью вызова метода next() можно получить следующий элемент в наборе. Метод hasNext() позволяет проверить наличие следующего элемента. Данные методы следует использовать совместно при организации цикла перебора элементов коллекции. Метод remove() удаляет текущий элемент, который был получен при последнем вызове next(). Пример использования итератора :

List<String> states = new ArrayList<String>();
states.add("Германия");
states.add("Франция");
states.add("Италия");
states.add("Испания");
         
Iterator<String> iter = states.iterator();
while(iter.hasNext())
    System.out.println(iter.next());

Примечание :
1. Если вызвать метод next() итератора, указывающего на последний элемент в коллекции, то возникнет исключение NoSuchElementException. Следует об этом помнить и использовать метод hasNext() перед методом next().
2. Обычные коллекции не потокобезопасные. Поэтому, если два итератора в разных потоках работают с одной и той же коллекцией, то изменение содержимого коллекции в одном потоке будет сопровождаться вызовом исключения ConcurrentModificationException в другом потоке при вызове итератора одного из его методов. Чтобы избежать этого, следует использовать потокобезопасные concurrent коллекции.

Порядок следования элементов в итераторе

Порядок получения элементов коллекции при переборе итератором зависит от типа и набора элементов. Если используется ArrayList, то итератор начинает с нулевого индекса и инкрементирует индекс на каждом шаге. Если объект имеет тип HashSet, то порядок следования элементов коллекции может оказаться случайным. При использовании Iterator'a, можно быть уверенным, что он переберет все элементы, но порядок элементов может быть случайным.

Подробное описание и пример использования итератора коллекции представлены здесь.

10. Использование цикла for-each

Цикл перебора элементов коллекции for-each, появившийся в JDK 5.0, реализован с использованием Iterator'a. Компилятор преобразует цикл for-each в обычный цикл с итератором. Таким образом, цикл "for each" работает с любым объектом, реализующим интерфейс Iterable. Цикл из примера п.9. можно представить следующим образом :

List<String> states = new ArrayList<String>();
states.add("Германия");
states.add("Франция");
states.add("Италия");
states.add("Испания");

for (String element : list)
    System.out.println(element.toString());

11. Использование метода итератора remove

Метод итератора remove() следует вызывать после вызова метода next(), иначе будет вызвано исключение IllegalStateException(). Метод remove() удаляет элемент, на который указывает итератор. Нельзя вызывать метод remove() два раза подряд для удаления элементов. Необходимо после первого удаления повторно вызвать метод next().

Если два потока паралельно вносят изменения (добавление, удаление) в набор данных, то необходимо либо синхронизировать коллекции, либо использовать потокобезопасные коллекции пакета Concurrent. В противном случае могут возникать исключения типа ConcurrentModificationException.

12. Итераторы Iterator и ListIterator

ListIterator расширяет свойства интерфейса Iterator, позволяя перемещаться по коллекции в обоих направлениях, изменять содержимое коллекции и получать текущую позицию итератора. При этом следует помнить, что ListIterator не указывает на конкретный элемент коллекции и его текущая позиция определяется двумя элементами, которые возвращают методы previous() и next(). Таким образом, модификация коллекции осуществляется для последнего элемента, который был возвращен одним из методов previous() или next().

13. Интерфейс Enumeration <E>

Интерфейс Enumeration, также как и Iterator, предназначен для обхода коллекций. Он включает два метода : hasMoreElements(), возвращающий результат проверки наличия следующего элемента, и nextElement (), возвращающий следующий элемент перечисления. Пример использования Enumeration :

for (Enumeration<E> e = v.elements(); e.hasMoreElements();)
    System.out.println(e.nextElement());

Интерфейс Iterator был введен несколько позднее, по сравнению с Enumeration, и он является частью JCF (Java Collection Framework), поэтому его использование предпочтительнее. Кроме этого, Iterator дополнительно включает метод удаления элемента из коллекции.

14. Метод коллекции toArray для получения массива элементов

Для получения массива элементов из набора данных типа List, необходимо использовать метод toArray(). Следующий пример демонстрирует получение из набора данных List двух массивов разных типов.

List<String> list = new ArrayList<String>();
list.add("Весна");
list.add("Лето");
list.add("Осень");
list.add("Зима");

String[] array   = list.toArray (new String[list.size()]);
Object[] objects = list.toArray ();

15. Использование утилиты Arrays

Согласно документации утилита java.util.Arrays формирует список на основе массива. Массив, при этом, используется для внутреннего представления списка, т.е. сохраняется связь между списком и исходным массивом. Таким образом, изменение значения элемента в массиве отобразится в списке, и наоборот. Ниже представлен пример создания списка и внесение изменений. В качестве комментариев отображены выводимые в консоль значения :

String[] a = {"one", "two", "three"};

List<String> list = Arrays.asList(a);
System.out.println(list); // [one, two, three]

a[0] = "111";
System.out.println(list); // [111, two, three]
		
list.set(1, "222");
System.out.println(Arrays.toString(a)); // [111, 222, three]

Если массив содержит объекты, то и созданный утилитой Arrays список будет содержать объекты. При этом, и массив, и список будут ссылаться на одни и те же элементы :

Object[] a = {new Object(), new Object(), new Object()};
List<Object> list = Arrays.asList(a);
System.out.println(a[0] == list.get(0)); // true

Метод Arrays.asList() является параметризованным методом и работает только с объектами, т.е. автоматическую упаковку примитивных типов, например int, к классу-обёртке (Integer) метод не выполняет. Пример :

// массивы из 10 элементов
int[]     i = new int    [10];           
Integer[] I = new Integer[10];
String[]  s = new String [10];

// Вывод размеров массивов в консоль
System.out.println(Arrays.asList(i).size());  // 1
System.out.println(Arrays.asList(I).size());  // 10
System.out.println(Arrays.asList(s).size());  // 10

16. Использование метода коллекции contains

Метод коллекции contains() позволяет проверить наличие определенного элемента в наборе. Процесс проверки выполняется в цикле для всех элементов набора пока не будет найден соответствующий «эквивалент». При сравнении объектов используется метод equals() элемента набора. Поэтому, необходимо быть внимательным, если набор включает сложные объекты, для которых требуется метод equals() переписать. Следующий пример демонстрирует использование метода contains() для простых строковых объектов набора; в консоль будет выведено логическое значение 'true'.

List<String> list = new ArrayList<String>();
list.add("Весна");
list.add("Лето");
list.add("Осень");
list.add("Зима");
		
System.out.println("" + list.contains("Лето"));

Незначительно усложняем пример и создаем коллекцию из объектов Saison, который включает два поля : id и name. Теперь, чтобы метод (проверки наличия в коллекции элемента) contains() работал корректно необходимо в классе Saison переписать метод equals(), в котором выполнить соответствующее сравнение двух объектов. В примере сравнение объектов выполняется для целочисленных идентификаторов id, поэтому метод equals() вернет true для любых строковых значений name и в консоль будет выведено 'true'.

class Saison {
    public int id;
    public String name;

    public Saison(int id, String name) {
        this.id = id;
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
        return (id == ((Saison)obj).id);
    }
}
. . .

List<Saison> list = new ArrayList<Saison>();
list.add(new Saison(1, "Весна"));
list.add(new Saison(2, "Лето"));
list.add(new Saison(3, "Осень"));
list.add(new Saison(4, "Зима"));
		
System.out.println("" + list.contains(new Saison(2, "Весна")));

Листинг метода contains() можно посмотреть здесь.

17. Методы коллекции Map

Создаем коллекцию типа TreeMap :

TreeMap <Integer, String> map;

map = new TreeMap<Integer, String>();
// Добавляем данные
map.put(1, "Понедельник");
map.put(2, "Вторник"    );
map.put(3, "Среда"      );
map.put(4, "Четверг"    );
map.put(5, "Пятница"    );
map.put(6, "Суббота"    );
map.put(7, "Воскресенье");

Следующие методы коллекции типа Map возвращают наборы определенных типов, которые можно перебрать по элементам, используя Iterator :

  • keySet() возвращает множество элементов Set<E> ключей;
  • values() возвращает коллекцию Collection<E> значений;
  • entrySet() возвращает множество пар "ключ-значение" типа Set<Map.Entry<K, V>> .

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

// Получить все ключи
System.out.println("Ключи : " + map.keySet());
/*  Ключи : [1, 2, 3, 4, 5, 6, 7] */
-------------------------------
// Получить все значения

System.out.println("Значения : " + map.values());
/*  Значения : [Понедельник, Вторник, Среда, Четверг,\
                  Пятница, Суббота, Воскресенье]
*/
-------------------------------
// Первый ключ и его значение
System.out.println("Первый ключ : " + map.firstKey() + 
                   ", значение : " + map.get(map.firstKey()));
/* Первый ключ : 1, значение : Понедельник */

// Удаление первого элемента коллекции
map.remove(map.firstKey());

Подробное описание коллекций типа Map с примерами их использования можно увидеть здесь.

Вопросы и ответы для собеседование по Java, Содержание.
Вопросы и ответы для собеседование по Java, часть 1.
Вопросы и ответы для собеседование по Java, часть 3.
Вопросы и ответы для собеседование по Java, часть 4.
Вопросы и ответы для собеседование по Java, часть 5.
Вопросы и ответы для собеседование по Java, часть 6.

  Рейтинг@Mail.ru