пятница, 21 апреля 2017 г.

Функциональное программирование в Java 8 (Часть 4): Splitter

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

Пример: Разделение stream, используя специальный фильтр

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

Обычныи (и медленный) подход

public <T> void splitAndPerform(Collection<T> items, Predicate<T> splitBy, Consumer<T> passed, Consumer<T> notPassed) {
    items.stream()
        .filter(splitBy)
        .forEach(passed);
    items.stream()
        .filter(splitBy.negate())
        .forEach(notPassed);
}
Решение работает, но достаточно медленно. Сложность  выполнения O(2n), т.к. мы проходим по коллекции два раза:  один проход, для тех элементов, которые удовлетворяют условиям, второй, для тех, которые не удовлетворяют.
Но что, если мы сами созданим разделитель. Он сортирует элементы коллекции, в зависимости от того, проходят ли они условия или нет, разделит их на 2 списка. Это позволит снизить сложность выполнения до О(n), т.к. проход по списку происходит один раз. Давайте сделаем это.

Более быстрый подход.

1. Разделение коллекции.
Как вы уже догадались, сначала нужно создать разделение.
В нашей функции splitBy(), мы хотим получить Predicate<T> в качестве парамета и вернуть новый объект Splitter, каоторый представляет из себя объект из двух списков. Один список содержит объекты, которые подошли фильру, второй - которые не подошли.
public class Splitter<T> {
    private List<T> passed;
    private List<T> notPassed;
    private Splitter(List<T> passed, List<T> notPassed) {
        this.passed = passed;
        this.notPassed = notPassed;
    }
    public static <T> Splitter<T> splitBy(Collection<T> items,Predicate<T> test) {
        List<T> passed = new LinkedList<T>();
        List<T> notPassed = new LinkedList<T>();
        items.stream()
                .forEach(item -> {
                    if(test.test(item)){
                        passed.add(item);
                        return;
                    }
                    notPassed.add(item);
                });
        return new Splitter<T>(passed, notPassed);
    }
}
Как вы заметили, мы использовали паттер Фабрика для создания Splitter. Теперь надо создать сам объект и наделить его функционалом.
2. Работа с разделенными списками.
Мы хотим работать со списками таким же способом, как со Stream. Но мы не хотим пересоздавать весь функционал, котоый есть у Stream, для наших списков. Здесь нам пригодится паттерн, я узнал о нем из этого видео и это очень клевый способ использовать лямбды. Фактически, мы создаем две функции, workOnPassedItems и workOnNotPassedItems. Они принимают Consumer<Stream<T>>. Следовательно, мы можем создавать лямды  и работать внутри нее с нормальным потоком. Этот метод будет применен к обоим спискам.

public class Splitter<T> {
    //...
    public Splitter<T> workWithPassed(Consumer<Stream<T>> func) {
        func.accept(passed.stream());
        return this;
    }
    public Splitter<T> workWithNotPassed(Consumer<Stream<T>> func) {
        func.accept(notPassed.stream());
        return this;
    }
}
Мы использовали шаблон Каскад, чтобы сделать использование различных методов более приятным. Вы увидите это в примере ниже.
В общем-то этои есть наш разделитель (Splitter). Рассмотрим несколько примеров использования.

Пример 1. Вывод числе и возведение в квадрат всех нечетных

В нашем первом примере мы хотим оперировать со списком чисел. Мы хотим печатать простые числа и возвести в квадрат все нечетные перед тем как их напечатать. Сначала мы раделяем числа не четные и нечетным. Далее работаем с каждым списком как описывалось.

public void workOnNumbers() {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Splitter.splitBy(numbers, num -> num%2 == 0)
            .workWithPassed(passed ->
                    passed.forEach(even -> System.out.println("" + even + " -> " + even)))
            .workWithNotPassed(notPassed ->
                        notPassed.forEach(odd -> System.out.println(odd + " -> " + (odd * odd))
                    ));
}

Пример 2. Отправка всем победителям уведомление и проигравшим другое

У нас есть список кандидатов. У все есть метода hasWon(), который возвращает boolean. Мы хотим разделить список на победителей и проигравших. Далее мы хотим разослать всем уведомления о победе или поражении. 

