Занятие №3

Обобщения и коллекции

Михаил

Павлов

Что вы узнаете сегодня

1) Обобщения (generics):

  • Для чего нужны

  • Как с ними работать

2) Коллекции:

  • ArrayList и LinkedList
  • HashMap
  • HashSet

3) Что такое raw types и wildcard

Представим, что мы хотим выводить в консоль объекты разных типов одинаково

class CommonIntegerWriter { 
    private Integer val; 
 
    public CommonIntegerWriter(Integer arg) { 
        val = arg; 
    } 
 
    public String toString() { 
        return "{" + val + "}"; 
    } 
 
    public Integer getValue() { 
        return val; 
    }
}

Класс для вывода в строку

(с использованием определенного типа)

class CommonBooleanWriter { 
    private Boolean val; 
 
    public CommonBooleanWriter(Boolean arg) { 
        val = arg; 
    } 
 
    public String toString() { 
        return "{" + val + "}"; 
    } 
 
    public Boolean getValue() { 
        return val; 
    }
}
class CommonBigDecimalWriter { 
    private BigDecimal val; 
 
    public CommonBigDecimalWriter(BigDecimal arg) { 
        val = arg; 
    } 
 
    public String toString() { 
        return "{" + val + "}"; 
    } 
 
    public BigDecimal getValue() { 
        return val; 
    }
}

Недостатки

  • Много однотипного кода

На прошлой лекции вы узнали, что для реализации общего поведения используется наследование

Адам Object - общий родительский элемент для всех объектов в Java

Основные методы класса Object

Метод Описание

 
Позволяет сравнивать 2 объекта

 
Возвращает тип (класс) экземпляра объекта

 
Возвращает хэш-код, используемый для поиска в коллекциях

 
Позволяет преобразовывать объект в строку
boolean equals( Object o );
Class getClass();
int hashCode();
String toString();
class CommonWriter { 
    private Object val; 
 
    public CommonWriter(Object arg) { 
        val = arg; 
    } 
 
    public String toString() { 
        return "{" + val + "}"; 
    } 
 
    public Object getValue() { 
        return val; 
    }
}

Класс для вывода в строку

(с использованием Object)

Пример использования

public class Test { 
    public static void main(String[] args) { 
        CommonWriter value1 = new CommonWriter(new Integer(10)); 
        System.out.println(value1); 
        Integer intValue1 = (Integer) value1.getValue(); 

        CommonWriter value2 = new CommonWriter("Hello world"); 
        System.out.println(value2); 
        Integer intValue2 = (Integer) value2.getValue(); 
    } 
} 

Ошибка приведения типа в рантайме

Недостатки

  • Ошибка проявляется в момент вызова

Преимущества

  • Кода ощутимо меньше

Нам помогут обобщения

Как сделать лучше?

Что такое обобщения (generics)

Обобщения - это параметризированные типы

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

Синтаксис

class CommonWriter<T> {
    private T val; 
 
    public CommonWriter(T arg) { 
        val = arg; 
    } 
 
    public String toString() { 
        return "{" + val + "}"; 
    } 
 
    public T getValue() { 
        return val; 
    } 
}
 CommonWriter<String> value2 = new CommonWriter<String>("Hello world"); 

Обобщения

public class Test { 
    public static void main(String[] args) { 
        CommonWriter<Integer> value1 = new CommonWriter<Integer>(new Integer(10)); 
        System.out.println(value1); 
        Integer intValue1 = value1.getValue(); 

        CommonWriter<String> value2 = new CommonWriter<String>("Hello world"); 
        System.out.println(value2); 
        Integer intValue2 = value2.getValue(); 
    } 
} 

Пример использования

Теперь ошибка в момент компиляции

Преимущества обобщений

  • Компилятор может поймать больше проблем с приведением типов
  • Меньше дублирования кода

Синтаксис

 CommonWriter<String> value2 = new CommonWriter<String>("Hello world"); 
 CommonWriter<String> value2 = new CommonWriter<>("Hello world"); 

Алмазный синтаксис:

Обычный синтаксис:

Несколько обобщений в одном классе

public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {
    private K key;
    private V value;
    public OrderedPair(K key, V value) {
	this.key = key;
	this.value = value;
    }

    public K getKey()	{ return key; }
    public V getValue() { return value; }
}
Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);
Pair<String, String>  p2 = new OrderedPair<>("hello", "world");

Слишком широкие обобщения - плохо

Как выделить нужное?

