Занятие №3

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

Михаил

Павлов

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

Обобщения:

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

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

  • Что такое raw type и wildcard

Коллекции:

  • ArrayList
  • HashMap
  • HashSet

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

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(); 
    } 
} 

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

Недостатки

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

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

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

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

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

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

(с использованием обобщений)

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

Обобщения

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(); 
    } 
} 

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

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

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

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

Синтаксис

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"); 

1) Обобщение в обозначении класса

2) Обобщение в методе

Синтаксис

 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");

Что будет, если не указать тип при создании класса?

List list = new ArrayList();

Написать так

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

Вместо этого

Мы получим обобщение без параметров или сырой тип (raw type)

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

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

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);
        
        List<String> list2 = list;
        
        for(Iterator<String> itemItr = list2.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<String>(); 
        list.add("First"); 
        list.add("Second"); 
        list.add(10);

        List list2 = list;  

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

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

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

Что если тип нам неизвестен?

Для этого есть маски

Маски (wildcards)

  • Используются для описания неизвестного типа
  • Синтаксис похож на обобщения, но вместо буквы - знак вопроса
public static void main(String[] args) { 
    //Integer List 
    List<Integer> list1 = Arrays.asList(1,2,3); 
  
    //Double list 
    List<Double> list2 = Arrays.asList(1.1,2.2,3.3); 
  
    printlist(list1);
  
    printlist(list2);
} 
  
private static void printlist(List<?> list) { 
    System.out.println(list); 
}

Маска

Не пользуйтесь сырыми типами

Вместо них используйте маски

(wildcards или <?>)

Слишком много обобщений в коде - плохо

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

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

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

class CommonWriter<T extends Writable> {
    private T val; 
 
    public CommonWriter(T arg) { 
        val = arg; 
    } 
 
    public String toString() { 
        return "{" + val + "}"; 
    } 
 
    public <T extends Writable> static void fastWrite(T ob) {
        String text = new CommonWriter<T>(ob).toString();
        System.out.println(text);
    }
}

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

Как работает в рантайме?

Компилятор

Кармансы

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

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

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

До

После

Компиляция

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

Нет типа

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

Как выглядит код после компиляции?

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

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;
}
  • В рантайме нельзя получить тип, он есть только до компиляции
  • Обобщения заменены на сырой тип или родительские классы, если использовано наследование

Итого

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

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

Коллекции

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

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

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

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

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

Схема

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

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

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

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

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

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

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

ArrayList

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

Что это дает:

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

Пример с ArrayList

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<String, String>();

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

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

	HashMap<String, String> newMap = new HashMap<String,String>();  
      	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");
		
	Iterator<String> i = set.iterator();
		
	while(i.hasNext()) {  
	    System.out.println(i.next());  
	}  
    }
}

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

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

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

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

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

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

***

  • Хорошее объяснение принципов использования наследования в обобщениях (PECS) - https://stackoverflow.com/a/2723538

Вопросы

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

Занятие 3

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