Ответ
Из всех нововведений, появившихся в Java 8, нужно отметить самые важные:
стримы, лябмды и дефолтные/функциональные методы в интерфейсах.
Будет большим плюсом, если к этому прибавить такие фичи, как :
- Ссылки на методы и конструкторы -> Integer::valueOf, System.out::print
- Функциональные интерфейсы -> Предикаты (Predicate), Функции (Function), Поставщики (Supplier), Потребители (Consumer)
- Опциональные значения (Optional)
P.S. Если вы знаете, что есть некоторые фичи, но не можете их объяснить, лучше не упоминайте их в ответе.
Ответ
Функциональный интерфейс в Java – это интерфейс, который содержит только 1 абстрактный метод.
Основное назначение – использование в лямбда выражениях и method reference.
Стандартные функциональные интерфейсы, появившиеся в Java 8:
- Predicate - проверяет соблюдение некоторого условия. boolean test(T t);
- Consumer - выполняет некоторое действие над объектом типа T, при этом ничего не возвращая. void accept(T t);
- Function<T,R> - представляет функцию перехода от объекта типа T к объекту типа R. R apply(T t);
- Supplier - не принимает никаких аргументов, но должен возвращать объект типа T. T get();
- UnaryOperator - принимает в качестве параметра объект типа T, выполняет над ними операции и возвращает результат операций в виде объекта типа T. T apply(T t);
- BinaryOperator - принимает в качестве параметра два объекта типа T, выполняет над ними бинарную операцию и возвращает ее результат также в виде объекта типа T. T apply(T t1, T t2);
Ответ
Да, можно. Так как условием функционального интерфейса является наличие одного абстрактного метода, он вполне может иметь дефолтные и статические методы.
Ответ
Stream API — это новый способ работать со структурами данных в функциональном стиле. Stream (поток) API (описание способов, которыми одна компьютерная программа может взаимодействовать с другой программой) — это по своей сути поток данных. Поток представляет последовательность элементов и предоставляет различные методы для произведения вычислений над данными элементами.
Параметрами стримы принимают функциональные интерфейсы, описанные в предыдущем вопросе.
Ответ
Java Stream API предлагает два вида методов:
- Конвейерные — возвращают другой stream, то есть работают как builder,
- Терминальные — возвращают другой объект, такой как коллекция, примитивы, объекты, Optional и т.д.
Терминальные операции завершают работу над стримом, после них нельзя применять методы.
Метод stream | Описание | Пример |
---|---|---|
filter | Отфильтровывает записи, возвращает только записи, соответствующие условию | collection.stream().filter(«a1»::equals).count() |
skip | Позволяет пропустить N первых элементов | collection.stream().skip(collection.size() — 1).findFirst().orElse(«1») |
distinct | Возвращает стрим без дубликатов (для метода equals) | collection.stream().distinct().collect(Collectors.toList()) |
map | Преобразует каждый элемент стрима | collection.stream().map((s) -> s + "_1").collect(Collectors.toList()) |
peek | Возвращает тот же стрим, но применяет функцию к каждому элементу стрима | collection.stream().map(String::toUpperCase).peek((e) -> System.out.print("," + e)).collect(Collectors.toList()) |
limit | Позволяет ограничить выборку определенным количеством первых элементов | collection.stream().limit(2).collect(Collectors.toList()) |
sorted | Позволяет сортировать значения либо в натуральном порядке, либо задавая Comparator | collection.stream().sorted().collect(Collectors.toList()) |
mapToInt, mapToDouble, mapToLong | Аналог map, но возвращает числовой стрим (то есть стрим из числовых примитивов) | collection.stream().mapToInt((s) -> Integer.parseInt(s)).toArray() |
flatMap, flatMapToInt, flatMapToDouble, flatMapToLong | Похоже на map, но может создавать из одного элемента несколько | collection.stream().flatMap((p) -> Arrays.asList(p.split(",")).stream()).toArray(String[]::new) |
Метод stream | Описание | Пример |
---|---|---|
findFirst | Возвращает первый элемент из стрима (возвращает Optional) | collection.stream().findFirst().orElse(«1») |
findAny | Возвращает любой подходящий элемент из стрима (возвращает Optional) | collection.stream().findAny().orElse(«1») |
collect | Представление результатов в виде коллекций и других структур данных | collection.stream().filter((s) -> s.contains(«1»)).collect(Collectors.toList()) |
count | Возвращает количество элементов в стриме | collection.stream().filter(«a1»::equals).count() |
anyMatch | Возвращает true, если условие выполняется хотя бы для одного элемента | collection.stream().anyMatch(«a1»::equals) |
noneMatch | Возвращает true, если условие не выполняется ни для одного элемента | collection.stream().noneMatch(«a8»::equals) |
allMatch | Возвращает true, если условие выполняется для всех элементов | collection.stream().allMatch((s) -> s.contains(«1»)) |
min | Возвращает минимальный элемент, в качестве условия использует компаратор | collection.stream().min(String::compareTo).get() |
max | Возвращает максимальный элемент, в качестве условия использует компаратор | collection.stream().max(String::compareTo).get() |
forEach | Применяет функцию к каждому объекту стрима, порядок при параллельном выполнении не гарантируется | set.stream().forEach((p) -> p.append("_1")); |
forEachOrdered | Применяет функцию к каждому объекту стрима, сохранение порядка элементов гарантирует | list.stream().forEachOrdered((p) -> p.append("_new")); |
toArray | Возвращает массив значений стрима | collection.stream().map(String::toUpperCase).toArray(String[]::new); |
reduce | Позволяет выполнять агрегатные функции на всей коллекцией и возвращать один результат | collection.stream().reduce((s1, s2) -> s1 + s2).orElse(0) |
P.S. У вас не попросят перечислить все методы, но могут спросить любой из них. Поэтому желательно выучить как можно больше.
Ответ
Способ создания стрима | Шаблон создания | Пример |
---|---|---|
1. Классический: Создание стрима из коллекции | collection.stream() | Collection collection = Arrays.asList("a1", "a2", "a3"); Stream streamFromCollection = collection.stream(); |
2. Создание стрима из значений | Stream.of(значение1,… значениеN) | Stream streamFromValues = Stream.of("a1", "a2", "a3"); |
3. Создание стрима из массива | Arrays.stream(массив) | String[] array = {"a1","a2","a3"}; Stream streamFromArrays = Arrays.stream(array); |
4. Создание стрима из файла (каждая строка в файле будет отдельным элементом в стриме) | Files.lines(путь_к_файлу) | Stream streamFromFiles = Files.lines(Paths.get("file.txt")) |
5. Создание стрима из строки | «строка».chars() | IntStream streamFromString = "123".chars() |
6. С помощью Stream.builder | Stream.builder().add(...)....build() | Stream.builder().add("a1").add("a2").add("a3").build() |
7. Создание параллельного стрима | collection.parallelStream() | Stream stream = collection.parallelStream(); |
8. Создание бесконечных стрима с помощью Stream.iterate | Stream.iterate(начальное_условие, выражение_генерации) | Stream streamFromIterate = Stream.iterate(1, n -> n + 1) |
9. Создание бесконечных стрима с помощью Stream.generate | Stream.generate(выражение_генерации) | Stream streamFromGenerate = Stream.generate(() -> "a1") |
Ответ
Кроме последовательных потоков Stream API поддерживает параллельные потоки. Распараллеливание потоков позволяет задействовать несколько ядер процессора (если целевая машина многоядерная) и тем самым может повысить производительность и ускорить вычисления. В то же время говорить, что применение параллельных потоков на многоядерных машинах однозначно повысит производительность - не совсем корректно. В каждом конкретном случае надо проверять и тестировать.
Чтобы сделать обычный последовательный поток параллельным, надо вызвать у объекта Stream метод parallel. Кроме того, можно также использовать метод parallelStream() интерфейса Collection для создания параллельного потока из коллекции.
Однако не все функции можно без ущерба для точности вычисления перенести с последовательных потоков на параллельные. Прежде всего такие функции должны быть без сохранения состояния и ассоциативными, то есть при выполнении слева направо давать тот же результат, что и при выполнении справа налево, как в случае с произведением чисел. Например:
Stream<String> wordsStream = Stream.of("мама", "мыла", "раму");
String sentence = wordsStream.parallel().reduce("Результат:", (x,y)->x + " " + y);
System.out.println(sentence);
Результатом этой функции будет консольный вывод:
Результат: мама Результат: мыла Результат: раму
Данный вывод не является правильным. Если же мы не уверены, что на каком-то этапе работы с параллельным потоком он адекватно сможет выполнить какую-нибудь операцию, то мы можем преобразовать этот поток в последовательный посредством вызова метода sequential():
Stream<String> wordsStream = Stream.of("мама", "мыла", "раму", "hello world");
String sentence = wordsStream.parallel()
.filter(s->s.length()<10) // фильтрация над параллельным потоком
.sequential()
.reduce("Результат:", (x,y)->x + " " + y); // операция над последовательным потоком
System.out.println(sentence);
И возьмем другой пример:
Stream<Integer> numbersStream = Stream.of(1, 2, 3, 4, 5, 6);
Integer result = numbersStream.parallel().reduce(1, (x,y)->x * y);
System.out.println(result);
Фактически здесь происходит перемножение чисел. При этом нет разницы между 1 * 2 * 3 * 4 * (5 * 6) или 5 * 6 * 1 * (2 * 3) * 4. Мы можем расставить скобки любым образом, разместить последовательность чисел в любом порядке, и все равно мы получим один и тот же результат. То есть данная операция является ассоциативной и поэтому может быть распараллелена.
В то же время если рабочая машина не является многоядерной, то поток будет выполняться как последовательный.
P.S. - может показаться, что ответ получился чрезмерно развернутым, но вышло совсем наоборот - чтобы описание вышло наиболее компактным и понятным пришлось не включать в ответ такие темы как Вопросы производительности в параллельных операциях и Упорядоченность в параллельных потоках. О них можно почитать отдельно.
Условие
Имеется класс User:
public class User {
String firstName;
String lastName;
int age;
}
Реализуйте метод
public List<String> getUserDetail(List<User> users, int age){
// solution
}
по следующим критериям:
- filter by User.age >= provided age
- map to String like firstName + lastName
- order by alphabet
- return List of String
Решение
public static List<String> getUserDetail(List<User> users, int age) {
return users.stream()
.filter((User u) -> u.age >= age)
.map((User u) -> u.firstName + " " + u.lastName)
.sorted()
.collect(Collectors.toList());
}
Ответ
Коллекция - это объект, способный хранить группу одинаковых элементов. В пакете java.util содержится библиотека коллекций(collection framework), которая предоставляет большие возможности для работы с множествами, хэш-таблицами, векторами, разными видами списков и т.д.
Основу библиотеки составляют открытые интерфейсы, которые можно использовать для создания собственных коллекций. Каждый интерфейс объявляет набор методов, которые вы обязаны реализовать в своей программе:
Collection - группа элементов(охватывает Set и List);
Set - множество элементов(без дублирования);
SortedSet - то же самое, что Set, только элементы упорядочены;
List - упорядоченный список;
Map - словарь, то есть коллекция, в которой каждый элемент имеет уникальный ключ;
SortedMap - то же самое, что и Map, однако элементы упорядочены;
Queue - интерфейс для работы с очередью.
Разумеется, интерфейсы были бы "пустыми", если бы в них не существовало встроенных классов, реализующих необходимые функций:
ArrayList - список List как массив элементов;
LinkedList - список List, выполняющий функции связанного списка;
HashSet - множество Set как хэш-таблица;
TreeSet - множество SortedSet, используемое как дерево;
HashMap - индексированный словарь хэш;
TreeMap - коллекция SortedMap древовидной структуры.
Задача каждого из интерфейсов - обеспечить простоту и удобство работы с большим количеством однотипных данных. Рассмотрим подробнее назначение каждого из этих интерфейсов.
Collection - общий интерфейс, объединяющий интерфейсы Set и List. Содержит методы для добавления и удаления элементов коллекции, проверки их правильности, наличия и другие.
Set - неупорядоченный набор неповторяющихся элементов. Расширяет интерфейс Collection. Если производится попытка добавить в набор элемент, который уже в нем содержится, она будет проигнорирована.
List - служит для работы с упорядоченными коллекциями. К каждому элементы такой коллекции можно обратиться по индексу. Расширяет интерфейс Collection.
Map - предназначен для работы с коллекциями-словарями, в которых содержатся ключи и соответствующие им значения(каждому ключу соответствует только одно значением). Словарь может содержать произвольное число элементов.
Queue - содержит методы для работы с очередями: в них элементы добавляются с одного конца, а извлекаются с другого.
Чем отличается ArrayList от LinkedList? Сравните операции вставки в начало, середину и конец коллекции. Time complexity в лучшем и худшем случаях?
Ответ
ArrayList реализован внутри в виде обычного массива. LinkedList реализован внутри по-другому. Он реализован в виде связного списка: набора отдельных элементов, каждый из которых хранит ссылку на следующий и предыдущий элементы.
При операции вставки в начало или середину ArrayList-a Все элементы, находящиеся справа от указанного индекса, будут сдвигаться на одну позицию вправо (index+1). И только после этого во внутренний массив под указанным индексом добавляется новый элемент. Сложность такой операции O(n), так как нам придется перебрать все элементы, которые мы сдвигаем. При вставке в конец списка нет необходимости сдвигать элементы массива, поэтому сложность будет O(1), но - если мы достигли конца массива то создается новый массив побольше, куда копируются элементы исходного массива. Значит в худшем случае вставка в начало ArrayList-a - O(n).
У каждого элемента LinkedList-a, которые он хранит, имеется ссылка на предыдущий и следующий элемент, но для
того чтобы такую структуру хранить, LinkedList всегда запоминает крайние элементы (head и tail) - т.е. первый и последний
элемент. Для вставки в начало и конец требуется лишь поменять ссылки этих элементов, поэтому сложность
всегда будет константой O(1). Что касается вставки в середину LinkedList-a - то нам потребуется методом перебора
найти в цепочке элементов нужный нам индекс по ссылке. Сложность вставки в середину - всегда O(n).
P.S. - Хоть вопрос затрагивал лишь операцию вставки, нужно также понимать как происходят поиск и удаление по индексу у ArrayList и LinkedList.
Ответ
В разработке...
Ответ
В разработке...
Ответ
В разработке...