Нужны ограничения - используем наследование

Ограниченные обобщения

abstract class Animal {
    public void move() {};
}

class Cat extends Animal {
    public void move() { System.out.println("RUN");}
}

class Eagle extends Animal {
    public void move() { System.out.println("FLY");}
}
public <T extends Animal> void inDanger(T animal) {
    animal.move();
}

Компилятор и обобщения

Компилятор

Кармансы

"Что же у него там,

в его кармансах?"

Стирание типов

Изначально

После

Компиляция

Обобщенный тип

Нет типа

* При подготовке этой презентации ни один кролик не пострадал

До компиляции:

public static <E> boolean containsElement(E [] elements, E element){
    for (E e : elements){
        if(e.equals(element)){
            return true;
        }
    }
    return false;
}

После компиляции:

public static boolean containsElement(Object [] elements, Object element){
    for (Object e : elements){
        if(e.equals(element)){
            return true;
        }
    }
    return false;
}
  • В рантайме нельзя получить тип, он есть только до компиляции
  • Обобщения заменены на Object или родительские классы, если использовано наследование

Итого

Условия работы с обобщениями

  • Нельзя работать с примитивами (но есть врапперы)
  • Нельзя создать экземпляр обобщения
  • Нельзя задавать обобщения для статических полей (и нельзя перегружать)
  • Нельзя использовать instanceof
  • Нельзя имплементировать несколько одинаковых параметризированных интерфейсов (ссылка на пример)

Что вы узнаете сегодня

1) Обобщения (generics):

  • Для чего нужны

  • Как с ними работать

2) Коллекции:

  • ArrayList и LinkedList
  • HashMap
  • HashSet

3) Что такое raw types и wildcard

Коллекции

Зачем нужны коллекции?

Коллекции облегчают работу со множествами (объектов)

* В Java есть много встроенных коллекций для разных ситуаций

Фичи встроенных коллекций

  • Благодаря коллекциям можно работать с любыми наследниками Object
  • Автоматическая подстройка размера под количество элементов, в отличие от массивов
  • Они подходят для самых разных задач:
    • сортировка
    • быстрый поиск
    • уникальность
    • и т.д.

Схема

наследования коллекций

(полиморфизм в действии)

Разберем несколько

встроенных коллекций:

  • ArrayList и LinkedList (интерфейс List)

  • HashMap (интерфейс Map)

  • HashSet (интерфейс Set)

ArrayList и LinkedList

Оба реализуют интерфейс List

Что это дает:

  • Вставка в определенное место списка
  • Хранение дубликатов
  • Специальный итератор по списку
  • Получение элемента по индексу
  • Поиск индекса объекта в списке

ArrayList vs LinkedList

Каждый элемент списка знает 2 своих соседей (Node)

Для хранения используются динамические массивы

ArrayList

LinkedList

  • Потребляет меньше памяти
  • Более быстрый доступ к элементам
  • Быстрее добавление и удаление элементов
  • Медленнее поиск

* Подробнее про разницу тут и тут

Пример с ArrayList и интерфейсом List

import java.util.*;

public class MyApplication {
    public static void main(String[] args) { 
        List<Integer> list1 = Arrays.asList(1,2,3);
	
        List<String> list2 = new ArrayList();
        list2.add("Hello");
        list2.add("world");
    }
}

HashMap

  • Объекты хранятся парами: Ключ - Значение
  • Ключ уникален
  • Значение не уникально
  • Быстрый поиск по ключу

(метод hashCode у Object)

Key      Value

Реализует интерфейс Map

Пример с HashMap

import java.util.*; 

public class MyApplication {
    public static void main(String[] args) { 
        HashMap<String, String> capitalCities = new HashMap<>();

        capitalCities.put("England", "London");
        capitalCities.put("Germany", "Berlin");
        capitalCities.put("Norway", "Oslo");
        capitalCities.put("USA", "Washington DC");

        capitalCities.putIfAbsent("Russia", "Moscow");

        for(Map.Entry<String, String> m : capitalCities.entrySet()){
            System.out.println(m.getKey() + " " + m.getValue());
        }

        HashMap<String, String> newMap = new HashMap<>();
        newMap.put("Spain","Madrid");
        newMap.putAll(capitalCities);
    }
}

Интерфейс Set позволяет удобно работать с несколькими множествами

boolean result = setA.containsAll(setB);

B подмножество А?

А

В

Добавить B в А

В

setA.addAll(setB);

