Java for Beginner
676 subscribers
544 photos
155 videos
12 files
835 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Что выведет код?

import java.util.Arrays;

public class Main {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
int[] subArr = Arrays.copyOfRange(arr, 1, 4);
subArr[0] = 10;
System.out.println(Arrays.toString(subArr));
System.out.println(Arrays.toString(arr));
}
}


#Tasks
А какая Алиса будет дальше?🤨

https://t.me/Java_for_beginner_dev

#Mems
Основные утилитные методы класса Arrays

1. Arrays.toString()
Метод toString() преобразует массив в строковое представление, что удобно для вывода его содержимого в лог или консоль. Этот метод поддерживает массивы любых типов, включая примитивные типы и объекты.
import java.util.Arrays;

public class ToStringExample {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
String arrayAsString = Arrays.toString(numbers);
System.out.println("Array: " + arrayAsString);
}
}


Вывод:
Array: [1, 2, 3, 4, 5]


2. Arrays.sort()
Метод sort() сортирует массив по возрастанию с использованием алгоритма быстрой сортировки (QuickSort) для примитивных типов и сортировки слиянием (MergeSort) для объектов. Этот метод чрезвычайно полезен для упорядочивания данных.
import java.util.Arrays;

public class SortExample {
public static void main(String[] args) {
String[] fruits = {"Banana", "Apple", "Cherry", "Date"};
Arrays.sort(fruits);
System.out.println("Sorted array: " + Arrays.toString(fruits));
}
}


Вывод:
Sorted array: [Apple, Banana, Cherry, Date]


3. Arrays.binarySearch()
Метод binarySearch() используется для поиска элемента в отсортированном массиве. Если элемент найден, метод возвращает его индекс; если нет — отрицательное значение, указывающее на предполагаемую позицию элемента в массиве.
import java.util.Arrays;

public class BinarySearchExample {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
int index = Arrays.binarySearch(numbers, 3);
if (index >= 0) {
System.out.println("Element found at index: " + index);
} else {
System.out.println("Element not found");
}
}
}


Вывод:

Element found at index: 2


4. Arrays.copyOf()
Метод copyOf() позволяет создать копию массива с новым размером. Этот метод полезен для изменения размера массива или создания его дубликата.
import java.util.Arrays;

public class CopyOfExample {
public static void main(String[] args) {
int[] original = {1, 2, 3};
int[] copy = Arrays.copyOf(original, 5);
System.out.println("Original array: " + Arrays.toString(original));
System.out.println("Copied array: " + Arrays.toString(copy));
}
}


Вывод:
Original array: [1, 2, 3]
Copied array: [1, 2, 3, 0, 0]
В этом примере новый массив copy содержит те же элементы, что и original, но его длина увеличена до 5. Остальные элементы заполняются значениями по умолчанию для типа данных (в данном случае нулями для целых чисел).


5. Arrays.equals()
Метод equals() проверяет, равны ли два массива по содержимому. Этот метод полезен при необходимости сравнения массивов, так как оператор == проверяет только равенство ссылок, а не содержимого массивов.
import java.util.Arrays;

public class EqualsExample {
public static void main(String[] args) {
int[] array1 = {1, 2, 3};
int[] array2 = {1, 2, 3};
boolean areEqual = Arrays.equals(array1, array2);
System.out.println("Arrays are equal: " + areEqual);
}
}


Вывод:
Arrays are equal: true


#Java #Training #Medium #Arrays
Сложные методы класса Arrays

1. Arrays.parallelSort()
Метод parallelSort() сортирует массив, используя многопоточность для повышения производительности на многоядерных процессорах. Этот метод особенно полезен для сортировки больших массивов, где использование параллелизма может значительно ускорить процесс.
import java.util.Arrays;

public class ParallelSortExample {
public static void main(String[] args) {
int[] largeArray = {5, 1, 4, 2, 3};
Arrays.parallelSort(largeArray);
System.out.println("Parallel sorted array: " + Arrays.toString(largeArray));
}
}


Вывод:
Parallel sorted array: [1, 2, 3, 4, 5]


