Лямбда – выражение в программировании — специальный синтаксис для определения функциональных объектов, заимствованный из λ-исчисления. То есть, используя функциональные объекты, можно объявлять функции в любом месте кода. Stream API в Java8 используется для работы с коллекциями, позволяя писать код в функциональном стиле.
Использование λ–выражения в Java дает возможность программисту внедрять функциональное программирование в парадигме объектно-ориентированного. Лямбда выражения -позволяют писать быстрее и делают код более ясным. В мире Java они появились в 8 версии и не остались незамеченными опытными программистами. Их очень хорошо применять Stream API.
Еще немного теории о лямбда выражениях:
- Лямбда-выражение является блоком кода с параметрами;
- Используйте лямбда-выражение,когда хотите выполнить блок кода в более поздний момент времени;
- Лямбда-выражения могут быть преобразованы в функциональные интерфейсы;
- Лямбда-выражения имеют доступ к final переменным из охватывающей области видимости;
- Ссылки на метод и конструктор ссылаются на методы или конструкторы без их вызова;
- Теперь вы можете добавить методы по умолчанию и статические методы к интерфейсам,которые обеспечивают конкретные реализации;
- Вы должны разрешать любые конфликты между методами по умолчанию из нескольких интерфейсов;
Для Stream есть два режима: последовательный и параллельный. Это позволяет задействовать возможности многоядерных процессоров. Коллекции используют fork/join параллелизм для разбиения работы на части.
Для того, чтобы была возможность работать со стрим API нужно выполнить такой алгоритм:
- создать stream;
- выполнить цепочку операций с объектами stream;
- выполнить терминальную операцию(объединение результата в коллекцию, агрегатная функция и т.д).
Создать стрим можно несколькими способами. Самые популярные из них будут в примерах:
- collection.stream()…
- Stream.of(значение1,… значениеN)…
- Arrays.stream(массив)…
- Files.lines(путь_к_файлу)…
- «строка».chars()…
- Stream.builder().add(…)….build()…
- collection.parallelStream()…
- Stream.iterate(начальное_условие, выражение_генерации)…
- Stream.generate(выражение_генерации)…
Пример:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class LambdaAndStreamAPI {
static Map<Integer, String> map = new HashMap<>();
static {
map.put(1, "User1");
map.put(2, "User2");
map.put(3, "User3");
}
public static void main(String[] args) {
map.forEach((k, v) -> System.out.println(k + " " + v));//обход мапы уже не такой громоздкий
List<String> list = new ArrayList<>();
list.stream().filter("a"::equals);//одно и то же
Stream.of(list).filter("a"::equals);//просто разные способы
System.out.println("abc4".chars().count());//вот, как можно быстро работать со строками
int[] array = {3,5};
System.out.println(Arrays.stream(array).average());//для масивов не нужно писать отдельных методов
}
}
В примере выше мы просто рассмотрели, как можно вызвать работу стрим API. У него очень много полезных методов, которые могут заменить написание громоздкого кода. Например, теперь чтобы пройтись по мапе нужно написать всего одну строчку кода: map.forEach((k, v) -> System.out.println(k + “=” + v));
Вот метод, который удаляет элемент списка
public List<Integer> removeEl(List<Integer> list, Integer el) {
list.removeIf(i -> i.equals(el));
return list;
}
Как по мне – это очень удобный механизм. Как я уже говорил: методов очень много приведу список самых популярных, по некоторым сделаю пример.
- filter – отфильтровывает записи, возвращает только записи, соответствующие условию. Пример: collection.stream().filter(«a1»::equals).count();
- skipa – позволяет пропустить N первых элементов. Пример: list.stream().skip(list.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); - 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);
- sum – возвращает сумму всех чисел. Пример: collection.stream().mapToInt((s) -> Integer.parseInt(s)).sum();
- average – возвращает среднее арифметическое всех чисел. Пример: collection.stream().mapToInt((s) -> Integer.parseInt(s)).average();
- mapToObj – преобразует числовой стрим обратно в объектный. Пример: intStream.mapToObj((id) -> new Key(id)).toArray();
- isParallel – узнать является ли Stream параллельным parallel вернуть параллельный стрим, если он уже параллельный, то может вернуть самого себя;
- sequential – вернуть последовательный стрим, если он уже последовательный, то может вернуть самого себя.
Теперь реальные жизненные ситуации использования Stream API и лямбда выражений.
Для этого я создал класс User с набором стандартных полей пользователя и заполнил коллекции тестовыми юзерами:
* обычный класс пользователь с конструктором по умолчанию
* сгенерированными методами equals, hashcode
* @author admin
*
*/
public class User {
private int id;
private String name;
private int age;
private String sex;
public User() {
// TODO Auto-generated constructor stub
}
public User(int id, String name, int age, String sex) {
super();
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((sex == null) ? 0 : sex.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (age != other.age)
return false;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (sex == null) {
if (other.sex != null)
return false;
} else if (!sex.equals(other.sex))
return false;
return true;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
import java.util.List;
import java.util.stream.Collectors;
public class Main {
private static List<User> users = new ArrayList<>();
static {// создаем наших юзеров и заполняем ими коллекцию
User user1 = new User(1, "Ivan", 23, "man");
User user2 = new User(2, "Olga", 45, "woman");
User user3 = new User(3, "John", 12, "man");
User user4 = new User(4, "Vitaliy", 25, "man");
users.add(user1);
users.add(user2);
users.add(user3);
users.add(user4);
}
/**
* Что если мы хотим получить данные по некоторым условиям?
* Не проблема: используем функцию filter и возвращаем коллекцию
* @param bottomAge
* @param topAge
* @param sex
* @return
*/
public static List<User> getUsersByAgeRangeAndSex(int bottomAge, int topAge, String sex) {
List<User> usersByAgeAndSex = users.stream()
.filter((p) -> p.getAge() > bottomAge && p.getAge() < topAge && p.getSex().equals(sex))
.collect(Collectors.toList());
return usersByAgeAndSex;
}
/**
* Функция average(), совместно с filter позволяют нам
* получить среднее значение по некоторым полям и по фильтрам в одну строчку
* @return
*/
public static double getMensAverage() {
return users.stream().filter((p) -> p.getSex().equals("man")).mapToInt(User::getAge).average().getAsDouble();
}
/**
* Если нужно использовать несколько фильтров, можете смело это делать
* можно добавить сколько условий, сколько этого требует задача
* @return
*/
public static int findCountOfWorkingPeople() {
return (int) users.stream().filter((p) -> p.getAge() >= 18).filter(
(p) -> (p.getSex().equals("woman") && p.getAge() < 55) || (p.getSex().equals("man") && p.getAge() < 60))
.count();
}
/**
* Удалить елемент из списка можно в одну строчку безо всякого труда
* @param user
* @return
*/
public static List<User> removeUser(User user) {
users.removeIf(i -> i.equals(user));
return users;
}
public static void main(String[] args) {
System.out.println(getUsersByAgeRangeAndSex(18, 30, "man"));
System.out.println(getMensAverage());
System.out.println(findCountOfWorkingPeople());
System.out.println(removeUser(users.get(1)));
}
}
Эти примеры – только малая часть, что можно делать с помощью лямбда выражений и Stream API. Я советую Вам посмотреть все методы и запомнить для себя наиболее полезные на Ваш взгляд. Например, как удаление элемента из списка.
На этом, думаю, пора заканчивать. Не забудьте поделиться этой статьей с друзьями и знакомыми (если, конечно, она Вам понравилась).
static {
List users = Arrays.asList(new User(1, “Ivan”, 23, “man”),
new User(2, “Olga”, 45, “woman”),
new User(3, “John”, 12, “man”),
new User(4, “Vitaliy”, 25, “man”) );
}
* вместо
private static List users = new ArrayList();
static {// создаем наших юзеров и заполняем ими коллекцию
User user1 = new User(1, “Ivan”, 23, “man”);
User user2 = new User(2, “Olga”, 45, “woman”);
User user3 = new User(3, “John”, 12, “man”);
User user4 = new User(4, “Vitaliy”, 25, “man”);
users.add(user1);
users.add(user2);
users.add(user3);
users.add(user4);
}