Java for Beginner
870 subscribers
1.01K photos
275 videos
14 files
1.69K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
История технологий сегодня — 25 марта

ℹ️ Кто родился в этот день

Христофо́р Кла́виус (или Клавий, лат. Christophorus Clavius; 25 марта 1538, Бамберг — 6 февраля 1612, Рим) — немецкий математик и астроном. Член ордена иезуитов. Известен как главный разработчик окончательной версии современного григорианского календаря. В последние годы своей жизни был одним из самых уважаемых астрономов в Европе, а его книги по астрономии широко использовались более 50 лет. Множество видных европейских астрономов и математиков обучались у Клавиуса, среди них: Грегуар де Сен-Венсан, Марин Гетальдич, Джулио Алени и Пауль Гульдин.

Кейси Оуэн Нейстат (родился 25 марта 1981 г.) — американский YouTube-блогер , кинорежиссер , видеоблогер и соучредитель мультимедийной компании Beme , которая позже была приобретена CNN. В 2018 году он основал 368, креативное пространство для сотрудничества создателей.

🌐 Знаковые события

1938 — Трансляцией фильма «Великий гражданин» начато регулярное вещание Московского телецентра.

1995 — Уорд Каннингем сделал общедоступной WikiWikiWeb, первую в мире вики-систему, являющуюся частью Портлендского репозитория шаблонов.


#Biography #Birth_Date #Events #25марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Раздел 8. Stream API и функциональный стиль в Java

Глава 7: Stream API в экосистеме Java. Интеграционные паттерны

Примитивные стримы (IntStream, LongStream)

Одно из фундаментальных ограничений Java — отсутствие обобщений (generics) для примитивных типов. Stream<T> может содержать только ссылочные типы, что вынуждает упаковывать int в Integer, long в Long, double в Double. Эта операция, называемая боксингом (boxing), создаёт объекты-обёртки в куче, увеличивает нагрузку на GC и снижает производительность кэша процессора из-за разыменования указателей.

Примитивные стримы — IntStream, LongStream, DoubleStream — решают эту проблему через специализацию. Они работают с сырыми примитивами, избегая аллокаций и обеспечивая плотное размещение данных в памяти.


Генерация числовых последовательностей

range и rangeClosed

IntStream.range(start, end) генерирует последовательность от start (включительно) до end (исключительно). rangeClosed включает обе границы.
// Индексы для обхода: 0, 1, 2, ..., 99
IntStream indices = IntStream.range(0, 100);

// Дни месяца: 1, 2, ..., 31
IntStream days = IntStream.rangeClosed(1, 31);


Эти методы возвращают упорядоченные, конечные потоки с известным размером — оптимальные характеристики для параллелизма. Spliterator такого потока делится идеально: середина вычисляется как (start + end) >>> 1, создавая сбалансированные сегменты.

iterate и generate

Бесконечные последовательности создаются через iterate (с заданным началом и функцией следующего элемента) или generate (с поставщиком значений).
// Степени двойки: 1, 2, 4, 8, ... с ограничением
IntStream powersOfTwo = IntStream.iterate(1, n -> n * 2)
.limit(20);

// Случайные числа
IntStream randomInts = new Random().ints(); // Бесконечный
IntStream limitedRandom = new Random().ints(100, 0, 1000); // 100 чисел от 0 до 999


iterate не поддерживает эффективное разделение — каждый элемент зависит от предыдущего, что делает параллелизм бесполезным. Random.ints() оптимизирован для параллельного использования через специализированный


Spliterator.

Агрегация примитивов: статистика без промежуточных объектов

Примитивные стримы предоставляют терминальные операции, возвращающие примитивные значения без боксинга:
int sum = IntStream.range(1, 101).sum();  // 5050, возвращает int

long count = LongStream.range(0, 1_000_000).count(); // 1000000L

OptionalDouble average = IntStream.of(10, 20, 30).average(); // OptionalDouble[20.0]

OptionalInt max = IntStream.of(3, 1, 4, 1, 5).max(); // OptionalInt[5]
OptionalInt, OptionalLong, OptionalDouble — примитивные версии Optional, избегающие обёртки. Методы: isPresent(), getAsInt() / getAsLong() / getAsDouble(), orElse(int), orElseGet(IntSupplier).