2. Arrays.stream()
Метод stream() позволяет преобразовать массив в поток (Stream), что открывает доступ к богатому набору методов для обработки данных, таких как фильтрация, маппинг и редукция. Этот метод особенно полезен для выполнения сложных операций над массивами в функциональном стиле.
import java.util.Arrays;
import java.util.stream.IntStream;

public class StreamExample {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers);
int sum = stream.filter(n -> n % 2 == 0).sum();
System.out.println("Sum of even numbers: " + sum);
}
}


Вывод:
Sum of even numbers: 6
В этом примере метод stream() преобразует массив в поток, который затем используется для фильтрации четных чисел и их суммирования.


3. Arrays.deepEquals()

Метод deepEquals() сравнивает вложенные массивы (например, двумерные массивы), обеспечивая корректное сравнение их содержимого. Это важно, так как обычный метод equals() сравнивает ссылки на вложенные массивы, а не их содержимое.
import java.util.Arrays;

public class DeepEqualsExample {
public static void main(String[] args) {
int[][] array1 = {{1, 2, 3}, {4, 5, 6}};
int[][] array2 = {{1, 2, 3}, {4, 5, 6}};
boolean areEqual = Arrays.deepEquals(array1, array2);
System.out.println("2D arrays are equal: " + areEqual);
}
}


Вывод:
2D arrays are equal: true


4. Arrays.setAll()
Метод setAll() позволяет инициализировать или модифицировать массив, применяя к каждому элементу функцию на основе его индекса. Этот метод особенно удобен для создания последовательностей или для сложных инициализаций массива.
import java.util.Arrays;

public class SetAllExample {
public static void main(String[] args) {
int[] numbers = new int[5];
Arrays.setAll(numbers, i -> i * 2);
System.out.println("Array after setAll: " + Arrays.toString(numbers));
}
}


Вывод:
Array after setAll: [0, 2, 4, 6, 8]


#Java #Training #Medium #Arrays
Stream API, внутреннее устройство, особенности и преимущества

Java Stream API — это мощный инструмент, введенный в Java 8, который позволяет разработчикам эффективно и элегантно работать с последовательностями данных. Этот API предоставляет декларативный способ обработки коллекций и массивов, делая код более лаконичным и выразительным.

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

Основные характеристики Stream API:

Декларативный стиль: Позволяет писать код в функциональном стиле, описывая, что нужно сделать, а не как это сделать.
Ленивые вычисления: Операции на потоках выполняются только тогда, когда это действительно необходимо.
Неизменяемость: Потоки не изменяют исходные данные, а создают новые потоки или результаты.
Поддержка параллелизма: Stream API легко интегрируется с параллельными вычислениями, что позволяет улучшить производительность на многоядерных процессорах.


Внутреннее устройство Stream API

Stream API реализован на основе концепции потоков данных (streams), которые представляют собой последовательности элементов из источника данных, такого как коллекция, массив или I/O. Потоки могут быть конечными и бесконечными, что позволяет работать как с ограниченными, так и с неограниченными последовательностями данных.

1. Источник данных
Потоки создаются из различных источников данных, таких как коллекции (List, Set, Map), массивы или генераторы данных. Например, можно создать поток из списка с использованием метода stream():
import java.util.Arrays;
import java.util.List;

public class StreamCreationExample {
public static void main(String[] args) {
List<String> items = Arrays.asList("apple", "banana", "orange");
items.stream().forEach(System.out::println);
}
}
В этом примере создается поток из списка строк и выводится каждый элемент на консоль.


2. Операции над потоками
Stream API поддерживает два типа операций: промежуточные (intermediate) и терминальные (terminal).

Промежуточные операции: Они применяются к потоку и возвращают новый поток. Примеры: filter(), map(), sorted(). Эти операции ленивы, что означает, что они не выполняются до тех пор, пока не будет вызвана терминальная операция.


Терминальные операции: Эти операции приводят к завершению работы с потоком, производя результат или побочные эффекты. Примеры: collect(), forEach(), reduce().