public void sendEmails(List<Candidates> candidates) {
    Splitter.splitBy(candidates, Candidates::hasWon)
            .workWithPassed(winners ->
                    winners.forEach(winner -> Email.send(winner.getEmail(), "You won!"))
    )
    .workWithNotPassed(losers ->
            losers.forEach(loser -> Email.send(loser.getEmail(), "You lost, sorry!"))
    );
}

об использовании partitioningBy

Получая обратную связь на данную статью, выявилось, что  Stream.collect(Collectors.partitioningBy(Predicate<T> test)) подходит нашему случаю и я с этим полностью согласен.
Происходит разделение Stream, в зависимости от test. Т.о. для нас map будет выглядеть как то так {true: passed, false: notPassed}. Далее мы получаем два списка из map и продолжаем. Новый метод будет выглядеть так:

public static <T> Splitter<T> splitBy(Collection<T> items,Predicate<T> test) {
        Map<Boolean, List<T>> map = items.stream()
                .collect(Collectors.partitioningBy(test));
        return new Splitter<T>(map.get(true), map.get(false));
}
И я должен признать, что данный метод выглядит лучше.

Какие права на существования у Splitter

Цель Splitter - продемонстировать как вы можете работать в функцией, как с объектом. Его цель не в том, чтобы заменить методы в JDK. Это класс для изучения и экспериментов. Если вы хотите эксперементировать - делайте это. Пожалуйста, оставьте комментарий к тому, что вы узнали или где вы оптимизировали класс, чтобы другие могли учиться на этом. Так же мы рассмотрели несколько паттернов. Они помогают сделать синтекс более приятным.

Вывод

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

PS это мой перевод данной статьи

воскресенье, 9 апреля 2017 г.

Функциональное программирование в Java 8 (Часть 3): Stream

Сегодня мы будем разбирться со Stream, которые вы используете, как функциональную альтернативу работая с коллекциями. Некоторые методы мы уже видели, когда рассматривали Optionals.

Когда мы используем Stream

Вы можете спросить, чем плох текущий способ хранения набора объектов. Почему нельзя продалжить использовать List,Set и другие?
Я хотел бы пояснить: с ними все в порядке. Но когда вы хотите работать в функциональном стиле, вы должны рассматривать возможность работать с ними (со Stream). Стандартный способ работы - перевести структуру данных в Stream. Далее, вы работаете с ними в функциональном стиле и в конце переводите в структуру данных по своему выбору. По этому мы научимся переводить наиболее популярные структуры данных в Stream.

Почему мы используем Stream

Stream - новый прекрасный способ работать с коллекциями данных. Они были представлены в Java 8. Одна из многих причин, почему вы должны их использовать - паттерн каскад, который используется в Stream. Это значит, что почти каждый метод Stream снова возвращает Stream, так что вы можете продолжить работать с ним. В следющей секции вы увидите, как можно использовать Stream, и это сделает код красивее. 
Stream так же неизменяемы. Так что каждый раз, когда вы изменяете его, создается новый Stream. Еще одно из преимуществ использования Stream, что они уважают особенности ФП. Если вы переведете структуру данных в Stream и будуте работать с ней, в итоге исходные данные не изменятся. Так что никаких побочных эффектов.

Как перевести структуру данных в Stream

Перевод набор объектов в Stream
Если вы хотите перести набор в Stream - можно использовать метод Stream.of():
public void convertObjects() {
    Stream<String> objectStream = Stream.of("Hello", "World");
}
Перевод List, Set, Array и др.
К счастью в Оракл продумали реализацию Stream в Java8. В каждый класс, который реализует  java.util.Collection<T>, добавили метод stream(), который переводит коллекцию в Stream. Массивы, так же легко могут быть переведны в Stream, с помощью Arrays.stream(array). Все так просто. 
public void convertStuff() {
    String[] array = {"apple", "banana"};
    Set<String> emptySet = new HashSet<>();
    List<Integer> emptyList = new LinkedList<>();
    Stream<String> arrayStream = Arrays.stream(array);
    Stream<String> setStream = emptySet.stream();
    Stream<Integer> listStream = emptyList.stream();
}
Всеравно, обычно, вы не будете хранить Stream в объекте. Вы будете его использовать и после этого переводить в необходимую структуру.

Работа со Stream