summaryStatistics: комплексная агрегация

Для многократной статистики за один проход:
IntSummaryStatistics stats = IntStream.of(10, 20, 30, 40, 50)
.summaryStatistics();

System.out.println(stats.getCount()); // 5
System.out.println(stats.getSum()); // 150
System.out.println(stats.getMin()); // 10
System.out.println(stats.getMax()); // 50
System.out.println(stats.getAverage()); // 30.0


IntSummaryStatistics — mutable контейнер, накапливающий данные без промежуточных коллекций. Эффективен для больших потоков: вместо boxed().collect(summarizingInt(Integer::intValue)) используется прямая агрегация примитивов.


#Java #для_новичков #beginner #stream_api #IntStream #LongStream
👍5
Преобразования между типами стримов

boxed: примитив к ссылочному

Когда нужен полноценный Stream<T> с методами объектов:
Stream<Integer> boxed = IntStream.range(0, 100).boxed();

// Применение сложных операций, недоступных для примитивов
List<String> labels = IntStream.range(0, 100)
.boxed()
.map(i -> "Item #" + i)
.collect(toList());


boxed() создаёт объект для каждого примитива — дорогая операция для больших потоков. Применять осознанно, когда объектная семантика необходима.

mapToObj: примитив к произвольному объекту

Более эффективный путь создания объектов из примитивов:
List<Book> booksByIndex = IntStream.range(0, library.size())
.mapToObj(i -> new Book("Book " + i, "Author " + i, 2000 + i))
.collect(toList());
mapToObj(IntFunction<R>) принимает функцию, создающую объект из индекса, без промежуточного boxed().
mapToInt, mapToLong, mapToDouble: объект к примитиву


Обратное преобразование для агрегации:
// Суммарное количество страниц
int totalPages = library.stream()
.mapToInt(Book::pageCount) // Book -> int
.sum();

// Средняя цена в long-центах для точности
long averagePriceCents = library.stream()
.mapToLong(b -> b.price().multiply(BigDecimal.valueOf(100)).longValue())
.average()
.orElse(0L);


Эти методы критичны для производительности: mapToInt(Book::pageCount).sum() вместо map(Book::pageCount).reduce(0, Integer::sum) избегает миллионов аллокаций Integer.


Сценарий: работа с индексами

Классическая задача, не имеющая прямого решения в Stream<T> — доступ к индексу элемента. Примитивные стримы предоставляют элегантный обход:
// Нумерация элементов с сохранением порядка
List<String> numbered = IntStream.range(0, books.size())
.mapToObj(i -> (i + 1) + ". " + books.get(i).title())
.collect(toList());

// Поиск с индексом
OptionalInt firstExpensiveIndex = IntStream.range(0, books.size())
.filter(i -> books.get(i).price().compareTo(THRESHOLD) > 0)
.findFirst(); // Возвращает индекс, не объект


Для List с произвольным доступом это эффективно. Для LinkedList или потоков без размера — требуется промежуточная материализация:
// Обобщённый подход для любого Stream
Stream<IndexedValue<Book>> withIndex = StreamUtils.zipWithIndex(books.stream());

// Или через AtomicInteger (менее элегантно, но без внешних библиотек)
AtomicInteger index = new AtomicInteger(0);
List<String> numbered = books.stream()
.map(b -> index.getAndIncrement() + ": " + b.title())
.collect(toList()); // Не параллелизуемо!



Сценарий: статистические расчёты

Примитивные стримы оптимизированы для математической обработки:
// Корреляция двух наборов данных (упрощённо)
double[] x = fetchDataX();
double[] y = fetchDataY();

double meanX = Arrays.stream(x).average().orElse(0.0);
double meanY = Arrays.stream(y).average().orElse(0.0);

double covariance = IntStream.range(0, x.length)
.mapToDouble(i -> (x[i] - meanX) * (y[i] - meanY))
.average()
.orElse(0.0);
Arrays.stream(double[]) создаёт DoubleStream без копирования массива — эффективный доступ к существующим данным.