#Java #Training #Medium #StreamAPI
3. Ленивые вычисления
Одной из ключевых особенностей Stream API является ленивость вычислений. Это означает, что промежуточные операции на потоке не выполняются сразу, а откладываются до тех пор, пока не будет вызвана терминальная операция. Благодаря этому достигается высокая производительность, так как обрабатывается только необходимое количество данных.

4. Параллельные потоки

Stream API предоставляет возможность легко параллелизировать потоковые операции. Это достигается с помощью метода parallelStream(), который разделяет поток на несколько частей, которые обрабатываются параллельно на разных ядрах процессора.
import java.util.Arrays;
import java.util.List;

public class ParallelStreamExample {
public static void main(String[] args) {
List<String> items = Arrays.asList("apple", "banana", "cherry", "date");

items.parallelStream()
.map(String::toUpperCase)
.forEach(System.out::println);
}
}
В этом примере элементы списка обрабатываются параллельно, что может привести к ускорению выполнения, особенно при работе с большими объемами данных.


Особенности Stream API

1. Неизменяемость
Потоки в Java неизменяемы, что означает, что исходные данные никогда не изменяются. Вместо этого создаются новые потоки или результаты на основе исходных данных. Это делает код более безопасным и предсказуемым, устраняя возможность случайных изменений данных.

2. Ленивость
Как уже упоминалось, ленивые вычисления — это важная особенность Stream API, которая позволяет значительно повысить производительность, особенно при работе с большими объемами данных. Потоковые операции выполняются только тогда, когда это необходимо, что позволяет избежать ненужных вычислений.

3. Параллелизм
Stream API легко интегрируется с параллельными вычислениями, что позволяет более эффективно использовать возможности многоядерных процессоров. Использование параллельных потоков (parallelStream) позволяет обрабатывать данные параллельно, улучшая производительность в многопоточных средах.

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

1. Читаемость кода
Stream API значительно улучшает читаемость и лаконичность кода. Вместо использования циклов и условных операторов, разработчики могут использовать потоковые операции, чтобы выразить свою логику декларативно.
import java.util.Arrays;
import java.util.List;

public class ClassicVsStreamExample {
public static void main(String[] args) {
List<String> items = Arrays.asList("apple", "banana", "cherry", "date");

// Классический подход
for (String item : items) {
if (item.startsWith("a")) {
System.out.println(item.toUpperCase());
}
}

// Подход с использованием Stream API
items.stream()
.filter(item -> item.startsWith("a"))
.map(String::toUpperCase)
.forEach(System.out::println);
}
}
Вывод обоих подходов будет одинаковым, но код с использованием Stream API выглядит более лаконично и понятно.


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

3. Легкость параллелизации
Stream API предоставляет простую и интуитивно понятную возможность параллелизации обработки данных. Использование метода parallelStream() не требует сложной настройки многопоточности, что делает параллельные вычисления доступными даже для менее опытных разработчиков.

4. Функциональный подход
Stream API позволяет использовать функциональный стиль программирования, который становится все более популярным. Это позволяет писать код, который более выразителен и легче тестируется, а также способствует лучшей поддержке и масштабируемости.


#Java #Training #Medium #StreamAPI
Варианты ответа:
Anonymous Quiz
0%
5
0%
8
36%
4
64%
3
Что выведет код?

import java.util.Arrays;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(3, 7, 8, 10, 15, 18);
long count = numbers.stream()
.filter(n -> n % 3 == 0)
.map(n -> n * 2)
.count();
System.out.println(count);
}
}


#Tasks
А кто поступает по другому? 😜

https://t.me/Java_for_beginner_dev

#Mems
Основные методы Stream API и примеры использования