Как я уже сказал, Stream - это способ работать со структурой данных в функциональном стиле. А сейчас мы рассмотрим самый часто встречаемые методы. 

Уже изученные методы

Map
Все довольно просто. Вместо того что работать с один элементом, мы работаем со всеми элементами в Stream. Если мы хотим возвести в квадрат каждое число, то мы можем использовать Map, вместо того, чтобы писать функцию для List:

public void showMap() {
    Stream.of(1, 2, 3)
        .map(num -> num * num)
        .forEach(System.out::println); // 1 4 9
}
flatMap
Мы используем flatMap, чтобы перейти от Stream<List<Integer>> к Stream<Integer>. В примере мы хотим сложить два List в один

public void showFlatMapLists() {
    List<Integer> numbers1 = Arrays.asList(1, 2, 3);
    List<Integer> numbers2 = Arrays.asList(4, 5, 6);
    Stream.of(numbers1, numbers2) //Stream<List<Integer>>
        .flatMap(List::stream)  //Stream<Integer>
        .forEach(System.out::println); // 1 2 3 4 5 6
}
Так же в примере используется foreEach, который я опишу ниже:
forEach
Метода forEach, как и ifPresent у Optional, у него есть побочный эффект. Данный метод можно использовать, чтобы вывести все элементы Stream. ForEach один из немногих методов, который не возвращает Stream (Примечание:это метод терминальный), так что он используется последним и только один раз. 
Вы должны быть осторожны при использовании forEach, потому что это вызывает побочные эффекты, которые мы не хотим получить. Поэтому подумайте дважды, если вы могли бы заменить его другим методом без побочных эффектов.(Примечание: возможно, автор намекает на то, что данный метод не подходит для использование в параллельном Stream)

public void showForEach() {
    Stream.of(0, 1, 2, 3)
        .forEach(System.out::println); // 0 1 2 3
}
Filter
Filter - это основной метод. Он принимате 'test' функцию, которая принимает значение и возвращает boolena. Т.о. она првоеряет каждый объект в Stream. Если он проходит тест - остается в Stream. Иначе - будет удален.
Тип 'test' функции - Function<T, Boolean>. В JavaDoc вы заметите, что тип test функции на самом деле - Predicate<T>. Но это короткая форма для всех функций, которые принимают один параметр и возвращают boolean. 

public void showFilter() {
    Stream.of(0, 1, 2, 3)
        .filter(num -> num < 2)
        .forEach(System.out::println); // 0 1
}
Функции могут сделать твою жизнь легче, особенно если использовать Predicate.negate() и Objects.nonNull()
Первый инверсирует результат test. Все объекты, которые не проходят оригинальный тест, проходят тест после инверсии и наоборот.
Второй метод используется как метод ссылка, чтобы избавиться от всех null элементов. Это поможет избежать NullPointerException, например, при применении функции map:
public void negateFilter() {
    Predicate<Integer> small = num -> num < 2;
    Stream.of(0, 1, 2, 3)
        .filter(small.negate()) // Все большие цифры проходят
        .forEach(System.out::println); // 2 3
}
public void filterNull() {
    Stream.of(0, 1, null, 3)
        .filter(Objects::nonNull)
        .map(num -> num * 2) // без фильтра получили бы NullPointerExeception
        .forEach(System.out::println); // 0 2 6
}
Collect
Как было сказано выше, вы можете перевести Stream в другую структура данных. Для этого и нужен Collect. Чаще всего вы будете приводить к List или Stream.

public void showCollect() {
    List<Integer> filtered = Stream.of(0, 1, 2, 3)
        .filter(num -> num < 2)
        .collect(Collectors.toList());
}
Но Collect можно использовать для большего. Например, чтобы собрать все в String. Так же в конце строки не будет раздражающего разделителя в конце строки.

public void showJoining() {
    String sentence = Stream.of("Who", "are", "you?")
        .collect(Collectors.joining(" "));
    System.out.println(sentence); // Who are you?
}
Шорткаты
Эти методы могут быть заменине комбинацией filter, map и collect, но на то они и шорткаты.
Reduce
Отличная функция! Она принимает начальный параметр T  и функцию типа BiFunction<T, T, T>. Если у вас BiFuction, у которой все  параметры типа Т, то шорткат для нее BinaryOperator<T>. Фактически она (функция Reduce) приводит все объекты Stream к одному. Вы можете сложить все строки в одну или просуммировать все числа и т.д. В данных примерах стартовым параметром будет пустая строка или 0. Данная функция поможет сделать ваш код более читаемым, если вы знаете, что она делает.