А

setA.removeAll(setB);

Исключить B из А

А

В

setA.retainAll(setB);

Пересечение А и В

А

В

HashSet

Важно: Значение уникально

За уникальностью следит Set

Key      Value

У Set ключ является значением

Реализует интерфейс Set

Пример с HashSet

import java.util.*; 

public class MyApplication {
  public static void main(String[] args) { 
    HashSet<String> set = new HashSet<>();  
    set.add("One");    
    set.add("Two");    
    set.add("Two");  
    set.add("Three");

    for (String item : set) {  
      System.out.println(item);  
    }
  }
}

Коллекции и иммутабельность

Все коллекции, по умолчанию, мутабельны

Объект считается иммутабельным, если его состояние нельзя изменить после создания

List<String> stringList = Arrays.asList("a", "b", "c");
stringList = Collections.unmodifiableList(stringList);

// Collections.unmodifiableSet
// Collections.unmodifiableMap

Можно создать иммутабельную коллекцию:

Что вы узнаете сегодня

1) Обобщения (generics):

  • Для чего нужны

  • Как с ними работать

2) Коллекции:

  • ArrayList и LinkedList
  • HashMap
  • HashSet

3) Что такое raw types и wildcard

Обобщения появились не сразу

Для сохранения работы старого кода добавлены raw types (сырые типы)

Сырые типы (raw types)

List list = new ArrayList();

Синтаксис

List<String> list = new ArrayList<String>(); 

Вместо

Или обобщения без параметров

* Сырые типы оставлены для обратной совместимости

У сырых типов есть проблема

import java.util.*;

public class MyApplication {
    public static void main(String[] args) { 
        List list = new ArrayList();
        list.add("First");
        list.add("Second");
        list.add(10);
        
        for(Iterator<String> itemItr = list.iterator(); itemItr.hasNext();) {
            System.out.println(itemItr.next()); 
        }
    }
}

Пример с сырым типом

Снова ошибка в момент выполнения

import java.util.*;

public class MyApplication {
    public static void main(String[] args) { 
        List<String> list = new ArrayList<>(); 
        list.add("First"); 
        list.add("Second"); 
        list.add(10);

        for(Iterator<String> itemItr = list.iterator(); itemItr.hasNext();) {
            System.out.println(itemItr.next()); 
        }
    }
}

Пример с обобщением с параметрами

Ошибка при компиляции

Но что делать, если тип нам неизвестен?

Стоит избегать использования сырых типов

public static void main(String[] args) {
    //Double list 
    List<Double> list2 = Arrays.asList(1.1,2.2,3.3); 
    
    //Integer List 
    List<Integer> list1 = Collections.emptyList(); 
  
    checkIfEmptyList(list1);
    checkIfEmptyList(list2);
} 
  
private static void checkIfEmptyList(List<Object> list) {
    if (list.isEmpty()) {
      throw new RuntimeException("Ошибка. Передан пустой лист");
    }
}

Коллекция с неизвестным содержимым

Не работает

Object - родительский класс для Integer,

но Collection<Object> не родитель для Collection<Integer>

Для коллекций с неизвестным типом есть отдельный синтаксис

Маски (wildcards)

  • Используются для описания неизвестного типа
  • Синтаксис похож на обобщения, но вместо буквы - знак вопроса
public static void main(String[] args) {
    //Double list 
    List<Double> list2 = Arrays.asList(1.1,2.2,3.3); 
    
    //Integer List 
    List<Integer> list1 = Collections.emptyList(); 
  
    checkIfEmptyList(list1);
    checkIfEmptyList(list2);
} 
  
private static void checkIfEmptyList(List<?> list) {
    if (list.isEmpty()) {
      throw new RuntimeException("Ошибка. Передан пустой лист");
    }
}

Использование маски

Маски тоже можно наследовать

Домашнее задание

public <T> static Email sendHomeWork(Homework<T> homework) {
    Email result = homework.makeHomework();
    return result;
}
java.lang.NullPointerException

at Study.sendHomework (Study.java :2)

at MyApplication.onAfterLection (MyApplication.java :3)

Спойлер следующей лекции:

  • Вы узнаете, что такое NullPointerException

Практика по обобщениям будет в рамках последующих занятий

Сегодня домашнего задания не будет

Полезные ссылки

Как выбрать нужную коллекцию

Спасибо за внимание!

JavaSIS#3.20 Занятие 3

By Сибирские интеграционные системы