1. filter()
Метод filter() используется для фильтрации элементов потока на основе заданного условия. Он принимает предикат (функцию, возвращающую boolean), и если элемент удовлетворяет этому условию, он остается в потоке.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FilterExample {
public static void main(String[] args) {
List<String>```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FilterExample {
public static void main(String[] args) {
List<String> items = Arrays.asList("apple", "banana", "cherry", "date");

List<String> filteredItems = items.stream()
.filter(item -> item.startsWith("a"))
.collect(Collectors.toList());

System.out.println("Filtered items: " + filteredItems);
}
}
В этом примере фильтруются только те элементы списка, которые начинаются с буквы "a". Результат собирается в новый список с помощью метода collect() и выводится на консоль.


2. map()
Метод map() используется для преобразования элементов потока. Он принимает функцию, которая применяется к каждому элементу потока, и возвращает новый поток с преобразованными элементами.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MapExample {
public static void main(String[] args) {
List<String> items = Arrays.asList("apple", "banana", "cherry", "date");

List<String> upperCaseItems = items.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());

System.out.println("Uppercase items: " + upperCaseItems);
}
}
В этом примере все строки из списка преобразуются в верхний регистр, и результат снова собирается в новый список.


3. flatMap()
Метод flatMap() используется для преобразования потоков из элементов, которые сами являются потоками. Этот метод "раскрывает" вложенные потоки, объединяя их в один поток.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FlatMapExample {
public static void main(String[] args) {
List<List<String>> listOfLists = Arrays.asList(
Arrays.asList("apple", "banana"),
Arrays.asList("cherry", "date")
);

List<String> flatList = listOfLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());

System.out.println("Flattened list: " + flatList);
}
}
В этом примере два списка объединяются в один с помощью flatMap(), и результат собирается в новый список.


4. collect()

Метод collect() используется для сбора элементов потока в коллекцию или для других операций агрегирования. Чаще всего используется с Collectors, чтобы собрать элементы в список, множество или другую коллекцию.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class CollectExample {
public static void main(String[] args) {
List<String> items = Arrays.asList("apple", "banana", "cherry", "date");

List<String> collectedItems = items.stream()
.collect(Collectors.toList());

System.out.println("Collected items: " + collectedItems);
}
}


#Java #Training #Medium #StreamAPI
5. reduce()
Метод reduce() используется для агрегирования всех элементов потока в одно значение. Этот метод принимает бинарную операцию, которая сворачивает элементы потока до одного результата.
import java.util.Arrays;
import java.util.List;

public class ReduceExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
.reduce(0, Integer::sum);

System.out.println("Sum: " + sum);
}
}
В этом примере все числа в списке суммируются, и результат выводится на консоль.


6. forEach()
Метод forEach() выполняет действие для каждого элемента потока. Это терминальная операция, которая завершает работу потока, не возвращая результата.
import java.util.Arrays;
import java.util.List;

public class ForEachExample {
public static void main(String[] args) {
List<String> items = Arrays.asList("apple", "banana", "cherry", "date");

items.stream().forEach(System.out::println);
}
}


7. distinct()
Метод distinct() используется для удаления дубликатов из потока. Он возвращает поток, содержащий только уникальные элементы.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class DistinctExample {
public static void main(String[] args) {
List<String> items = Arrays.asList("apple", "banana", "apple", "date");

List<String> distinctItems = items.stream()
.distinct()
.collect(Collectors.toList());

System.out.println("Distinct items: " + distinctItems);
}
}


8. sorted()
Метод sorted() используется для сортировки элементов потока. Он может принимать компаратор, если требуется нестандартная сортировка.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SortedExample {
public static void main(String[] args) {
List<String> items = Arrays.asList("banana", "apple", "date", "cherry");

List<String> sortedItems = items.stream()
.sorted()
.collect(Collectors.toList());

System.out.println("Sorted items: " + sortedItems);
}
}


Примеры использования Stream API в реальных задачах

1. Подсчет количества уникальных слов в тексте
Предположим, у нас есть текст, и мы хотим узнать, сколько уникальных слов в нем содержится.
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;

public class UniqueWordsExample {
public static void main(String[] args) {
String text = "apple banana apple cherry date cherry banana";

Set<String> uniqueWords = Arrays.stream(text.split(" "))
.collect(Collectors.toSet());

System.out.println("Unique words: " + uniqueWords);
System.out.println("Number of unique words: " + uniqueWords.size());
}
}


Вывод:
Unique words: [banana, date, cherry, apple]
Number of unique words: 4


#Java #Training #Medium #StreamAPI
2. Фильтрация и преобразование списка объектов
Рассмотрим пример, где у нас есть список объектов Person, и мы хотим отфильтровать людей старше 18 лет, а затем преобразовать их имена в верхний регистр.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}
}

public class PersonExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 23),
new Person("Bob", 17),
new Person("Charlie", 19)
);

List<String> adultsNames = people.stream()
.filter(person -> person.getAge() > 18)
.map(Person::getName)
.map(String::toUpperCase)
.collect(Collectors.toList());

System.out.println("Adults' names: " + adultsNames);
}
}


Вывод:
Adults' names: [ALICE, CHARLIE]


3. Группировка объектов по ключу
Допустим, нам нужно сгруппировать людей по возрасту.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}
}

public class GroupingByExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 23),
new Person("Bob", 17),
new Person("Charlie", 23),
new Person("David", 17)
);

Map<Integer, List<Person>> groupedByAge = people.stream()
.collect(Collectors.groupingBy(Person```java
.collect(Collectors.groupingBy(Person::getAge)));

groupedByAge.forEach((age, personList) -> {
System.out.println("Age: " + age);
personList.forEach(person -> System.out.println(" " + person.getName()));
});
}
}
В этом примере люди группируются по возрасту, и результат выводится на консоль. Группировка позволяет удобно структурировать данные по ключевому признаку, что полезно в задачах аналитики и отчетности.


