Вы достигли нового уровня

Уровень 35

1. Системы контроля версий

- Привет, Амиго!

- Привет!

- Сегодня я расскажу тебе о системах контроля версий.

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

Проекты с миллионом строк кода – это реальность.

- Ничего себе.

- Это все очень сложно. Люди часто мешают друг другу, часто меняют один и тот же код и т.д. и т.п.

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

Система контроля версий – это программа, представленная в виде клиента и сервера.

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

Основное ее отличие от просто программ, которые позволяют распределённую работу с документами, это то, что она хранит все предыдущие версии всех документов (файлов с кодом).

- А можно больше подробностей. Как это все работает?

- Вот представь, что ты – программист и хочешь внести небольшие изменения в исходный код в программе, который хранится в репозитории (хранилище) на сервере.

Для этого тебе надо:

1) Залогиниться на сервер (Login)

2) Скопировать последнюю версию всех файлов к себе на компьютер – команда Checkout.

3) Внести изменения в нужные фалы.

4) Запустить программу локально и проверить, что она компилируется и работает.

5) Отправить свои «изменения» на сервер – команда Commit.

- В общем ясно.

- Но и это еще не все. Вот представь, что ты пришел утром на работу, а в Индии уже обед и твои индийские коллеги уже что-то поменяли, и закомитили свои изменения в ваш репозиторий (хранилище) на сервере.

Тебе же надо работать с последней актуальной версией данных. Тогда ты выполняешь команду Update.

- А в чем ее отличие от Checkout?

- Checkout предназначена для копирования всех файлов репозитория, а команда Update – только для тех файлов, которые обновились на сервере, со времен последней твоей команды Checkout/Update.

Вот как примерно это работает:

Checkout:

Теперь допустим, мы изменили файл A и хотим залить его на сервер. Для этого надо использовать команду Commit

А вот как работает команда Update:

- Как интересно. А есть еще команды?

- Да, и довольно много. Но они могут быть разные в зависимости от того, какую именно программу контроля версий ты выберешь. Поэтому я стараюсь рассказывать только общие принципы.

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

- А что делать второму?

Второму предложат сделать Update, чтобы забрать к себе последние изменения с сервера. Это кстати хороший стиль – перед коммитом делать Update

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

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

Такое часто случается, когда например, оба программиста добавили что-то в конец файла.

- Ясно. Разумно, в общем.

- И еще одна вещь – ветки.

Представь, что двум программистам из команды дали задание переписать один модуль. Или, ещё лучше – написать его заново. Пока этот модуль закончен не будет, программа не сможет работать, а может быть даже и компилироваться.

- Что же делать?

- Для этого в репозиторий добавили ветки (branches). Т.е. грубо говоря – разделили репозиторий на две части. Но не по файлам или директориям, а по версиям.

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

Такой путь – это и есть альтернативная ветка истории.

Или можешь просто попробовать представить, что ветка – это просто копия репозитория. Т.е. в какой-то момент, мы клонировали репозиторий на сервере и у нас кроме главного (который часто называют trunkствол) появился еще один – branch (ветка).

- Ну, так вроде понятнее.

А почему нельзя было просто сказать, что мы скопировали репозиторий?

- Это не есть копирование в чистом виде.

Такие ветки можно не только отделять от ствола (trunk), но и присоединять к нему.

- Т.е. можно какую-то работу выполнить в ветке, а когда она будет закончена – добавить репозиторий-ветку к репозиторию-стволу?

- Ага.

- И что при этом станет с файлами?

- Файлы смерджатся (объединятся).

- Ну, звучит круто, надеюсь так же круто и работает.

- А то. Ладно, давай делать перерыв.

Вот тут очень много полезной информации.

2. коммиты и ветки

- Сегодня я расскажу тебе о двух самых популярных программах контроля версий – SVN и Git.

SVN работает примерно по той схеме, как я приводил на прошлом уроке. Git, работает немного сложнее, и на нем я собираюсь остановиться поподробнее.

- А можно мне каких-нибудь ссылок на документацию про SVN и про Git?

- Конечно, держи.

http://svnbook.red-bean.com/nightly/ru/svn-book.html

http://githowto.com/ru (это просто шедевр)

И так - Git.

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

- А куда тогда комититься?

- Комитится пользователь всегда в свой локальный репозиторий.

