Запись нашей сегодняшней встречи -
https://www.youtube.com/watch?v=KTO2-XhWCnY
Спасибо всем кто участвовал👍, продолжение на следующей неделе)))
https://www.youtube.com/watch?v=KTO2-XhWCnY
Спасибо всем кто участвовал👍, продолжение на следующей неделе)))
YouTube
Создание консольного проекта TODO, часть 1. Запись встречи от 07/07/2024
Первая часть создания консольного проекта TODO (менеджера записей), которую мы провели совместно с подписчиками.
В видео рассматривается создание логики приложения, использование распространённого паттерна MVC, логики создания текстовых файлов, записи в…
В видео рассматривается создание логики приложения, использование распространённого паттерна MVC, логики создания текстовых файлов, записи в…
Java for Beginner pinned «Запись нашей сегодняшней встречи - https://www.youtube.com/watch?v=KTO2-XhWCnY Спасибо всем кто участвовал👍, продолжение на следующей неделе)))»
Внутренние классы
Внутренние классы (Inner Classes) — это классы, которые определены внутри других классов. Они используются для логической группировки классов, которые будут использоваться только в одном месте, и для улучшения читаемости и организации кода. Внутренние классы имеют доступ к членам внешнего класса, включая приватные члены.
Виды внутренних классов:
Не статические внутренние классы (Non-static inner classes):
Обычные внутренние классы (Regular Inner Classes)
Локальные внутренние классы (Local Inner Classes)
Анонимные внутренние классы (Anonymous Inner Classes)
Статические вложенные классы (Static Nested Classes)
Обычные внутренние классы (Regular Inner Classes)
Это классы, объявленные непосредственно внутри другого класса, но вне всех методов. Они имеют доступ ко всем полям и методам внешнего класса, включая приватные.
В этом примере InnerClass является обычным внутренним классом и имеет доступ к приватному полю outerField внешнего класса OuterClass.
Локальные внутренние классы (Local Inner Classes)
Это классы, объявленные внутри метода, конструктора или блока и могут использоваться только внутри этого метода.
В этом примере LocalInnerClass объявлен внутри метода methodWithInnerClass и доступен только внутри этого метода.
Анонимные внутренние классы (Anonymous Inner Classes)
Анонимные внутренние классы используются для создания экземпляров классов с одновременным переопределением их методов, обычно для реализации интерфейсов или абстрактных классов.
В этом примере мы создаем анонимный внутренний класс, который реализует интерфейс MyInterface и переопределяет его метод myMethod.
#Java #Training
Внутренние классы (Inner Classes) — это классы, которые определены внутри других классов. Они используются для логической группировки классов, которые будут использоваться только в одном месте, и для улучшения читаемости и организации кода. Внутренние классы имеют доступ к членам внешнего класса, включая приватные члены.
Виды внутренних классов:
Не статические внутренние классы (Non-static inner classes):
Обычные внутренние классы (Regular Inner Classes)
Локальные внутренние классы (Local Inner Classes)
Анонимные внутренние классы (Anonymous Inner Classes)
Статические вложенные классы (Static Nested Classes)
Обычные внутренние классы (Regular Inner Classes)
Это классы, объявленные непосредственно внутри другого класса, но вне всех методов. Они имеют доступ ко всем полям и методам внешнего класса, включая приватные.
public class OuterClass {
private String outerField = "Outer Field";
public class InnerClass {
public void display() {
System.out.println("Outer field is: " + outerField);
}
}
public void testInner() {
InnerClass inner = new InnerClass();
inner.display();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.testInner();
}
}
В этом примере InnerClass является обычным внутренним классом и имеет доступ к приватному полю outerField внешнего класса OuterClass.
Локальные внутренние классы (Local Inner Classes)
Это классы, объявленные внутри метода, конструктора или блока и могут использоваться только внутри этого метода.
public class OuterClass {
public void methodWithInnerClass() {
class LocalInnerClass {
void display() {
System.out.println("This is a local inner class");
}
}
LocalInnerClass localInner = new LocalInnerClass();
localInner.display();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.methodWithInnerClass();
}
}
В этом примере LocalInnerClass объявлен внутри метода methodWithInnerClass и доступен только внутри этого метода.
Анонимные внутренние классы (Anonymous Inner Classes)
Анонимные внутренние классы используются для создания экземпляров классов с одновременным переопределением их методов, обычно для реализации интерфейсов или абстрактных классов.
public class OuterClass {
public void createAnonymousClass() {
MyInterface anonymous = new MyInterface() {
@Override
public void myMethod() {
System.out.println("Anonymous inner class method");
}
};
anonymous.myMethod();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.createAnonymousClass();
}
}
interface MyInterface {
void myMethod();
}
В этом примере мы создаем анонимный внутренний класс, который реализует интерфейс MyInterface и переопределяет его метод myMethod.
#Java #Training
Что выведет код?
#Tasks
import java.util.*;
public class CollectionsExample {
public static void main(String[] args) {
Set<String> set = new HashSet<>(Arrays.asList("apple", "banana", "cherry", "date"));
List<String> list = new ArrayList<>(set);
Collections.sort(list, new CustomComparator());
Queue<String> queue = new LinkedList<>(list);
System.out.println(queue.poll() + " " + queue.peek());
}
}
class CustomComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
#Tasks
Статические вложенные классы и примеры использования внутренних классов
Статические вложенные классы (Static Nested Classes)
Статические вложенные классы объявляются с ключевым словом static. Они похожи на обычные внутренние классы, но не имеют доступа к нестатическим членам внешнего класса. Они могут использоваться как самостоятельные классы.
В этом примере StaticNestedClass является статическим вложенным классом и имеет доступ только к статическим полям внешнего класса OuterClass.
Использование обычных внутренних классов:
Обычные внутренние классы часто используются, когда необходимо логически сгруппировать класс внутри другого класса и когда внутренний класс должен иметь доступ к членам внешнего класса.
Использование локальных внутренних классов:
Локальные внутренние классы полезны, когда вам нужен вспомогательный класс, который используется только внутри метода.
Использование анонимных внутренних классов:
Анонимные внутренние классы часто используются для создания простых одноразовых объектов с переопределенными методами.
Преимущества и недостатки внутренних классов
Преимущества:
Улучшение логической организации кода.
Упрощение доступа к членам внешнего класса.
Снижение количества классов верхнего уровня.
Недостатки:
Усложнение кода для начинающих программистов.
Потенциальные проблемы с производительностью из-за увеличения числа классов.
#Java #Training
Статические вложенные классы (Static Nested Classes)
Статические вложенные классы объявляются с ключевым словом static. Они похожи на обычные внутренние классы, но не имеют доступа к нестатическим членам внешнего класса. Они могут использоваться как самостоятельные классы.
public class OuterClass {
private static String staticOuterField = "Static Outer Field";
public static class StaticNestedClass {
public void display() {
System.out.println("Static outer field is: " + staticOuterField);
}
}
public static void main(String[] args) {
StaticNestedClass nested = new StaticNestedClass();
nested.display();
}
}
В этом примере StaticNestedClass является статическим вложенным классом и имеет доступ только к статическим полям внешнего класса OuterClass.
Использование обычных внутренних классов:
Обычные внутренние классы часто используются, когда необходимо логически сгруппировать класс внутри другого класса и когда внутренний класс должен иметь доступ к членам внешнего класса.
public class OuterClass {
private String outerField = "Outer Field";
public class InnerClass {
public void display() {
System.out.println("Outer field is: " + outerField);
}
}
public void testInner() {
InnerClass inner = new InnerClass();
inner.display();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.testInner();
}
}
Использование локальных внутренних классов:
Локальные внутренние классы полезны, когда вам нужен вспомогательный класс, который используется только внутри метода.
public class OuterClass {
public void methodWithInnerClass() {
class LocalInnerClass {
void display() {
System.out.println("This is a local inner class");
}
}
LocalInnerClass localInner = new LocalInnerClass();
localInner.display();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.methodWithInnerClass();
}
}
Использование анонимных внутренних классов:
Анонимные внутренние классы часто используются для создания простых одноразовых объектов с переопределенными методами.
public class OuterClass {
public void createAnonymousClass() {
MyInterface anonymous = new MyInterface() {
@Override
public void myMethod() {
System.out.println("Anonymous inner class method");
}
};
anonymous.myMethod();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.createAnonymousClass();
}
}
interface MyInterface {
void myMethod();
}
Преимущества и недостатки внутренних классов
Преимущества:
Улучшение логической организации кода.
Упрощение доступа к членам внешнего класса.
Снижение количества классов верхнего уровня.
Недостатки:
Усложнение кода для начинающих программистов.
Потенциальные проблемы с производительностью из-за увеличения числа классов.
#Java #Training
Лямбда-выражения
Лямбда-выражения, или просто "лямбды", были введены в Java 8 и представляют собой компактный способ представления анонимных функций. Они позволяют передавать поведение как параметр метода, что делает код более гибким и лаконичным.
Синтаксис лямбда-выражений
Лямбда-выражение состоит из параметров, стрелки (->) и тела. Существует несколько форм записи лямбда-выражений:
Без параметров:
С одним параметром (скобки можно опустить):
С несколькими параметрами:
С телом, состоящим из нескольких выражений:
Использование лямбда-выражений
Лямбда-выражения чаще всего используются в сочетании с функциональными интерфейсами. Функциональный интерфейс — это интерфейс с единственным абстрактным методом (SAM — Single Abstract Method). Примеры таких интерфейсов включают Runnable, Callable, Comparator и интерфейсы из пакета java.util.function.
Пример использования лямбда-выражения с функциональным интерфейсом Runnable:
Встроенные функциональные интерфейсы
Java предоставляет ряд встроенных функциональных интерфейсов в пакете java.util.function, которые можно использовать с лямбда-выражениями. Некоторые из них:
Predicate<T>: принимает один аргумент и возвращает логическое значение.
Consumer<T>: принимает один аргумент и не возвращает результат.
Function<T, R>: принимает один аргумент и возвращает результат.
Supplier<T>: не принимает аргументов, но возвращает результат.
UnaryOperator<T>: принимает один аргумент и возвращает результат того же типа.
BinaryOperator<T>: принимает два аргумента и возвращает результат того же типа.
#Java #Training #Medium
Лямбда-выражения, или просто "лямбды", были введены в Java 8 и представляют собой компактный способ представления анонимных функций. Они позволяют передавать поведение как параметр метода, что делает код более гибким и лаконичным.
Синтаксис лямбда-выражений
Лямбда-выражение состоит из параметров, стрелки (->) и тела. Существует несколько форм записи лямбда-выражений:
Без параметров:
() -> System.out.println("Hello, World!");
С одним параметром (скобки можно опустить):
x -> x * 2;
С несколькими параметрами:
(x, y) -> x + y;
С телом, состоящим из нескольких выражений:
(x, y) -> {
int sum = x + y;
return sum;
};
Использование лямбда-выражений
Лямбда-выражения чаще всего используются в сочетании с функциональными интерфейсами. Функциональный интерфейс — это интерфейс с единственным абстрактным методом (SAM — Single Abstract Method). Примеры таких интерфейсов включают Runnable, Callable, Comparator и интерфейсы из пакета java.util.function.
Пример использования лямбда-выражения с функциональным интерфейсом Runnable:
Runnable r = () -> System.out.println("Lambda Runnable");
new Thread(r).start();
Встроенные функциональные интерфейсы
Java предоставляет ряд встроенных функциональных интерфейсов в пакете java.util.function, которые можно использовать с лямбда-выражениями. Некоторые из них:
Predicate<T>: принимает один аргумент и возвращает логическое значение.
Predicate<String> isEmpty = s -> s.isEmpty();
System.out.println(isEmpty.test("")); // true
Consumer<T>: принимает один аргумент и не возвращает результат.
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello, Consumer!");
Function<T, R>: принимает один аргумент и возвращает результат.
Function<Integer, String> intToString = num -> "Number: " + num;
System.out.println(intToString.apply(5)); // Number: 5
Supplier<T>: не принимает аргументов, но возвращает результат.
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get());
UnaryOperator<T>: принимает один аргумент и возвращает результат того же типа.
UnaryOperator<Integer> square = x -> x * x;
System.out.println(square.apply(5)); // 25
BinaryOperator<T>: принимает два аргумента и возвращает результат того же типа.
BinaryOperator<Integer> add = (x, y) -> x + y;
System.out.println(add.apply(2, 3)); // 5
#Java #Training #Medium
Что выведет код?
#Tasks
public class ArithmeticExample {
public static void main(String[] args) {
int a = 10;
int b = 20;
int c = 5;
int result = (a * b / c) + (a % c * b) - (a - c);
System.out.println(result);
}
}
#Tasks
Применение лямбда-выражений в коллекциях и Stream API
Лямбда-выражения и коллекции
Одним из самых мощных применений лямбда-выражений является их использование в коллекциях. С введением лямбд в Java 8, стандартные интерфейсы коллекций были расширены новыми методами, такими как forEach, removeIf, replaceAll и sort, которые принимают лямбда-выражения в качестве параметров.
Метод forEach:
Метод removeIf:
Метод replaceAll:
Метод sort:
Лямбда-выражения и Stream API
Stream API в Java 8 позволяет легко и эффективно работать с потоками данных, выполняя операции над элементами коллекций. Лямбда-выражения играют ключевую роль в использовании Stream API.
Создание Stream:
Фильтрация (filter):
Преобразование (map):
Сортировка (sorted):
Агрегация (reduce):
Преимущества лямбда-выражений
Компактность и читабельность: Лямбда-выражения позволяют значительно сократить объем кода, особенно при работе с коллекциями и потоками.
Удобство использования: Лямбда-выражения упрощают передачу поведения как параметра и делают код более декларативным.
Функциональное программирование: Лямбда-выражения являются основой функционального программирования в Java, позволяя легко работать с функциями высшего порядка и композициями.
#Java #Training #Medium
Лямбда-выражения и коллекции
Одним из самых мощных применений лямбда-выражений является их использование в коллекциях. С введением лямбд в Java 8, стандартные интерфейсы коллекций были расширены новыми методами, такими как forEach, removeIf, replaceAll и sort, которые принимают лямбда-выражения в качестве параметров.
Метод forEach:
List<String> list = Arrays.asList("a", "b", "c", "d");
list.forEach(element -> System.out.println(element));
Метод removeIf:
List<String> list = new ArrayList<>(Arrays.asList("one", "two", "three", "four"));
list.removeIf(element -> element.length() > 3);
list.forEach(System.out::println); // one, two
Метод replaceAll:
List<String> list = new ArrayList<>(Arrays.asList("one", "two", "three", "four"));
list.replaceAll(String::toUpperCase);
list.forEach(System.out::println); // ONE, TWO, THREE, FOUR
Метод sort:
List<String> list = new ArrayList<>(Arrays.asList("one", "two", "three", "four"));
list.sort((s1, s2) -> s1.compareTo(s2));
list.forEach(System.out::println); // four, one, three, two
Лямбда-выражения и Stream API
Stream API в Java 8 позволяет легко и эффективно работать с потоками данных, выполняя операции над элементами коллекций. Лямбда-выражения играют ключевую роль в использовании Stream API.
Создание Stream:
List<String> list = Arrays.asList("one", "two", "three", "four");
Stream<String> stream = list.stream();
Фильтрация (filter):
List<String> list = Arrays.asList("one", "two", "three", "four");
List<String> filteredList = list.stream()
.filter(s -> s.startsWith("t"))
.collect(Collectors.toList());
filteredList.forEach(System.out::println); // two, three
Преобразование (map):
List<String> list = Arrays.asList("one", "two", "three", "four");
List<Integer> lengths = list.stream()
.map(String::length)
.collect(Collectors.toList());
lengths.forEach(System.out::println); // 3, 3, 5, 4
Сортировка (sorted):
List<String> list = Arrays.asList("one", "two", "three", "four");
List<String> sortedList = list.stream()
.sorted()
.collect(Collectors.toList());
sortedList.forEach(System.out::println); // four, one, three, two
Агрегация (reduce):
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println(sum); // 15
Преимущества лямбда-выражений
Компактность и читабельность: Лямбда-выражения позволяют значительно сократить объем кода, особенно при работе с коллекциями и потоками.
Удобство использования: Лямбда-выражения упрощают передачу поведения как параметра и делают код более декларативным.
Функциональное программирование: Лямбда-выражения являются основой функционального программирования в Java, позволяя легко работать с функциями высшего порядка и композициями.
#Java #Training #Medium
Введение в Stream API и основные методы создания стримов
Stream API, введенный в Java 8, предоставляет мощный способ работы с коллекциями данных. Stream представляет собой последовательность элементов, поддерживающую различные операции для создания желаемых результатов. Операции со стримами могут быть промежуточными (возвращающими другой стрим) и терминальными (возвращающими конечный результат).
Создание стримов
Из коллекций:
Из массивов:
Из значений:
Из файлов:
Бесконечные стримы:
iterate:
generate:
Промежуточные методы Stream API
Промежуточные методы возвращают новый стрим, позволяя строить цепочки операций.
filter:
map:
flatMap:
distinct:
sorted:
Без компаратора:
С компаратором:
peek:
limit:
skip:
#Java #Training #Stream #Medium
Stream API, введенный в Java 8, предоставляет мощный способ работы с коллекциями данных. Stream представляет собой последовательность элементов, поддерживающую различные операции для создания желаемых результатов. Операции со стримами могут быть промежуточными (возвращающими другой стрим) и терминальными (возвращающими конечный результат).
Создание стримов
Из коллекций:
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
Из массивов:
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
Из значений:
Stream<String> stream = Stream.of("a", "b", "c");
Из файлов:
try (Stream<String> stream = Files.lines(Paths.get("file.txt"))) {
stream.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
Бесконечные стримы:
iterate:
Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(10);
stream.forEach(System.out::println);
generate:
Stream<Double> stream = Stream.generate(Math::random).limit(10);
stream.forEach(System.out::println);
Промежуточные методы Stream API
Промежуточные методы возвращают новый стрим, позволяя строить цепочки операций.
filter:
List<String> list = Arrays.asList("a", "ab", "abc", "abcd");
list.stream()
.filter(s -> s.length() > 2)
.forEach(System.out::println); // abc, abcd
map:
List<String> list = Arrays.asList("1", "2", "3");
list.stream()
.map(Integer::parseInt)
.forEach(System.out::println); // 1, 2, 3
flatMap:
List<List<String>> list = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d")
);
list.stream()
.flatMap(Collection::stream)
.forEach(System.out::println); // a, b, c, d
distinct:
List<String> list = Arrays.asList("a", "b", "a", "c", "b");
list.stream()
.distinct()
.forEach(System.out::println); // a, b, c
sorted:
Без компаратора:
List<String> list = Arrays.asList("c", "a", "b");
list.stream()
.sorted()
.forEach(System.out::println); // a, b, c
С компаратором:
List<String> list = Arrays.asList("c", "a", "b");
list.stream()
.sorted(Comparator.reverseOrder())
.forEach(System.out::println); // c, b, a
peek:
List<String> list = Arrays.asList("a", "b", "c");
list.stream()
.peek(System.out::println)
.map(String::toUpperCase)
.forEach(System.out::println); // a, A, b, B, c, C
limit:
Stream<Integer> stream = Stream.iterate(1, n -> n + 1);
stream.limit(5)
.forEach(System.out::println); // 1, 2, 3, 4, 5
skip:
Stream<Integer> stream = Stream.iterate(1, n -> n + 1);
stream.skip(5)
.limit(5)
.forEach(System.out::println); // 6, 7, 8, 9, 10
#Java #Training #Stream #Medium
Что выведет код?
#Tasks
public class ConcatenationExample {
public static void main(String[] args) {
int x = 5;
int y = 10;
String str1 = "Hello";
String str2 = "World";
String result = str1 + x + y + str2 + (x + y) + str1.length() + (x * y);
System.out.println(result);
}
}
#Tasks
Варианты ответа:
Anonymous Quiz
79%
Hello510World15550
7%
Hello510World15105Hello
14%
Hello510World1510150
0%
Hello510World1510150Hello
Терминальные методы Stream API
Терминальные методы завершают цепочку операций со стримами, возвращая результат или выполняя действие.
forEach:
collect:
reduce:
toArray:
findFirst:
findAny:
count:
anyMatch:
allMatch:
noneMatch:
Продвинутые методы и примеры использования Stream API
groupingBy:
partitioningBy:
mapping:
joining:
Примеры использования Stream API
Фильтрация и преобразование списка строк:
Группировка чисел по четности:
Подсчет элементов в списке:
Нахождение максимального значения в списке:
#Java #Training #Stream #Medium
Терминальные методы завершают цепочку операций со стримами, возвращая результат или выполняя действие.
forEach:
List<String> list = Arrays.asList("a", "b", "c");
list.stream()
.forEach(System.out::println); // a, b, c
collect:
List<String> list = Arrays.asList("a", "b", "c");
List<String> result = list.stream()
.collect(Collectors.toList());
reduce:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
int sum = list.stream()
.reduce(0, (a, b) -> a + b);
toArray:
List<String> list = Arrays.asList("a", "b", "c");
String[] array = list.stream()
.toArray(String[]::new);
findFirst:
List<String> list = Arrays.asList("a", "b", "c");
Optional<String> first = list.stream()
.findFirst();
findAny:
List<String> list = Arrays.asList("a", "b", "c");
Optional<String> any = list.stream()
.findAny();
count:
List<String> list = Arrays.asList("a", "b", "c");
long count = list.stream()
.count();
anyMatch:
List<String> list = Arrays.asList("a", "b", "c");
boolean anyMatch = list.stream()
.anyMatch(s -> s.equals("a"));
allMatch:
List<String> list = Arrays.asList("a", "b", "c");
boolean allMatch = list.stream()
.allMatch(s -> s.length() == 1);
noneMatch:
List<String> list = Arrays.asList("a", "b", "c");
boolean noneMatch = list.stream()
.noneMatch(s -> s.equals("d"));
Продвинутые методы и примеры использования Stream API
groupingBy:
List<String> list = Arrays.asList("a", "ab", "abc", "abcd");
Map<Integer, List<String>> groupedByLength = list.stream()
.collect(Collectors.groupingBy(String::length));
partitioningBy:
List<String> list = Arrays.asList("a", "ab", "abc", "abcd");
Map<Boolean, List<String>> partitionedByLength = list.stream()
.collect(Collectors.partitioningBy(s -> s.length() > 2));
mapping:
List<String> list = Arrays.asList("a", "ab", "abc", "abcd");
List<Integer> lengths = list.stream()
.collect(Collectors.mapping(String::length, Collectors.toList()));
joining:
List<String> list = Arrays.asList("a", "b", "c");
String joined = list.stream()
.collect(Collectors.joining(", "));
Примеры использования Stream API
Фильтрация и преобразование списка строк:
List<String> list = Arrays.asList("apple", "banana", "cherry", "date");
List<String> result = list.stream()
.filter(s -> s.startsWith("a"))
.map(String::toUpperCase)
.collect(Collectors.toList());
Группировка чисел по четности:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Map<Boolean, List<Integer>> evenOddMap = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
Подсчет элементов в списке:
List<String> list = Arrays.asList("a", "b", "c", "a", "b", "c");
Map<String, Long> frequencyMap = list.stream()
.collect(Collectors.groupingBy(s -> s, Collectors.counting()));
Нахождение максимального значения в списке:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> maxNumber = numbers.stream()
.max(Integer::compare);
#Java #Training #Stream #Medium