public void showReduceSum() {
    Integer sum = Stream.of(1, 2, 3)
        .reduce(0, Integer::sum);
    System.out.println(sum); // 6
}
Расмотрим, как reduce работает:

  • суммируем первое число...
  • со вторым ...
  • с третьим...
  • со входным параметром
Как вы заметили появляется длинная цепочка функций. В конце мы получим sum(1, sum 2,(sum 3,0))). Они будут вычеслены с права налево или  из нутри к наружу. По этой причине нам нужен начальный параметр.
Sorted
Вы можете использовать Stream, чтоб отсортировать. Объекту в Streamб необязательно даже реализовывать Comperable<T>, т.к. можно написать свой собственный  Comperator<T>. Это обычная BiFunction<T, T, int>, но Comperator это шорткат для всех BiFunctional, которые принимают 2 паарметра и возвращают int.И этот int как в compareTo(), говорит нам, что первый объект меньше, когда int < 0, равны, когда int == 0, и больше, когда int > 0. Функция сортировки будет оперировать этими int и отсортирует Stream.

public void showSort() {
    Stream.of(3, 2, 4, 0)
        .sorted((c1, c2) -> c1 - c2)
        .forEach(System.out::println); // 0 2 3 4
}
Другие виды Stream
Есть специальные виды Stream, которые содержать только цифры, у них есть свой набор методов. Расмотрим IntStream и Sum, но так же есть DoubleStream, LongStream  и д.р. Подробности в JavaDoc. Чтобы перевести обычный Stream в IntStream, необходимо использовать mapToInt. Она делает то же самое, что обычная map, но возвращает IntStream, конечно, можно передать mapToInt функцию, которая будет возвращать int. В данном примере будет рассмотрено, как суммровать числа без reduce:

public void sumWithIntStream() {
    Integer sum = Stream.of(0, 1, 2, 3)
        .mapToInt(num -> num)
        .sum();
}
Использование Stream в тестах
Рассмотрим методы anyMatch, но тоак же есть методы count, max и др. которые могут пригодиться при тестировании. anyMatch работает, как filter, но он сообщает, прошел ли какой-либо объект фильтрацию. Его можно использовать в assertTrue, чтобы проверить, есть ли у какого-нибудь обхект специфическое свойство. В следующем примере проверим, было ли определенное имя сохранено в БД.

@Test
public void testIfNameIsStored() {
    String testName = "Albert Einstein";
    Datebase names = new Datebase();
    names.drop();
    db.put(testName);
    assertTrue(db.getData()
        .stream()
        .anyMatch(name -> name.equals(testName)));
}
Большой пример
В этом примере мы хотим отправить сообщение каждому пользователю, у которого сегодня день рождения.
класс User
User определяется именем и датой рождения. День рождения будет в формате "день.месяц.год". В данном примере не будем производить никаких проверок.

public class User {
    private String username;
    private String birthday;
    public User(String username, String birthday) {
        this.username = username;
        this.birthday = birthday;
    }
    public String getUsername() {
        return username;
    }
    public String getBirthday() {
        return birthday;
    }
}
Чтобы хранить всех пользователей используем List. В настоящей программе List будет заменен БД.

public class MainClass {
    public static void main() {
        List<User> users = new LinkedList<>();
        User birthdayChild = new User("peter", "20.02.1990");
        User otherUser = new User("kid", "23.02.2008");
        User birthdayChild2 = new User("bruce", "20.02.1980");
        users.addAll(Arrays.asList(birthdayChild, otherUser, birthdayChild2));
        greetAllBirthdayChildren(users);
    }
    private static void greetAllBirthdayChildren(List<User> users) {
        // Next Section
    }
}
Поздравление
Теперь мы хотим поздравить именинников. Прежде всего необходимо отфильтровать всех пользователей, у которых сегодня день рождения. После этого мы должны сообщить об этом. Итак, давайте сделаем это. Я не буду реализовывть sendMessage(String message, User receiver), он просто должен отправлять поздравления