- А серверный репозиторий?

- Для синхронизации локального и серверного репозитория есть специальные команды – Pull(вытащить) и Push(поместить/затолкнуть)

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

В SVN для этого пришлось бы заводить отдельную ветку, а затем мерджить ее с trunk'ом.

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

Такая работа выглядит немного избыточной, пока ты пишешь мало кода, но когда длинна твоих задач вырастет до недель, ты поймешь, что просто не можешь все это время что-то писать и не комититься.

- А почему нельзя просто две недели делать свою работу, а потом закомититься один раз на сервер?

- Дело в том, что программа контроля версий предлагает очень много удобств.

Представь, что ты комитишься каждый день, и на 10-й день обнаружил, что то, что ты делал два последних дня, не будет работать, так как задумано. И ты бы хотел вернуться к коду 8-го дня и попробовать сделать задачу другим образом.

Тогда ты просто откатываешь (rollback) изменения в локальном репозитории за два последних дня и возвращаешься к нужному тебе состоянию кода.

- Т.е. и так можно делать?

- Ага. Более того, ты благодаря сохранению истории коммитов, ты можешь узнать, кто, когда и зачем что-то коммитил, в рамках какой фичи/баги велась работа и какие десять файлов были изменены одновременно в рамках этой работы.

Допустим кто-то «фиксил багу» и поломал чужой функционал. Ты можешь просто откатить(rollback) его код и работать дальше, как будто его и не было.

- Ладно, это круто, убедил. А можно пару примеров, как все это работает?

- Конечно.

Клонирование (clone) центрального репозитория к себе на локальный компьютер:

- Т.е. операция Checkout больше не нужна.

- Ага. А вот примеры операций Push:

И Pull:

- Ага. Более-менее ясно.

- Кстати, есть крутой сервис, называется GitHub.

Любой программист может там зарегистрироваться и создавать свои Git-репозитории. Советую тебе познакомиться с ним поближе.

Вот тебе пара полезных ссылок:

http://habrahabr.ru/post/125799/

http://githowto.com/ru

http://webhamster.ru/site/page/index/articles/comp/171

http://tutorials.assembla.com/git-guide-for-windows-users/tour.ru.html

На заметку, под гит существует довольно много «клиентов».

Во-первых – это GitBash – где можно вводить команды текстом

Во-вторых – это TortoiseGit – хорошая программа, которая встраивается в проводник Windows и позволяет работать с файлами в Git-репозитории прямо из проводника.

Intellij IDEA имеет поддержку Git, и все сложные команды можно делать прямо из нее в пару кликов.

- И что мне учить?

- Рекомендую разобраться со всем этим.

Вот, пройдешь ты собеседование, придешь на работу. Тебе скинут ссылку на гит, твои логин и пароль и все. Дальше ты сам.

- Что сам?

- Сам ставишь гит, вытаскиваешь себе копию репозитория,…

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

А инструкция по сборке скорее всего тоже в Git'е (в Git-репозитории), как и документация по проекту.

К тебе подойдет твой тимлид вечером и скажет: - Ну что, с чем уже разобрался?

А ты такой: – А я тут гит пробую установить, но у меня еще ничего не получилось. Вы ведь меня не уволите, правда?

Или ты можешь еще днем подойти к тимлиду и сказать: - Установил гит, вытащил проект, порылся в документации, но там сотни файлов и пока что не все понятно. Где актуальная инструкция по сборке?

Чувствуешь разницу?

- Ага. Во втором случае я такой крутой программист и все дела, а в первом какой-то роболох, который не умеет даже проект из гита вытащить. Т.е. облажался еще до того, как дело дошло до программирования. Меня, небось, после этого и программировать-то не допустят.

- Вот ты сам и ответил на свои вопросы. Так что учи, разбирайся. Никто за тебя это не сделает.

- А ты мне не поможешь?

- Уже помог. Мы тут Java учим, если ты не забыл. А все остальное – сам. Или тебе голова дана только для того, чтобы ею пить?

- Ладно, я все понял. Спасибо, Билаабо!

3. Generics

- Привет, Амиго!

- Привет, Элли!

- Сегодня мы с Ришей собираемся рассказать тебе все о generic'ах.

- Так я уже вроде бы почти все знаю.

- Почти все, да не все.