Параллелизм примитивных стримов

IntStream.parallel(), LongStream.parallel() наследуют все особенности ForkJoinPool. Дополнительное преимущество — отсутствие boxing при слиянии результатов. sum(), average(), summaryStatistics() в параллельном режиме используют примитивные аккумуляторы, минимизируя синхронизацию.
// Эффективная параллельная агрегация
long sum = LongStream.range(0, 100_000_000)
.parallel()
.sum(); // Разделение по сегментам, локальные суммы, финальное слияние


#Java #для_новичков #beginner #stream_api #IntStream #LongStream
👍5
Что выведет код?

import java.util.stream.IntStream;

public class Task250326 {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};

double avg = IntStream.of(arr)
.map(i -> i % 2 == 0 ? i : 0)
.average()
.orElse(0);

System.out.println(avg);

int sum = IntStream.of(arr)
.map(i -> i % 2 == 0 ? i : 0)
.sum();

System.out.println(sum);

long count = IntStream.of(arr)
.map(i -> i % 2 == 0 ? i : 0)
.count();

System.out.println(count);
}
}


#Tasks
👍2
Варианты ответа:
Anonymous Quiz
14%
2.0 6 5
57%
1.2 6 5
14%
2.0 6 3
14%
1.2 6 3
👍3
Что такое instanceof? 🤓

Ответ:

Оператор instanceof
используется для проверки, является ли объект экземпляром определенного класса, подкласса или реализует ли он определенный интерфейс.

Возвращает true или false.

Синтаксис: object instanceof Type. Если объект null, оператор всегда возвращает false (потому что null не является экземпляром ничего).

Важно для безопасного приведения типов перед выполнением операций, специфичных для конкретного класса.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологий сегодня — 26 марта

ℹ️ Кто родился в этот день

Ло́уренс Э́двард «Ла́рри» Пейдж (англ. Lawrence Edward «Larry» Page; 26 марта 1973, Лансинг, штат Мичиган, США) — американский программист, разработчик и сооснователь (совместно с Сергеем Брином) поисковой системы Google. Изобретатель PageRank, самого известного алгоритма ссылочного ранжирования Google. Пейдж является главным исполнительным директором (CEO) материнской компании Google Alphabet. 4 апреля 2011 года вновь стал главным исполнительным директором компании Google, сменив на этом посту Эрика Шмидта, после своей отставки в качестве генерального директора Google в августе 2001. Он объявил о своём намерении уйти в отставку во второй раз в июле 2015 года, чтобы стать главным исполнительным директором Alphabet, на посту во главе которой будут реорганизованы активы Google. Во главе с Пейджем Alphabet стремится добиться значительных успехов в различных отраслях.

Пал Э́рдёш (венг. Erdős Pál; 26 марта 1913, Будапешт — 20 сентября 1996, Варшава) венгерский математик, один из наиболее продуктивных математиков XX века. Работал в самых разных областях современной математики: комбинаторика, теория графов, теория чисел, математический анализ, теория приближений, теория множеств и теория вероятностей. Лауреат множества математических наград, включая премию Вольфа (1983/1984).


🌐 Знаковые события

1999 — зафиксировано появление червя «Мелисса», поразившего за короткое время сотни тысяч компьютеров во всём мире.