Вывод:
Age: 17
Bob
David
Age: 23
Alice
Charlie


#Java #Training #Medium #StreamAPI
CompletableFuture, особенности и внутреннее устройство

CompletableFuture — это один из наиболее мощных и гибких инструментов для работы с асинхронными задачами в Java. Впервые представленный в Java 8, он расширяет возможности Future, предоставляя удобные методы для создания, комбинирования и обработки результатов асинхронных вычислений.

CompletableFuture является частью пакета java.util.concurrent и реализует интерфейсы Future и CompletionStage. Это позволяет CompletableFuture комбинировать возможности обоих подходов: он может использоваться как обычный Future, чтобы получать результаты выполнения задач, а также может поддерживать цепочки асинхронных операций через методы CompletionStage.

Future — это интерфейс, который представляет собой результат асинхронного вычисления. Однако он имеет несколько ограничений, таких как отсутствие удобных методов для комбинирования нескольких задач и невозможность завершить Future вручную. Эти ограничения и были решены в CompletableFuture.

Внутреннее устройство

Внутри CompletableFuture основывается на асинхронном выполнении задач, используя ForkJoinPool, который является реализацией Executor и хорошо подходит для задач, разбиваемых на подзадачи. ForkJoinPool оптимизирует распределение задач между потоками, что особенно полезно при выполнении большого количества мелких асинхронных операций.

Когда создается новый экземпляр CompletableFuture, он по умолчанию находится в незавершенном состоянии. Он может быть завершен вручную с помощью методов complete() или автоматически при завершении задачи, связанной с ним. По завершению, CompletableFuture уведомляет все зарегистрированные на него действия (коллбэки), что делает его очень удобным для построения цепочек асинхронных операций.

Особенности CompletableFuture

1. Завершение вручную.
Одной из ключевых особенностей CompletableFuture является возможность завершать его вручную. Это полезно, когда результат выполнения известен заранее, или когда требуется завершить задачу в случае возникновения ошибки.
import java.util.concurrent.CompletableFuture;

public class ManualCompletionExample {
public static void main(String[] args) {
CompletableFuture<String> future = new CompletableFuture<>();

// Завершаем вручную
future.complete("Result");

future.thenAccept(result -> System.out.println("Completed with: " + result));
}
}
В этом примере мы создаем экземпляр CompletableFuture и завершаем его вручную с результатом "Result". Метод thenAccept() регистрирует действие, которое будет выполнено при завершении future.


2. Асинхронное выполнение задач.
CompletableFuture позволяет легко выполнять задачи асинхронно с использованием методов runAsync() и supplyAsync().
import java.util.concurrent.CompletableFuture;

public class AsyncExample {
public static void main(String[] args) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
System.out.println("Asynchronous task");
} catch (InterruptedException e) {
e.printStackTrace();
}
});