- Да? Ладно, я готов слушать.

- Тогда начнем.

Generic'ами в Java, называют классы, которые содержат типы-параметры.

Причины появления generic'ов – см. комментарий в коде:

Пример
ArrayList stringList = new ArrayList();
stringList.add("abc"); //добавляем строку с список
stringList.add("abc"); //добавляем строку с список
stringList.add( 1 ); //добавляем чисто с список

for(Object o: stringList)
{
 String s = (String) o; //тут будет исключение, когда дойдем до элемента-числа
}

Как решают проблему Generic'и:

Пример
ArrayList<String> stringList = new ArrayList<String>();
stringList.add("abc"); //добавляем строку с список
stringList.add("abc"); //добавляем строку с список
stringList.add( 1 ); //тут будет ошибка компиляции

for(Object o: stringList)
{
 String s = (String) o;
}

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

- Я это уже знаю.

- Вот и отлично. Повторение мать учения.

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

Код с generic'амиЧто происходит на самом деле
List<String> strings = new ArrayList<String>();
strings.add("abc");
strings.add("abc");
strings.add( 1); // тут ошибка компиляции

for(String s: strings)
{
 System.out.println(s);
}
List strings = new ArrayList();

strings.add((String)"abc");
strings.add((String)"abc");
strings.add((String) 1); //ошибка компиляции

for(String s: strings)
{
 System.out.println(s);
}

- Хитро, да.

- Да, но у этого подхода есть побочный эффект. Внутри класса-generic'а не хранится никакой информации о его типе-параметре. Такой подход позже назвали стиранием типов.

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

Код с generic'амиЧто происходит на самом деле
class Zoo<T>
{
 ArrayList<T> pets = new ArrayList<T>();

 public T createAnimal()
 {
  T animal = new T();
  pets.add(animal)
  return anumal;
 }
}
class Zoo
{
 ArrayList pets = new ArrayList();

 public Object createAnimal()
 {
  Object animal = new ???();
  pets.add(animal)
  return anumal;
 }
}

При компиляции все типы параметров заменяются на Object. И информации о типе, который в него передавали, в классе нет.

- Да, согласен, это не очень хорошо.

- Ладно, все не так страшно, я тебе позже расскажу, как эту проблему научились обходить.

Но и это еще не все. Java позволяет задать тип-родитель для типов-параметров. Для этого используется ключевое слово extends. Пример:

Код с generic'амиЧто происходит на самом деле
class Zoo<T extends Cat>
{
 T cat;

 T getCat()
 {
  return cat;
 }

 void setCat (T cat)
 {
  this.cat = cat;
 }

 String getCatName()
 {
  return this.cat.getName();
 }

}
class Zoo
{
 Cat cat;

 Cat getCat()
 {
  return cat;
 }

 void setCat(Cat cat)
 {
  this.cat = cat;
 }

 String getCatName()
 {
  return this.cat.getName();
 }

}

Обрати внимание на два факта.

Во-первых, в качестве типа параметра теперь можно передать не любой тип, а тип Cat или один из его наследников.

Во-вторых, в классе Zoo у переменных типа T теперь можно вызвать методы класса Cat. Почему – объяснено в столбце справа (потому что вместо типа T везде подставится тип Cat)

- Ага. Если мы сказали, что в качестве типа-параметра у нас передают тип Cat или его наследников, значит, мы уверены, что методы класса Cat и типа T обязательно есть.

Что ж. Умно.

4. Задачи

Задачи
1. Знакомство с дженериками

Параметризируйте классы SomeClass и Solution следующим образом:
1. SomeClass должен работать с типами, которые наследуются от Number;
2. Solution должен работать с типами, которые наследуются от List, который в свою очередь параметризируется типом SomeClass.
2. Вызов статического метода

Измените статический метод в классе GenericStatic так, чтобы он стал дженериком.
Пример вызова дан в методе main.

5. Generics: super, extends, list

- Новая интересная тема - wildcards.

По смыслу, это что-то вроде шаблона «*», который совпадает с чем угодно.

Но давай начнем издалека.

Представь, что у тебя есть класс «Warrior(Воин)» и метод, который вычисляет, какой из двух воинов сильнее. Вот как, например, это могло бы выглядеть:

Пример 1
class WarriorManager
{
 public static boolean fight(Warrior w1, Warrior w2)
 {
  return w1.power > w2.power;
 }
}
Пример вызова
MagicWarrior mag = new MagicWarrior();
ArcherWarrior archer = new ArcherWarrior();

boolean isMagicCooler = WarriorManager.fight(mag, archer);

MagicWarrior и ArcherWarrior – это классы-наследники от Warrior.

Немного простовато, но для примера сойдет.

- Ок.

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

Пример 1
class WarriorManager
{
 public static boolean fight(Warrior w1, Warrior w2)
 {
  return w1.power > w2.power;
 }

 public static boolean fight(List<Warrior> w1, List<Warrior> w2)
 {
  return
 }
}
Пример вызова
ArrayList<MagicWarrior> magi = new ArrayList<MagicWarrior>();
for(int i=0;i<10;i++)
 magi.add(new MagicWarrior());

ArrayList<ArcherWarrior> archers = new ArrayList<ArcherWarrior>();
for(int i=0;i<10;i++)
 archers.add(new ArcherWarrior());

boolean isMagicCooler = WarriorManager.fight(magi, archers); //ошибка компиляции!

И вот тут ты встречаешь ошибку компиляции и недоумеваешь, а что не так-то?

Все дело в том, что MagicWarrior – это наследник Warrior и его объекты можно передавать в метод fight(Warrior, Warrior)

А вот List<MagicWarior> это не наследник List<Warrior>. И передавать его туда нельзя!

- Как это не наследник?

- А вот так. То List и то – List. Пусть и с параметрами.

- Да, а я как-то сразу не обратил внимание. И что, есть уже решение этой проблемы?

- Ага. Для этого используется более сложная конструкция. Выглядит это так:

Пример 1
class WarriorManager
{
 public static boolean fight(Warrior w1, Warrior w2)
 {
  return w1.power > w2.power;
 }

 public static boolean fight(List<? extends Warrior> w1, List<? extends Warrior> w2)
 {
  return
 }
}

«? extends Warrior» обозначает «любой тип, унаследованный от Warrior ».

Т.е. туда можно передать список List<MagicWarrior> и список List<ArcherWarrior> и все будет работать.

- Так это же отлично. Чем меньше всяких таких проблем, тем лучше.

- Так и я о том же.

- А если мне не нужен наследник Warrior? Что если я хочу, чтобы в метод можно было передать любой List с любым параметром? Так можно?

- Да, для описания этой ситуации существует две записи:

 
List<? extends Object>
List<?>

Они эквивалентны по смыслу, поэтому обычно используют вторую.

У меня – все.

- Спасибо, Элли, действительно узнал сегодня много нового.

6. Задачи

Задачи
1. Простой generics

Параметризируйте класс Solution, чтобы он мог работать со всеми классами, которые наследуются от HashMap.
Метод getMap должен возвращать тип поля map.
2. Несколько суперклассов для дженерика

Дан класс Solution, параметризированный T. Ограничьте параметр T.
T должен быть наследником класса ClassForGenerics и одновременно реализовывать интерфейс InterfaceForGenerics.
Менять можно только класс Solution.

7. Generics: Class<T>

- Привет! Я продолжу лекцию Элли про Generic'м. Готов слушать?

- Ага.

- Тогда начинаем.

Факт первый. У методов класса тоже могут быть свои типы-параметры.

- Да, я знаю.

- Нет, я имею ввиду именно свои типы-параметры:

Пример
class Calcultor
{
 <T> T add(T a, T b); //сложить
 <T> T sub(T a, T b); //отнять
 <T> T mul(T a, T b); //умножить
 <T> T div(T a, T b); //делить
}

Это типы-параметры именно метода(ов). У класса параметров нет. Можно даже объявить методы статическими и вызывать их без использования объекта.

- Ясно. Смысл типов-параметров в методах такой же, как и в классах?

- Ага. Но есть и кое-что новое.

Как ты уже знаешь, в описании типа можно использовать wildcard. Тогда представь себе ситуацию:

Пример 1
public void doSomething(List<? extends MyClass> list)
{
 for(MyClass object : list)
 {
  System.out.println(object.getState()); //тут все работает отлично.
 }
}

А вот, что случится, если мы захотим добавить в коллекцию новый элемент:

Пример 2
public void doSomething(List<? extends MyClass> list)
{
 list.add(new MyClass()); //ошибка!
}

Дело в том, что в общем случае в метод doSomething можно передать List с типом элементов не MyClass, а любой из наследников MyClass. А в такой список заносить объекты MyClass уже нельзя!

- Ага. И что же делать?

- Ничего. Прямо в этой ситуации – ничего не сделаешь. Но это дало разработчикам Java повод для размышлений. И они придумали новое ключевое слово – super.

Выглядит его использование практически так же:

 
List<? super MyClass> list

Но между extends и super есть существенное различие.

«? extends T» обозначает, что класс должен быть наследником T.

«? super T» обозначает, что класс должен быть предком T.

- Ух ты. А где это используется?

- «? super T» используется, когда метод собирается добавлять в коллекцию объект типа T. Тогда это может быть коллекция типа T или любого типа-предка T.

- Ага. Ссылку на объект типа T можно же присвоить любому родительскому типу для T.

- Честно говоря – этот подход используется не очень часто. Тем более, что у него есть и обратная сторона. Пример:

Примеры
public void doSomething(List<? super MyClass> list)
{
 for(MyClass object : list) //ошибка!
 {
  System.out.println(object.getState());
 }
}
public void doSomething(List<? super MyClass> list)
{
 list.add(new MyClass()); //тут все работает отлично.
}

Теперь не работает первый пример.

Т.к. коллекция list может быть даже List<Object> (Object самый верхний родитель MyClass), то фактически мы пишем такой код, а так писать нельзя:

Пример 1
List<Object> list;

for(MyClass object : list) //ошибка!
{
 System.out.println(object.getState());
}

- Ясно. Спасибо за интересную лекцию.

- Пожалуйста.

8. Задачи

Задачи
1. Wildcards

Перепишите дженерики в методе add используя wildcards.
Логику не меняйте. Не оставляйте закомментированный код.
2. List to Map

Реализуйте логику метода convert в классе ConvertableUtil, который должен возвращать словарь, значениями которого являются элементы переданного Списка, а ключами являются объекты, полученные вызовом интерфейсного метода getKey.
Элементы Списка должны наследоваться от Convertable, который в свою очередь параметризирован каким-то ключом.
Например, ConvertableBook параметризирован String, т.е. ключ в результирующем словаре должен иметь тип - String ConvertableUser параметризирован Integer, т.е. ключ в результирующем словаре должен иметь тип - Integer Значения в словаре должны совпадать с элементами Списка.
Смотрите метод main для примера.
Расставьте в методе ConvertableUtil.convert дженерик типы.

9. Generics: ? wildcard

- Ну, и напоследок еще одна маленькая лекция по Generic.

Сейчас я тебе расскажу, как обходить «стирание типов» (Type erasure).

- Ага. Мне тоже хочется это знать.

- Как ты уже наверное знаешь, в Java есть тип Class, который используется, чтобы хранить ссылку на объект класса. Примеры:

Примеры
Class clazz = Integer.class;
Class clazz = String.class;
Class clazz = "abc".getClass();

- Ага.

Но вот, чего ты, наверное, не знаешь, так это того, что есть еще один класс Class, который является Generic'ом. И переменные этого Generic Class'а могут хранить только ссылки на тип, который был типом-параметром. Примеры:

Примеры
Class<Integer> clazz1 = Integer.class; //все отлично работает
Class<String> clazz2 = Integer.class; //ошибка компиляции.
Class<String> clazz1 = String.class; //все отлично работает
Class<String> clazz2 = int.class; //ошибка компиляции.
Class<? extends String> clazz1 = "abc".getClass(); //все отлично работает
Class<Object> clazz2 = "abc".getClass(); //ошибка компиляции.

- А почему оно так работает?

- Дело в том, что значение поля class у типа Integer (т.е. у Integer.class) – это на самом деле объект типа Class<Integer>.

Но, давай пойдем дальше.

Так вот, пользуясь тем фактом, что Class<T> - это Generic, и тем, что переменная его типа может хранить значение только типа T, можно сделать вот такую хитрую комбинацию:

Пример
class Zoo<T>
{
 Class<T> clazz;
 ArrayList<T> animals = new ArrayList<T>

 Zoo(Class<T> clazz)
 {
  this.clazz = clazz;
 }