#Biography #Birth_Date #Events #26марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
[Совет по Java #019]

Тема: switch по enum без обработки всех значений — бомба замедленного действия.

Проблема: Классический оператор switch на enum с блоком default или без покрытия всех значений является скрытой угрозой.

При добавлении нового элемента в enum (например, после расширения бизнес-логики) компилятор не предупредит о том, что этот новый элемент не обрабатывается в существующих switch. Код продолжит компилироваться, а во время выполнения для нового значения сработает ветка default (или вовсе ничего, что приведет к NullPointerException при попытке присвоения результата).

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

Решение: Начиная с Java 14, используйте switch-выражения (включая синтаксис с ->), которые требуют исчерпывающего покрытия (exhaustiveness) для enum и sealed типов.

Компилятор проверяет, что все возможные значения обработаны, и выдает ошибку при их неполноте. Для старых версий Java можно использовать флаг компиляции -Xlint:fallthrough или включить проверку в IDE, но это менее надежно.

Альтернатива — заменить switch на полиморфизм (метод внутри enum), что полностью исключает проблему.

public class SwitchEnumExample {

public enum Status {
NEW, PROCESSING, COMPLETED, CANCELLED
}

//Антипаттерн: switch с default
public static String badSwitch(Status status) {
switch (status) { // Обрабатывает не все значения
case NEW:
return "Новый";
case PROCESSING:
return "В обработке";
default:
return "Завершен или отменен"; // CANCELLED попадает сюда незаметно
}
}

//Еще хуже: без default
public static String worseSwitch(Status status) {
switch (status) {
case NEW:
return "Новый";
case PROCESSING:
return "В обработке";
case COMPLETED:
return "Завершен";
// CANCELLED не обработан — при его передаче NPE (switch без результата)
}
return null;
}

//Решение 1: Switch-выражение (Java 14+)
public static String goodSwitch(Status status) {
return switch (status) {
case NEW -> "Новый";
case PROCESSING -> "В обработке";
case COMPLETED -> "Завершен";
case CANCELLED -> "Отменен";
// Компилятор проверит, что все варианты покрыты
};
}

//Решение 2: Switch-выражение с блоком
public static String goodSwitchWithBlock(Status status) {
return switch (status) {
case NEW -> "Новый";
case PROCESSING -> "В обработке";
case COMPLETED -> {
String msg = "Завершен";
yield msg; // yield вместо return в блоке
}
case CANCELLED -> "Отменен";
};
}

// Теперь добавление нового элемента enum требует переопределения метода
// — компилятор принуждает к этому

public static void main(String[] args) {
System.out.println(goodSwitch(Status.NEW));
System.out.println(goodSwitch(Status.CANCELLED));

System.out.println(StatusWithMethod.COMPLETED.getDisplayName());

// Демонстрация проблемы
System.out.println(badSwitch(Status.CANCELLED)); // "Завершен или отменен" — потеря информации
}
}


Объяснение: Switch-выражения в Java 14+ (стандартизированы в Java 17) являются выражением, а не оператором, и требуют исчерпывающего покрытия для enum.

Если в enum добавится новый элемент, код с switch-выражением перестанет компилироваться, заставляя разработчика явно обработать новое значение. Это пример безопасной эволюции кода.

Старый оператор switch позволяет пропустить обработку, что делает код хрупким. Полиморфный подход с абстрактным методом внутри enum еще надежнее: добавление нового элемента требует немедленной реализации метода, и IDE подсветит ошибку.

#Java #советы
👍4
Что выведет код?

public class Task260326 {
enum Level {
LOW, MEDIUM, HIGH
}

public static void main(String[] args) {
Level level = Level.valueOf("HIGH");
String result = getPriority260326(level);
System.out.println(result);
}

static String getPriority260326(Level level) {
switch (level) {
case LOW:
return "Low priority";
case MEDIUM:
return "Medium priority";
}
return "Unknown";
}
}


#Tasks
👍4
Варианты ответа:
Anonymous Quiz
0%
Low priority
10%
Medium priority
5%
High priority
85%
Unknown
👍6
Что такое тернарный оператор? 🤓

Ответ:

Тернарный оператор
— это сокращенная форма записи условного оператора if-else.

Имеет синтаксис: условие ? выражение1 : выражение2.

Если условие истинно, вычисляется и возвращается выражение1, иначе — выражение2. Важно, что типы выражение1 и выражение2 должны быть совместимы для присваивания. Используется для краткости, например: int max = (a > b) ? a : b;.

Не стоит злоупотреблять вложенными тернарными операторами — код становится трудночитаемым.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологий сегодня — 27 марта

ℹ️ Кто родился в этот день

Джон Ро́бинсон Пирс (англ. John Robinson Pierce; 27 марта 1910, Де-Мойн — 2 апреля 2002, Саннивейл, Калифорния) — американский инженер и писатель в жанре научной фантастики. Его работы охватывали широкие области инженерных наук и прикладной физики от радиосвязи, технологий микроволнового излучения, психоакустики до написания компьютерной музыки и научно-фантастических художественных произведений. Начиная с 1936 года, Пирс плодотворно работал в Bell Labs, где занимался различными исследованиями и сотрудничал с многими известными учёными, среди его друзей — нобелевский лауреат Уильям Шокли. Вот лишь краткий перечень работ Пирса, дающий понимание спектра его интересов как инженера и учёного:
Исследования и разработка электронных ламп, в частности, совместное участие с Рудольфом Компфнером в создании лампы бегущей волны;
Изобретение вторично-электронного умножителя;
Исследования в области разработки магнетронов для радаров и отражательных клистронов;
Участие в разработке импульсно-кодовой модуляции для оцифровки аналоговых сигналов (совместно с Клодом Шенноном и Бернардом Оливером);
Большая роль в разработке первого коммерческого спутника связи Телстар. Артур Кларк за эту работу назвал Пирса «одним из двух отцов спутниковой связи» (наряду с Хэролдом Розеном).


Вильге́льм Ко́нрад Рёнтген (в русской традиции Рентге́н [р’инг’э́н]; нем. Wilhelm Conrad Röntgen; 27 марта 1845 года, Леннеп — 10 февраля 1923 года, Мюнхен) немецкий физик, работавший в Вюрцбургском университете. С 1875 года служил профессором в Хоэнхайме, с 1876 года — профессором физики в Страсбурге, с 1879 года — в Гиссене, с 1885 года — в Вюрцбурге, с 1899 года — в Мюнхене. Первый в истории лауреат Нобелевской премии по физике (1901 год) за открытие в 1895 году излучения, впоследствии названного в его честь.


🌐 Знаковые события

1878 — Русский крестьянин Ф. Блинов подал заявку на получение патента на изобретённый им «вагон с нескончаемыми рельсами» (первый в мире гусеничный трактор).


#Biography #Birth_Date #Events #27марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Раздел 8. Stream API и функциональный стиль в Java

Глава 7: Stream API в экосистеме Java. Интеграционные паттерны

Optional и Stream — братья по духу

Optional и Stream появились в Java 8 как единое архитектурное видение: переход от императивного управления состоянием к декларативной композиции операций.

Оба API разделяют ключевые принципы: ленивость вычислений, функциональная обработка отсутствия значения (пустой поток / пустой Optional), цепочечная композиция методов. Понимание их взаимодействия открывает идиомы, устраняющие громоздкие проверки if (x != null) и if (optional.isPresent()).


Optional.stream(): мост между мирами

Метод Optional.stream(), добавленный в Java 9, — один из наиболее недооценённых инструментов. Он превращает Optional<T> в Stream<T>: непустой Optional становится потоком из одного элемента, пустой — пустым потоком. Это позволяет интегрировать Optional в потоковые конвейеры без императивных ветвлений.

Императивный антипаттерн:
List<String> names = new ArrayList<>();
Optional<User> optionalUser = userRepository.findById(userId);

if (optionalUser.isPresent()) {
User user = optionalUser.get();
names.add(user.name());
}
// names содержит 0 или 1 элемент, логика размазана по условию


Проблемы: мутация внешней коллекции, побочный эффект в ifPresent, невозможность дальнейшей композиции. Код читается как инструкция, а не как намерение.

Декларативная трансформация:
List<String> names = userRepository.findById(userId)
.stream() // Optional<User> -> Stream<User> (0 или 1 элемент)
.map(User::name) // Stream<String>
.collect(toList()); // List<String>, размер 0 или 1


Здесь отсутствие значения обрабатывается естественно: пустой Optional порождает пустой поток, map не применяется, collect возвращает пустой список. Нет условий, нет ветвлений, нет мутации внешнего состояния.


Композиция Optional в потоковых цепочках

Сила Optional.stream() проявляется в сложных конвейерах, где несколько операций могут вернуть пустой результат:
// Цепочка: заказ -> клиент -> адрес -> город
List<String> cities = orders.stream()
.map(Order::getCustomerId)
.map(customerRepository::findById) // Stream<Optional<Customer>>
.flatMap(Optional::stream) // Stream<Customer>, пустые отфильтрованы
.map(Customer::getAddress)
.map(Address::getCity)
.distinct()
.collect(toList());


Без Optional.stream() потребовались бы вложенные filter(Optional::isPresent) и map(Optional::get), или flatMap с лямбдой, возвращающей Stream.empty() или Stream.of(value).


#Java #для_новичков #beginner #stream_api #optional
👍7
flatMap для раскрытия вложенности

Когда Optional содержит объект, который сам возвращает поток или коллекцию, flatMap объединяет уровни вложенности:
// Найти все заказы пользователя по ID, если пользователь существует
Stream<Order> userOrders = userRepository.findById(userId)
.stream() // Stream<User>
.flatMap(u -> u.getOrders().stream()); // Stream<Order>

// Сбор в список, если пользователь найден
List<Order> orders = userOrders.collect(toList());


Здесь flatMap играет двойную роль: раскрывает Optional (через stream()) и раскрывает внутреннюю коллекцию заказов. Пустой Optional прерывает цепочку, возвращая пустой поток.

Более сложный сценарий — цепочка зависимых Optional:
// Было: глубокая вложенность ifPresent
optionalUser.ifPresent(user ->
user.getProfile().ifPresent(profile ->
profile.getAvatar().ifPresent(avatar ->
process(avatar)
)
)
);

// Стало: плоская композиция через stream и flatMap
userRepository.findById(userId)
.stream()
.flatMap(u -> u.getProfile().stream())
.flatMap(p -> p.getAvatar().stream())
.forEach(this::process);


Каждый flatMap обрабатывает потенциальное отсутствие значения, пропуская дальнейшие операции для пустых Optional. Результат — линейная цепочка вместо пирамиды вложенности.


Stream в Optional: reduce и find

Обратное направление — терминальные операции потока, возвращающие Optional. Это естественное завершение конвейера, где результат может отсутствовать:
Optional<Book> oldest = library.stream()
.filter(b -> b.genre() == Genre.FICTION)
.min(comparing(Book::year)); // Optional<Book>

// Интеграция обратно в поток
List<String> oldestTitle = oldest
.stream() // Optional -> Stream
.map(Book::title)
.collect(toList());


findFirst(), findAny(), min(), max(), reduce() — все возвращают Optional, сигнализируя о возможности пустого результата. Композиция этих операций с Optional.stream() создаёт бесшовные цепочки обработки, где отсутствие данных — нормальный сценарий, не исключительная ситуация.


Обработка коллекций в Optional

Когда Optional содержит коллекцию, которая сама может быть пустой, Optional.stream() сочетается с flatMap для нормализации:
// Репозиторий возвращает Optional<List<Order>> — неудачный дизайн, но встречается
Optional<List<Order>> optionalOrders = orderRepository.findByCustomerId(id);

// Нормализация: Optional<List<Order>> -> Stream<Order>
Stream<Order> orders = optionalOrders
.stream() // Stream<List<Order>>
.flatMap(List::stream); // Stream<Order>, пустой если Optional пуст или список пуст

// Или в одном выражении
List<Order> result = optionalOrders.stream()
.flatMap(List::stream)
.filter(Order::isActive)
.collect(toList());


Этот паттерн устраняет необходимость проверять optionalOrders.isPresent() и optionalOrders.get().isEmpty() отдельно.


#Java #для_новичков #beginner #stream_api #optional
👍4
Что выведет код?

import java.util.*;
import java.util.stream.*;

public class Task270326 {
public static void main(String[] args) {
List<Optional<String>> optionals = Arrays.asList(
Optional.of("java"),
Optional.empty(),
Optional.of("kotlin"),
Optional.empty(),
Optional.of("scala")
);

List<String> result = optionals.stream()
.flatMap(opt -> opt.stream())
.map(String::toUpperCase)
.collect(Collectors.toList());

System.out.println(result);
}
}


#Tasks
👍3
https://devforge.ru/

Маленький анонс...

Первый шаг к самостоятельности сделан 💪
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥2
Подписался час назад... 🤨

Теперь 2 вопроса.

Как нашел, а главное ПОЧЕМУ????

ссылка - чпуньк

🤔
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🤣3🤯11