future.join(); // Ожидаем завершения
}
}
В этом примере задача выполняется асинхронно, и основной поток ждет ее завершения с помощью join().


3. Построение цепочек асинхронных задач.
Одним из самых мощных аспектов CompletableFuture является возможность построения цепочек асинхронных задач, что позволяет организовать сложные рабочие процессы.
import java.util.concurrent.CompletableFuture;

public class ChainExample {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(greeting -> greeting + ", World")
.thenAccept(result -> System.out.println(result));

// Ожидаем завершения всех задач
CompletableFuture<Void> allOf = CompletableFuture.allOf();
allOf.join();
}
}
Этот код создает цепочку операций, где одна задача преобразует результат другой.


#Java #Training #Medium #CompletableFuture
Что выведет код?

import java.util.*;

public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("apple", "banana", "cherry", "apple", "date"));
Set<String> set = new HashSet<>(list);
List<String> resultList = new ArrayList<>(set);
Collections.sort(resultList);
System.out.println(resultList);
}
}


#Tasks
И постоянно же так! 🤦‍♂️🤪

https://t.me/Java_for_beginner_dev

#Mems
Основные методы CompletableFuture и примеры использования

Методы создания

runAsync(Runnable runnable) — выполняет задачу асинхронно и возвращает CompletableFuture<Void>.
supplyAsync(Supplier<U> supplier) — выполняет задачу асинхронно и возвращает CompletableFuture<U>, где U — тип результата.

import java.util.concurrent.CompletableFuture;

public class CreateExample {
public static void main(String[] args) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Running async task");
});

CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> {
return "Async result";
});

future.join(); // Ожидание завершения
System.out.println(result.join());
}
}


Методы обработки результата

thenApply(Function<? super T,? extends U> fn) — преобразует результат после завершения задачи.
thenAccept(Consumer<? super T> action) — выполняет действие над результатом.
thenRun(Runnable action) — выполняет действие без использования результата.

import java.util.concurrent.CompletableFuture;

public class HandleExample {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

CompletableFuture<String> transformed = future.thenApply(result -> result + ", World");

transformed.thenAccept(result -> System.out.println("Final result: " + result));
}
}


Комбинирование нескольких задач
thenCombine(CompletableFuture<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) — комбинирует два CompletableFuture и возвращает новый результат.
thenCompose(Function<? super T,? extends CompletionStage<U>> fn) — создает зависимость между двумя задачами, где вторая зависит от результата первой.

import java.util.concurrent.CompletableFuture;

public class CombineExample {
public static void main(String[] args) {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<String> combined = future1.thenCombine(future2, (f1, f2) -> f1 + " " + f2);

combined.thenAccept(result -> System.out.println("Combined result: " + result));
}
}


Методы обработки исключений

exceptionally(Function<Throwable,? extends T> fn) — обрабатывает исключение и возвращает альтернативный результат.
handle(BiFunction<? super T, Throwable,? extends U> fn) — обрабатывает результат или исключение.

import java.util.concurrent.CompletableFuture;

public class ExceptionExample {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("Something went wrong!");
}
return "Result";
});

future.exceptionally(ex -> {
System.out.println("Exception: " + ex.getMessage());
return "Default result";
}).thenAccept(result -> System.out.println("Result: " + result));
}
}



Примеры использования CompletableFuture в реальных задачах


Параллельное выполнение нескольких задач
import java.util.concurrent.CompletableFuture;

public class ParallelTasksExample {
public static void main(String[] args) {
CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> 2);
CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> 3);

CompletableFuture<Integer> result = task1.thenCombine(task2, (a, b) -> a * b);

result.thenAccept(res -> System.out.println("Multiplication result: " + res));
}
}

#Java #Training #Medium #CompletableFuture
Выполнение задачи с таймаутом
Если задача выполняется слишком долго, можно установить таймаут, после которого будет возвращён результат по умолчанию или выброшено исключение.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class TimeoutExample {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000); // Имитация длительной задачи
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Task completed";
});