public static void greetAllBirthdayChildren(List<User> users) {
    String today = "20.02"; //Чтобы облегчить пример. В реальности необходимо использовать LocalDateTime.
    users.stream()
        .filter(user -> user.getBirthday().startsWith(today))
        .forEach(user -> sendMessage("Happy birthday, ".concat(user.getUsername()).concat("!"), user));
}
private static void sendMessage(String message, User receiver) {
    //...
}
Параллелизм
Stream могут выполняться параллельно! По умолчанию каждый Stream не параллельный. Чтобы его сделать таковым необходимо использовать parallelStream(). Это поможет выполняться вашим Stream быстрее, но необходимо быть аккуратнее с этим. Как рассказано здесь параллелизм, например, может испортить сортировку. Поэтому будьте готовы столкнуться с неприятными ошибками с parralelStream, хотя это может сделать вашу программу значительно быстрее.
Выводы
Вот и все на сегодня! Мы много узнали о Stream на Java. Мы узнали, как преобразовать структуру данных в поток, как работать со Stream и как преобразовать ваш поток обратно в структуру данных. Я представил наиболее распространенные методы и когда вы должны их использовать. В конце урока, мы проверили наши знания на более крупном примере, где мы поприветствовали всех детей в день рождения. В следующей части этой серии у нас будет большой пример, когда мы будем использовать Stream. Но я пока не буду рассказывать вам пример, так что надеюсь, вы будете удивлены.
PS это мой перевод данной статьи.

пятница, 7 апреля 2017 г.

Функциональное программирование в Java 8 (Часть 2): Optionals

Всем привет.

После того, как  в прошлой статье мы сделали наш первый большой шаг в ФП, в сегоднешней части поговорим об Optionals.

Зачем нужен Optional?

Если быть откровенным, вы так же думаете, что null раздражает? Для каждого объекта, если его значение может быть null, нужно проверять, null он или нет:

if(argument == null) {
    throw new NullPointerException();
}

Стремное решение. Этот шаблон раздумает код, так же про него можно легко забыть. Как это исправить?

Знакомство с Optionals

В Java 8, был представлен java.util.Optional<T>, чтобы обрабатыать объекты, которые не инициализированы. Это объект-контейнер, который может хранить другой объект. Дженерик T - тип объекта, который вы хотите хранить. 

Integer i = 5;
Optional<Integer> optinalI = Optional.of(i);

У класса Optional нет никаких публичных конструкторов. Чтобы создать optional, необходимо использовать Optional.of(object) или Optional.ofNullable(object).  Первый сособ используется, если объект никогда не будет null. Второй - если объект может быть null.

Как работает Optional

У Optional 2 сосотояния. Либо в нем хранится null, либо объект. Если хранится объект - optional называют существующим, если null - пустым.  Чтобы получить существующий объект можно использовать Optional.get(),  но нужно быть аккуратным, если вызвать к пустому объекту - будет исключение: NoSuchElementException. Чтобы проверить что объект существует нужно вызвать Optional.isPresent().

Примеры:

public void playingWithOptionals() {
    String s = "Hello World!";

    String nullString = null;

    Optional<String> optionalS1 = Optional.of(s); // Will work

    Optional<String> optionalS2 = Optional.ofNullable(s); // Will work too

    Optional<String> optionalNull1 = Optional.of(nullString); // -> NullPointerException

    Optional<String> optionalNull2 = Optional.ofNullable(nullString); // Will work

    System.out.println(optionalS1.get()); // prints "Hello World!"

    System.out.println(optionalNull2.get()); // -> NoSuchElementException

    if(!optionalNull2.isPresent()) {

        System.out.println("Is empty"); // Will be printed

    }

}

Частые проблемы при использовании Optional