 public T createNewAnimal()
 {
  T animal = clazz.newInstance();
  animals.add(animal);
  return animal
 }
}
Как это использовать
Zoo<Tiger> zoo = new Zoo<Tiger>(Tiger.class); // вот тут передается тип!
Tiger tiger = zoo.createNewAnimal();

Это не мега хитрая комбинация – мы просто передаем ссылку на нужный тип. Но, если бы мы просто пользовались Class вместо Class<T>, то кто-то мог по ошибке передать туда два разных типа – один в качестве параметра T, другой – в конструктор.

- Ага. Понимают. Ничего сверхъестественного не произошло, но и страшного тоже. Ссылка на тип есть, пользоваться ей можно, работает и ладно.

- Вот, слышу слова «не мальчика, но мужа!» Работает и ладно – это часто самый оптимальный вариант.

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

Именно десятки тысяч популярных отлаженных библиотек – это сегодня самый весомый аргумент в пользу Java. Так что, сохраняя обратную совместимость, Java остается самым популярным языком, и поэтому не может внедрять радикальные новшества.

- А я сделаю свою Java с блекджеком и …

- Ладно, я уже подустал за день. Давай до свидания.

- До свидания, Риша, и спасибо за такой интересный урок.

10. Задачи

Задачи
1. extends vs super

Логика всех методов - добавить source в destination.
!!!Расставьте ?, extends и super где необходимо:!!!
1) one - должен работать с одним и тем же типом;
2) two - должен добавлять любых наследников типа T в список, умеющий хранить только тип T;
3) three - должен добавлять объекты типа T в любой список, параметризированный любым родительским классом;
4) four - должен добавлять любых наследников типа T в список, параметризированный любым родительским классом.
Не оставляйте закомментированный код.
2. ClassLoader - что это такое?

Реализуйте логику метода getAllAnimals.
Аргумент метода pathToAnimals - это абсолютный путь к директории, в которой хранятся скомпилированные классы.
Путь не обязательно содержит / в конце. НЕ все классы наследуются от интерфейса Animal.
НЕ все классы имеют публичный конструктор без параметров.
Только для классов, которые наследуются от Animal и имеют публичный конструктор без параметров, - создать по одному объекту.
Добавить созданные объекты в результирующий сет и вернуть.
Метод main не участвует в тестировании.

11. Учимся гуглить

- Привет, Амиго!

Продолжаем наши уроки – учимся гуглить.

Вот тебе несколько заданий:

 Погугли
1 Документация по SVN
2 Как настроить SVN
3 Как настроить TortoiseSVN
4 Как создать репозиторий на GitHub
5 Как настроить TortoiseGit
6 Как добавить свой проект в репозиторий на GitHub?
7 Что такое wildcard в generic'ах?
8 Как узнать тип типа-параметра в generic'ах в java?
9 Как создать объект типа-параметра в Generic Java?
10 Как определить тип generic-параметра с помощью Reflection в Java?

12. Профессор дает доп. материал

- Привет, Амиго!

Вот тебе дополнительный материал по теме.

Ссылка на дополнительный материал

13. Хулио

- Привет, Амиго!

- Привет, Хулио.

- Что нового?

- Вот думаю, как решить очередную задачу.

- Оно тебе надо? Как по мне, то лучше не работать и быть бедным, чем работать и быть бедным. Давай лучше посмотрим что - нибудь интересное.

- Есть время учиться, а есть время и для отдыха. Включай.

Оригинал видео на YouTube

14. Вопросы к собеседованию по этой теме

- Привет, Амиго!

 Вопросы к собеседованиям
1 Какие системы контроля версий вы знаете?
2 Чем отличаются SVN и Git?
3 Что такое GitHub? У вас есть проекты на GitHub?
4 Зачем нужны системы контроля версий?
5 Что такое generic? Как они реализованы в Java?
6 Что такое стирание типов?
7 Расскажите про extends и super в Generic'ах?
8 Что такое wildcard?
9 Как использовать wildcard?
10 В чем отличие ArrayList и ArrayList<?>

15. Большая задача

- Привет, боец!

- Поздравляю тебя с повышением уровня квалификации. Нам нужны отчаянные парни.

- Уверен, у тебя есть еще много нерешенных задач. Самое время решить парочку из них!