try {
String result = future.get(1, TimeUnit.SECONDS); // Устанавливаем таймаут в 1 секунду
System.out.println(result);
} catch (TimeoutException e) {
System.out.println("Timeout occurred");
} catch (Exception e) {
e.printStackTrace();
}
}
}
В этом примере задача длится 2 секунды, но установленный таймаут — 1 секунда. В результате будет выведено сообщение о таймауте.


Цепочка зависимых асинхронных операций
Часто требуется выполнить несколько асинхронных операций последовательно, передавая результат одной задачи в другую.
import java.util.concurrent.CompletableFuture;

public class ChainedAsyncExample {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> "Hello")
.thenApplyAsync(greeting -> greeting + ", World")
.thenApplyAsync(message -> message + "!")
.thenAcceptAsync(System.out::println);

CompletableFuture<Void> allOf = CompletableFuture.allOf(); // Для ожидания завершения всех задач
allOf.join(); // Ожидаем завершения
}
}
В этом примере цепочка асинхронных операций последовательно преобразует строку, добавляя к ней различные части и выводя окончательный результат на консоль.


Асинхронная обработка коллекций данных
Представим, что у нас есть список элементов, и для каждого элемента нужно выполнить асинхронную операцию, например, запрос в базу данных или внешний API.
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class AsyncProcessingExample {
public static void main(String[] args) {
List<String> data = Arrays.asList("One", "Two", "Three");

List<CompletableFuture<String>> futures = data.stream()
.map(item -> CompletableFuture.supplyAsync(() -> processItem(item)))
.collect(Collectors.toList());

List<String> results = futures.stream()
.map(CompletableFuture::join) // Ожидаем завершения всех задач
.collect(Collectors.toList());

results.forEach(System.out::println);
}

private static String processItem(String item) {
try {
Thread.sleep(1000); // Имитация асинхронной операции
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Processed: " + item;
}
}
Этот пример демонстрирует асинхронную обработку списка элементов, где каждая операция выполняется параллельно, а результаты собираются и выводятся после завершения всех задач.


#Java #Training #Medium #CompletableFuture
Optional, особенности и внутреннее устройство

Optional — это класс, введенный в Java 8, который представляет собой контейнер для значений, который может содержать значение или быть пустым. Основная цель Optional — избежать проблем с NullPointerException (NPE), которые возникают, когда программа пытается получить доступ к объекту через ссылку, содержащую null.

Optional — это обертка, которая может содержать либо одно значение, либо быть пустой. Таким образом, вместо того чтобы возвращать null, методы могут возвращать экземпляр Optional, сигнализируя о том, что результат может отсутствовать. Это позволяет явно обрабатывать отсутствие значения и избегать NPE.

Пример использования Optional:
import java.util.Optional;

public class OptionalExample {
public static void main(String[] args) {
Optional<String> possibleValue = Optional.of("Hello, World!");
possibleValue.ifPresent(System.out::println); // Выведет "Hello, World!"

Optional<String> emptyValue = Optional.empty();
System.out.println(emptyValue.isPresent()); // Выведет "false"
}
}
В этом примере possibleValue содержит строку, и при наличии значения оно выводится на консоль. emptyValue является пустым, и метод isPresent() возвращает false.


Внутреннее устройство Optional

Optional реализован как финальный класс с двумя основными полями:


Value — содержит значение, если оно присутствует.
Empty — статическое поле, представляющее пустой Optional, используется в качестве синглтона.


Конструктор Optional приватен, и создание объекта осуществляется через статические фабричные методы, такие как of(), ofNullable(), и empty().

Ключевые методы класса Optional:

of(T value): Создает Optional, содержащий переданное значение. Если значение null, то выбрасывается NullPointerException.
ofNullable(T value): Создает Optional, который может быть пустым, если переданное значение null.

empty(): Возвращает пустой Optional.

Пример внутренней структуры класса Optional:
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();

private final T value;

private Optional() {
this.value = null;
}

private Optional(T value) {
this.value = Objects.requireNonNull(value);
}

public static <T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}

public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}

public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}

// Другие методы...
}
Этот код демонстрирует, как Optional хранит значение и как реализованы его статические фабричные методы.


#Java #Training #Medium #Optional