1. Работа с Optional и null
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    if(first != null) {

        String value = first.get(); 

        //... 

    }

}
Так использовать Optional нельзя! Когда вы получаете Optional (В примере из DB), не нужно проверять значение объекта на null. Если в DB нет строки, она вернет Optional.empty(), а не null. Если получите пустой элемент, как в примере, всеравно будет исключение NoSuchElementException.
2. Использование isPresented() и get().
2.1 Использование значение, когда объект представлен.
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    if(first.isPresent()) {

        String value = first.get(); 

        //... 

    }

}
Как было уже сказано, необходимо быть на 100% уверенным, прежде, чем использовать Optional.get(). Вы не получите NoSuchElementException в обновленной функции. Но не нужно проверять isPresent() + get() вместе. Т.к. не нужно забывать про null,  сначала, нужно проверить first != null. И мы получаем тот же результат! И как же избавиться от этого надоедливого блока? Вот как:
public void workWithFirstStringInDB() {

    DBConnection dB = new DBConnection();

    Optional<String> first = dB.getFirstString();

    first.ifPresent(value -> /*...*/);

}
Метод Optional.ifPresent() наш новый лучший заменитель блока проверки. Он принимает Функцию, т.е. лямду или ссылку на метод, и выполняет ее, если объект существует. 
2.2 Возвращение исмененного объекта.
public Integer doubleValueOrZero(Optional<Integer> value) {

    if(value.isPresent()) {

       return value.get() * 2;

    }

    return 0;

}
В этом методе мы хотид удвоить объект, если он представлен. Иначе вернуть 0. Данный пример работает, но это не функциональный способ решения проблемы. У нас есть 2 функции, чтобы сделать этот метод красивее. Первая Optional.map(Function<T, R> mapper) и вторая Optional.orElse(T other). Мap берет функцию, применяет ее и возвращает результат опять обернутый в Optional, если объект пустой - будет возвращен пустой Optional. orElse вернет значение Optional, если оно есть или вернет взначение, котороек передано как параметр orElse(object). Учитывая это, функцию можно реализовать в одну строку.
public Integer doubleValueOrZero(Optional<Integer> value) {

    return value.map(i -> i * 2).orElse(0);

}

Когда использовать объект, который м.б. null, а когда Optional

Вы можете найти много книг, лекций и дискуссий о вопросе: использовать мне null или Optional в конкретной ситуации. Оба решения могут быть правильными. По ссылке можно найти хорошее правило, которое можно применять почти во всех случаях. Использвать Optional, когда существует явная необходимость показать, что результата нет или null может стать причиной ошибки.
Т.е. не нужно использовать Optional так:
public String defaultIfOptional(String string) {

    return Optional.ofNullable(string).orElse("default");

}
Т.к. проверка на null более читабельна:
public String defaultIfOptional(String string) {

    return (string != null) ? string : "default";

}
Вы должны использовать Optionals, как возвращаемое значение из функции. Не стоит создавать новые, чтобы создать классную цепочку методов, как в примере выше. В большинстве случаев null достаточно.

Вывод

Вот и все на сегодня! Мы разобрались с Optional. Это класс-контейнер  для других классов, который либо существует, либо пустой. Мы удалили некоторый общий кода, который поставлялся с Optionals, и  снова использовали функции как объекты. Мы также обсудили, когда следует использовать null и когда Optionals.

В следующей части мы будем использовать Streams как новый способ обработки коллекции объектов, таких как Iterables и Collections.

Спасибо за чтение и хорошего дня,

Никлас

ps это мой перевод данной статьи

четверг, 6 апреля 2017 г.

Java 8 forEach

Рассмотрим примеры forEach в java 8 для List и для Map

1. List.
Просто создадим список:
List<String> list = new ArrayList<String>()
        {{
            add("a");
            add("b");
            add("c");
            add("d");
            add("e");
        }};
1.1 До Java8:

for(String item : list){
 System.out.println(item);
}
1.2 Рассмотрим варианты вывода списк в Java 8:
list.forEach(l -> System.out.println(l));
Его можно упростить используя method reference
list.forEach(System.out::println);
Так же можно через stream:
list.stream().forEach(System.out::println);
2. Map Заполнение:
Map<String, Integer> map = new HashMap<String, Integer>()
            {{
                put("a", 1);
                put("b", 2);
                put("c", 3);
                put("d", 4);
                put("e", 5);
                put("f", 6);
            }};

2.1 до Java 8:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
 System.out.println("key : " + entry.getKey() + " value : " +
                     entry.getValue());
}
2.2 Java 8
map.forEach((String, Integer) -> System.out.println(
             "key : " + String + " value : " + Integer));